【问题标题】:PHP parent & subclass DB operationsPHP 父类和子类 DB 操作
【发布时间】:2012-09-24 11:39:39
【问题描述】:

PHP 和 OOP 的新手,所以请多多包涵……我正处于设计和编写我的第一个 PHP 和 OOP 网站的乐观早期阶段,我一生都在编写蹩脚的 M$ VBA 垃圾。

我有一个“用户”类,它有一个保存方法以及相关的数据库调用等......(实际上我有处理连接和 CRUD 的 DB 和 DBUtils 类 - 我的业务类只调用选择、更新、删除方法在 DB Utils 上并传递关联的数据数组)

随便...

在“管理员”上处理保存方法的最佳方法是什么?我知道如果我向 Admin 类添加一个保存方法,它将取代 User 上的方法,但我不希望它这样做。我想在 Admin 上编写 save 方法,只处理特定于 Admin 对象的属性等,以及从“User”继承的属性在 User save 方法中处理。

这有意义吗?它是我正在寻找的特定 OOP 模式吗?任何有关我应该如何设计和构建此代码的帮助或指导都将不胜感激。

编辑:哇!感谢以下所有答案。不确定哪个是我的首选。我得去玩玩……

【问题讨论】:

  • 即使你重写了Admin类中的save方法,你仍然可以通过parent::save()调用父类(即User)中的那个。
  • 为什么不使用现成的 ORM?例如,我最喜欢的:RedBean 它非常易于使用并且提供快速开发。

标签: php database oop inheritance


【解决方案1】:

您的主要问题源于您一直忽略 OOP 中的核心思想之一:single responsibility principle .. 再一次,似乎每个提供“答案”的人都不知道 SRP 是什么要么。

您所说的“业务逻辑”应该与存储相关的操作分开。 UserAdmin 的实例都不应该知道存储是如何执行的,因为它不是业务逻辑。

$entity = new Admin;
$mapper = new AdminMapper( $db );

$entity->setName('Wintermute');
$mapper->fetch( $entity ); 
//retrieve data for admin with that name

$entity->setName('Neuromancer');
$mapper->store( $entity );
// rename and save

您在上面看到的是data mapper 模式的一个极其简化的应用。这种模式的目标是分离业务和存储逻辑。

如果不是直接实例化映射器$mapper = new AdminMapper( $db ),而是由injected 工厂$mapper = $this->mapperFactory->build('Admin') 提供(因为它应该在适当的代码库中),那么您将没有关于存储介质的指示。数据可以存储在 SQL DB 或文件中,也可以存储在一些远程 REST API 中。如果映射器的接口保持不变,您可以随时更换它。

使用工厂可以避免与特定类名的紧密耦合,并且在 SQL 数据库映射器的情况下,可以让您在每个实例中注入相同的数据库连接。

要了解更多信息,您可以阅读thisthisthis

但是,如果您正在认真考虑学习 OOP,那么阅读this book 是强制性的

【讨论】:

  • “再说一次,似乎每个提供“答案”的人都不知道 SRP 是什么”——公平地说,我相信我已经涵盖了这一点。此外,在存储库模式和活动对象模式之间进行选择并不意味着某些东西不是面向对象的 - 鉴于我们不知道这些类中除了持久性之外是否还有其他东西,它可能不会违反 SRP。跨度>
  • @tereško 我对我的应用程序感兴趣,对于这个问题,您提出了 2 个不同的映射器 AdminMapper 和 UserMapper?你将如何实现它们:第一个扩展第二个,覆盖它的 save() 方法?
  • @cyril ,为什么对扩展类如此痴迷?!如果你有 3 种方法:save()open()delete(),而 AdminMapperUserMapper 完全不同,那么扩展的意义何在?
【解决方案2】:

您正在尝试实现Active record pattern

对象持久化的一种方法是提供一个共同的祖先类 [e.g. BasicEntity] 每个子类都扩展,它基于给定的数据模式构建查询:

class BasicEntity
{

    protected $tablename;

    protected $schema;

