Подтвердить что ты не робот

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 = '[email protected]';
$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) ";
  }

}

Вот проблема, я хочу сделать статический метод filter или get (в основном, чтение из базы данных), а затем вернуть массив объектов из данных базы данных

    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'=>'[email protected]']);

Вот проблема: filter как статическая функция __construct в User класс не будет вызван, а table_columns, table_name будет пустым

также в методе filter я не могу получить к ним доступ в любом случае, потому что они не являются статическими... Я могу создать объект User в качестве макета User в методе filter и решить эти проблемы, но так или иначе чувствовать себя правильно

В принципе у меня есть проблема с дизайном, любое предложение приветствуется

4b9b3361

Ответ 1

Проблема заключается в том, что статический объект на самом деле не создается при запуске статически.

Если вы хотите, чтобы конструктор запускался, но все же статическим способом, вам нужен "singleton". Здесь объект создается один раз, а затем вы можете повторно использовать. Вы можете смешать эту технику статическим и нестационарным способом (поскольку вы на самом деле создаете "глобальный" объект, который может использоваться совместно).

Примером является

class Singleton {
    private static $instance;

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

$obj = Singleton::getInstance();

Каждый раз, когда это получает тот же экземпляр и запоминает состояние из ранее.

Если вы хотите, чтобы ваша база кода имела как можно меньше изменений, вы можете создать "инициализированную" переменную статически - вам просто нужно запомнить ее для каждой функции. Хотя это звучит здорово, это даже хуже, чем синглтон, поскольку он все еще помнит состояние. И каждый раз вам нужно запомнить 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" для разных мнений и других примеров или где/где их не использовать.

Ответ 2

Я согласен с большим количеством ваших помещений в вашем коде и дизайне. Сначала - User должен быть не статическим классом. Второй - Base база должна иметь статическую функцию, которая действует factory для 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      }

Проблема заключается в том, что строки 1 и 10 пытаются использовать 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

Это будет похоже на создание каждого свойства public, но вместо прямого доступа к свойствам они сначала будут запускаться через функцию, которой вы управляете.

Это решение требует только добавления одной функции к вашему классу Base и небольшому изменению вашего текущего цикла

public function __set($prop, $value)
{
     if (property_exists($this, $prop)) {
          $this->$prop = $value;
     }
}

а затем измените строку 10 - 11 выше на:

foreach($q as $col => $val) {
    $obj->$col = $val
}

Ответ 3

В целом рекомендуется разделить логику хранения и извлечения данных и структуры самих данных в двух отдельных классах. "Репозиторий" и "Модель". Это делает ваш код более чистым, а также устраняет эту проблему.

Конечно, вы можете реализовать эту структуру разными способами, но что-то вроде этого было бы отличной отправной точкой:

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

Удачи!

Ответ 4

Я не думаю, что с вашим подходом есть что-то непоправимое. Тем не менее, так я и сделал:

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 = '[email protected]';
$u->insert();

И затем базовый класс, мы можем использовать Late Static Binding здесь 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 Late Static Binding и методы вместо свойств.

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

-Notes-

  • Вы также указали "Вставить неправильно" INSTER.
  • Я также поставил _ перед защищенным/приватным материалом, просто что-то, что мне нравится делать.
  • final является необязательным, но вы можете использовать static вместо self, если вы намерены продолжить дочерний класс.
  • метод фильтра нуждается в некоторой работе, поскольку у вас есть некоторый массив для преобразования строк там, а что нет.