基类设计:静态和非静态数据访问


oop model Base class design : static and non-static data access

我想做一个基类…这是一个很小的框架,如果你想练习的话

所以我从子类的例子开始,因为它有更少的代码!

class User extends Base {
    public $id ; 
    public $username ;
    public $email ;
    public $password ;
    function __construct(){
        $this->table_name = 'users';
        $this->set_cols(get_class_vars('User'));
    }
}
$u = new User;
$u->username = 'jason';
$u->email = 'j@gmail.com';
$u->insert();

这是我的基类

class Base {
  protected $table_name ; 
  protected $table_columns ;
  protected function set_cols($cols){
      unset($cols['table_name']);
      unset($cols['table_columns']);
      $this->table_columns = array_keys($cols);
  }
  public function insert(){
      $colums = $values = array();
      foreach($this->table_columns as $col )
      {
        if(!$this->$col) continue ;
        $values[] = $this->$col ;
        $colums[] = $col ;
      }

      $values =  implode(',' , $values);
      $colums =  implode(',' , $colums);
    echo  $sql = "INSTER INTO ".$this->table_name ."   ($colums)
      VALUES ($values) ";
  }
}

这里是问题,我想使filterget方法(基本上从数据库读取)静态,然后从数据库数据返回对象数组

    class Base{
      static function filter($conditions =array()){

          $query_condition =  $conditions ; // some function to convert array to  sql string 
          $query_result = "SELECT * FROM  ".$this->table_name ." WHERE  $query_condition ";
          $export = array();

          $class = get_called_class();
          foreach($query_result as $q )
          {
              $obj =  new $class;   
              foreach($this->table_columns as $col )
              $obj->$col = $q[$col];
              $export[]  = $obj;
          }
      return $export;
   }
}
$users = User::filter(['username'=>'jason' , 'email'=>'j@gmail.com']);

这是问题,filter作为静态函数__constructUser类将不会被调用,table_columns, table_name将是空的

也在filter方法我无法访问它们,因为它们不是静态的…我可以在filter方法中创建一个虚拟的User对象来解决这个问题但不知何故它感觉不对劲

基本上我有一个设计问题,欢迎任何建议

问题是,静态对象并不是真正的"创建"当你静态运行。

如果你想让构造函数运行,但仍然以静态的方式运行,你需要一个"单例"。在这里,对象只创建一次,然后就可以重用了。你可以以静态和非静态的方式混合使用这种技术(因为你实际上是在创建一个可以共享的"全局"对象)。

例如

class Singleton {
    private static $instance;
    public static function getInstance() {
        if (null === static::$instance) {
            self::$instance = new static();
        }
        return self::$instance;
    }
}
$obj = Singleton::getInstance();

每次都会获取相同的实例并记住之前的状态。

如果你想保持你的代码库尽可能少的变化,你可以静态地创建一个"初始化"的变量——你只需要记住在每个函数中调用它。虽然听起来很棒,但它甚至比Singleton更糟糕,因为它仍然记住状态,并且每次都需要记住init。但是,您可以将其与静态和非静态调用混合使用。

class notASingletonHonest {
    private static $initialized = false;
    private static function initialize() {
        if (!self::$initialized) { 
             self::$initialized = true;
             // Run construction stuff...
        }
    }
    public static function functionA() {
        self::$initialize();
        // Do stuff
    }
    public static function functionB() {
        self::$initialize();
        // Do other stuff
    }
}

但是在你确定一个结构之前先阅读一下。第一种比第二种要好得多,但即使你使用它,也要确保你的单例类可以在任何时候真正地运行,而不依赖于以前的状态。

因为这两个类都记住状态,所以有很多代码纯粹主义者警告你不要使用单例。您实际上是在创建一个全局变量,可以在没有任何控制的情况下对其进行操作。(免责声明-我使用单例,我使用了工作所需的任何技术的混合)

Google "php Singleton"获取一系列的意见和更多的例子,或者在哪里/哪里不使用它们。

我同意你在代码和设计中的很多前提。首先,User应该是一个非静态类。第二,Base基应该有一个静态函数作为User对象的工厂。

让我们专注于过滤器方法

中的这部分代码
1      $query_result = "SELECT * FROM  ".$this->table_name ." WHERE  $query_condition ";
2      $export = array();
3
4 
5      $class = get_called_class();
6      foreach($query_result as $q )
7      {
8          $obj =  new $class;   
9
10         foreach($this->table_columns as $col )
11         $obj->$col = $q[$col];
12
13         $export[]  = $obj;
14
15      }

问题是110行试图使用this,你想知道最好的方法来避免它。

我要做的第一个更改是将protected $table_name;更改为const TABLE_NAME,就像在php文档http://php.net/manual/en/language.oop5.constants.php#104260中的评论一样。如果您需要table_name是一个可变变量,那就是糟糕设计的标志。这将允许您将1行更改为:

$class = get_called_class()
$query_result = "SELECT * FROM ". $class::TABLE_NAME . "WHERE $query_condition";

要解决10行的问题-我相信你有两个很好的选择。