    public function update()
    {

        $fields = "";
        $placeholders = "";

        foreach($this -> schema as $field => $type)
        {

            // you join the fields here to get something like ('username', 'email', 'enabled', 'createdAt', 'password')
            // then you write your PDO statement providing placeholders like (:?, :?, :?, :?, :?)
            // you'll have to bind parameters based on their $type [int, string, date]

        }

        $query = sprintf(
            "UPDATE %s SET VALUES(%s) = %s",
            $this -> tablename,
            $fields,
            $placeholders
        );

        // execute statement here, handle exceptions, and so...

    }

}

所以你的User 类将是这样的:

class User extends BasicEntity
{

    protected $id;
    protected $username;
    protected $email;
    protected $password;
    protected $enabled;
    protected $createdAt;

    public function __construct()
    {

        $this -> tablename = '_user';
        $this -> schema = array(
            'id'        => 'int',
            'username'  => 'string',
            'email'     => 'string',
            'password'  => 'string',
            'enabled'   => 'int',
            'createdAt' => 'datetime'
        );

    }

}

还有你的Admin 班级:

class Admin extends User
{

    protected $additionalProperty;

    public function __construct()
    {

        parent::__construct();

        $this -> schema['additionalProperty'] = 'string';

    }

}

调用update() 将根据类模式构建正确的查询。这种方法适用于低复杂度级别,因为您会注意到:

  • 如果您[在同一张表上!] 扩展实体,您需要为没有此类字段的行提供空表字段[在本例中为additionalProperty];
  • 如果您的架构发生更改 [例如您更改了变量名],您必须将其硬编码到类构造函数中,从而使其更难维护;
  • 如果您想处理实体之间的关系,那么在每个 SELECT 语句中编写正确的连接将是一件很痛苦的事情,除非您只是编写大量单独的查询,从而降低性能。

要解决第一个问题,您需要对象的组合,因此您不会使主表增长太多[例如,它只是获取对外部AdditionalPropertyList 实体的引用]。

要解决第二个问题,您必须将架构保存在外部文件中或使用inline annotations

要解决第三个问题,您必须编写自己的 ORM [Object Relational Mapping],或者更好地切换到 an existing one

无论如何,出于学习的好处,如果您打算构建可扩展且可维护的应用程序,我会站在巨人的肩膀上,我会选择一个框架。

【讨论】:

    【解决方案3】:

    当您在 Admin 类中编写保存方法时,您可以在推迟到标准保存的父对象之后执行额外的工作...

    class Admin extends User
    {
        // Other methods
    
        public function save()
        {
            parent::save();
            // now save your additional fields wherever they need to go
        }
    }
    

    附带说明,虽然使用对象来表示域,例如 User 和 Admin,但很好 - 在其他地方使用持久性逻辑有时是值得的,例如使用 The Repository Pattern。这可以防止您的对象被绑定到持久性机制。

    【讨论】:

      【解决方案4】:

      有很多方法可以解决这个问题。其中之一是有一个受保护的方法,可以在扩展类中重写以提供来自该扩展类的数据;

      一个例子:

      class User
      {
        public function Save()
        {
          $savestuff = $this->GetProperties();
          // do save acctions
        }
      
        protected function GetProperties()
        {
          return 'user prop';
        }
      }
      
      class Admin Extends User
      {
        protected function GetProperties()
        {
          return  'admin prop';
        }
      } 
      

      【讨论】:

        【解决方案5】:

        尽管是 OO 的新手,但听起来您确实遇到了 PHP 对象模型的主要缺点之一。您想要做的事情可以通过方法重载轻松实现(具有多个具有相同名称但采用不同参数的方法的能力,调用的实际成员函数取决于参数)。

        您可以通过检查Admin 类中save 方法中的参数来解决此问题:

        class Admin
        {
            public function save(Model_Abstract $model = null)//type hinting
            {
                if ($model instanceof Model_Admin)
                {
                    //do admin insert/update stuff here
                    return;
                }
                parent::save($model);//call regular method
            }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-10-21
          • 1970-01-01
          • 1970-01-01
          • 2016-06-22
          • 1970-01-01
          相关资源
          最近更新 更多