12 votes

Modèle d'exploitation Conception des classes de base : accès statique et non statique aux données

J'essaie de créer une classe de base ... un petit cadre si vous voulez, juste pour m'entraîner.

Donc je commence par l'exemple de la classe enfant parce qu'elle a moins de code ! !!

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();

Voici ma classe de base

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) ";
  }

}

Voici le problème, je veux faire filter o get (essentiellement la lecture de la base de données) statique, puis renvoie un tableau d'objets à partir des données de la base de données.

    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']);

Voici le problème, avec filter en tant que fonction statique __construct en User ne sera pas appelée et la classe table_columns , table_name sera vide

également dans le filter je ne peux pas y accéder de toute façon car ils ne sont pas statiques ... Je peux créer une méthode factice User dans le filter et résoudre ce problème, mais d'une certaine manière, ça ne semble pas correct.

En fait, j'ai un problème de conception. Toute suggestion est la bienvenue.

3voto

Robbie Points 9099

Le problème est que l'objet statique n'est pas vraiment "créé" lors de l'exécution statique.

Si vous voulez que le constructeur s'exécute, mais toujours de manière statique, vous avez besoin d'un "singleton". L'objet est alors créé une fois et peut être réutilisé. Vous pouvez mélanger cette technique de manière statique et non statique (car vous créez en fait un objet "global" qui peut être partagé).

Un exemple est

class Singleton {
    private static $instance;

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

$obj = Singleton::getInstance();

A chaque fois, on obtient la même instance et on se souvient de l'état antérieur.

Si vous souhaitez conserver votre base de code avec le moins de changements possible, vous pouvez créer vous-même une variable "initialisée" de manière statique - vous devez juste vous rappeler de l'appeler dans chaque fonction. Bien que cela semble génial, c'est encore pire qu'un Singleton car il se souvient toujours de l'état ET vous devez vous souvenir de l'init à chaque fois. Vous pouvez toutefois l'utiliser en mélangeant des appels statiques et non statiques.

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
    }
}

Mais lisez un peu avant de vous fixer sur une structure. La première est bien meilleure que la seconde, mais même si vous l'utilisez, assurez-vous que vos classes singleton peuvent réellement être exécutées à tout moment sans dépendre de l'état précédent.

Comme les deux classes se souviennent de l'état, de nombreux puristes du code vous mettent en garde contre l'utilisation de singletons. Vous créez essentiellement une variable globale qui peut être manipulée sans contrôle de n'importe où. (Disclaimer - j'utilise des singletons, j'utilise un mélange de toutes les techniques requises pour le travail).

Recherchez sur Google "php Singleton" pour obtenir un éventail d'opinions et d'exemples ou pour savoir où et comment ne pas les utiliser.

2voto

Scott Points 2287

Je suis d'accord avec beaucoup de vos prémisses dans votre code et votre conception. Premièrement - User doit être une classe non statique. Deuxièmement - Base doit avoir une fonction statique qui agit comme une usine pour les éléments suivants User objets.

Concentrons-nous sur cette partie de votre code à l'intérieur de la méthode de filtrage.

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      }

Le problème est que les lignes 1 y 10 essayez d'utiliser this et vous voulez connaître le meilleur moyen de l'éviter.

Le premier changement que je ferais serait de changer protected $table_name; à const TABLE_NAME comme dans ce commentaire dans les docs php http://php.net/manual/en/language.oop5.constants.php#104260 . Si vous avez besoin table_name pour être une variable changeante, c'est le signe d'une mauvaise conception. Cela vous permettra de changer de ligne 1 à :

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

Pour résoudre le problème en ligne 10 - Je crois que vous avez deux bonnes options.

Option 1 - Constructeur :

Vous pouvez réécrire votre constructeur pour qu'il prenne un second paramètre optionnel qui serait un tableau. Votre constructeur assignerait alors toutes les valeurs du tableau. Vous réécrivez ensuite votre for boucle (lignes 6 à 15 ) à :

foreach($query_result as $q)
{
    $export[] = new $class($q);
} 

Et changez votre constructeur en :

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];
          }
    }
}

Option 2 - Magic __set

Cela reviendrait à rendre chaque propriété publique, mais au lieu d'un accès direct aux propriétés, celles-ci passeraient d'abord par une fonction que vous contrôlez.

Cette solution ne requiert que l'ajout d'une seule fonction à votre Base et une petite modification de votre boucle actuelle

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

et ensuite changer la ligne 10 - 11 au-dessus de :

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

1voto

Frank Houweling Points 475

En général, c'est une bonne idée de séparer la logique de stockage et de récupération des données et la structure des données elles-mêmes dans deux classes distinctes. Un 'Référentiel' et un 'Modèle'. Cela rend votre code plus propre, et résout également ce problème.

Bien sûr, vous pouvez mettre en œuvre cette structure de nombreuses façons, mais quelque chose comme ceci serait un excellent point de départ :

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

Bonne chance !

1voto

ArtisiticPhoenix Points 1091

Je ne pense pas qu'il y ait quelque chose d'intrinsèquement mauvais dans votre approche. Cela dit, c'est la façon dont je le ferais :

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();

Et puis la classe de base, nous pouvons utiliser la liaison statique tardive ici. static au lieu de 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;
    }

}

En apparence, cela semble trivial, mais réfléchissez à ceci :

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'
        ];
    }

}

Ici, nous avons une implémentation de ces méthodes complètement différente de celle de la première classe d'utilisateurs. Ce que nous avons fait, c'est forcer l'implémentation de ces valeurs dans les classes enfant, là où elles doivent l'être.

De plus, en utilisant des méthodes au lieu de propriétés, nous avons un endroit où placer une logique personnalisée pour ces valeurs. Cela peut être aussi simple que de retourner un tableau ou de récupérer les propriétés définies et d'en filtrer quelques-unes. Nous pouvons également y accéder en dehors de la classe (comme il se doit) si nous en avons besoin pour une autre raison.

Donc, dans l'ensemble, vous n'étiez pas si loin, vous aviez juste besoin d'utiliser static Late Static Binding, et les méthodes au lieu des propriétés.

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

-Notes-

  • vous avez également mal orthographié Insert INSTER .
  • J'ai aussi mis _ devant des trucs protégés/privés, c'est juste quelque chose que j'aime faire.
  • final est facultatif mais vous pouvez utiliser static au lieu de self si vous avez l'intention d'étendre davantage la classe enfant.
  • la méthode de filtrage doit être retravaillée car il y a une conversion de tableau en chaîne de caractères et autres.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X