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

时间:2016-09-19 08:23:03

标签: php oop

我正在努力建立一个基类...如果你只是为了练习而建立一个小框架

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

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因为__construct类中的静态函数User不会被调用而table_columnstable_name将为空

同样在filter方法中我无法访问它们因为它们不是静态的...我可以在User方法中创建一个虚拟filter对象并解决这个问题但不知怎的,它感觉不对

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

4 个答案:

答案 0 :(得分:3)

问题是静态对象在静态运行时并未真正“创建”。

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

一个例子是

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”提供了一系列意见和更多示例,或者在何处/何处不使用它们。

答案 1 :(得分:2)

我同意您的代码和设计中的许多前提。首先 - User应该是非静态类。其次 - Base 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 docs 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循环(行6重写为15)重写为:

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 - 魔术__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
}

答案 2 :(得分:1)

通常,在两个单独的类中分离存储和检索数据的逻辑以及数据本身的结构是一个好主意。 '存储库'和'模型'。这使您的代码更清晰,并解决了这个问题。

当然,您可以通过多种方式实现此结构,但这样的事情将是一个很好的起点:

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
祝你好运!

答案 3 :(得分:1)

我不认为你的方法存在任何本质上的错误。也就是说,这就是我这样做的方式:

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使用Late Static Binding。

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

-Notes-

  • 您还拼写错误的INSTER
  • 我还把_放在受保护/私密的东西面前,这只是我喜欢做的事情。
  • final是可选的,但如果您打算进一步扩展子类,则可能需要使用static而不是self
  • 过滤方法,需要一些工作,因为你有一些数组到字符串转换那里,什么不是。