选项1 -构造函数:

你可以重写你的构造函数,使其接受第二个可选参数,该参数将是一个数组。然后,构造函数将分配数组的所有值。然后将for循环(615行)重写为:

foreach($query_result as $q)
{
    $export[] = new $class($q);
} 
并将构造函数更改为:
function __construct($vals = array()){
    $columns = get_class_vars('User');
    $this->set_cols($columns);
    foreach($columns as $col)
    {
          if (isset($vals[$col])) {
              $this->$col = $vals[$col];
          }
    }
}

选项2 - Magic _set

这将类似于将每个属性设置为公共,但不是直接访问属性,它们将首先通过您可以控制的函数运行。

这个解决方案只需要在你的Base类中添加一个函数,并对你当前的循环做一个小的改变

public function __set($prop, $value)
{
     if (property_exists($this, $prop)) {
          $this->$prop = $value;
     }
}
然后将上面的10 - 11行改为:
foreach($q as $col => $val) {
    $obj->$col = $val
}

一般来说,将存储和检索数据的逻辑以及数据本身的结构分离到两个单独的类中是一个好主意。一个"存储库"和一个"模型"。这使你的代码更干净,也修复了这个问题。

当然,你可以用很多方法来实现这个结构,但像这样的东西将是一个很好的起点:

class Repository{
    private $modelClass;
    public function __construct($modelClass)
    {
        $this->modelClass = $modelClass;
    }
    public function get($id)
    {
        // Retrieve entity by ID
        $modelClass = $this->modelClass;
        return new $$modelClass();
    }
    public function save(ModelInterface $model)
    {
        $data = $model->getData();
        // Persist data to the database;
    }
}

interface ModelInterface
{
    public function getData();
}

class User implements ModelInterface;
{
    public int $userId;
    public string $userName;
    public function getData()
    {
        return [
            "userId" => $userId,
            "userName" => $userName
        ];
    }
}
$userRepository = new Repository('User');
$user = $userRepository->get(2);
echo $user->userName; // Prints out the username

祝你好运!

我不认为你的方法有什么本质上的错误。也就是说,我要这样做:

final class User extends Base {
    public $id ;
    public $username ;
    public $email ;
    public $password ;
    protected static $_table_name = 'users';
    protected static $_table_columns;
    public static function getTableColumns(){
        if( !self::$_table_columns ){
            //cache this on the first call
            self::$_table_columns = self::_set_cols( get_class_vars('User') );
        }
        return self::$_table_columns;
    }
    public static function getTableName(){
        return self::$_table_name;
    }
    protected static function _set_cols($cols){
        unset($cols['_table_name']);
        unset($cols['_table_columns']);
        return array_keys($cols);
    }
}
$u = new User;
$u->username = 'jason';
$u->email = 'j@gmail.com';
$u->insert();

然后是基类,我们可以在这里使用晚期静态绑定static而不是self

abstract class Base {
    abstract static function getTableName();
    abstract static function getTableColumns();
    public function insert(){
        $colums = $values = array();
        foreach( static::getTableColumns() as $col ){
            if(!$this->$col) continue ;
            $values[] = $this->$col ;
            $colums[] = $col ;
        }
        $values =  implode(',' , $values);
        $colums =  implode(',' , $colums);
        echo  $sql = "INSERT INTO ". static::getTableName() ." ($colums)    VALUES ($values) ";
    }
    static function filter($conditions =array()){

        $query_condition =  $conditions ; // some function to convert array to  sql string
        $query_result = "SELECT * FROM  ".static::getTableName() ." WHERE  $query_condition ";
        $export = array();
        $columns = static::getTableColumns(); //no need to call this in the loop

        $class = get_called_class();
        foreach($query_result as $q ){
            $obj =  new $class;
            foreach( $columns as $col ){
                $obj->$col = $q[$col];
            }
            $export[]  = $obj;
        }
        return $export;
    }
}

从表面上看,这似乎微不足道,但考虑一下:

class User extends Base {
    public $id ; 
    public $username ;
    public $email ;
    public $password ;
    final public static function getTableName(){
        return 'users';
    }
    final public static function getTableColumns(){
        return [
            'id',
            'username',
            'email',
            'password'
        ];
    }
}

这里我们有一个与第一个Users类完全不同的方法实现。所以我们所做的就是在它所属的子类中强制实现这些值。

此外,通过使用方法而不是属性,我们可以为这些值放置自定义逻辑。这可以简单到返回一个数组或获取定义的属性并过滤掉其中的一些。如果出于其他原因需要,我们也可以在类外访问它们。

所以总的来说,你并没有那么远,你只需要使用static晚期静态绑定,和方法而不是属性。

http://php.net/manual/en/language.oop5.late-static-bindings.php

指出,

  • 你还拼错了插入INSTER
  • 我也把_在保护/私人的东西前面,只是我喜欢做的事情。
  • final是可选的,但如果你打算进一步扩展子类,你可能想要使用static而不是self
  • 过滤器方法,还需要一些工作,因为你有一些数组到字符串的转换,等等。