IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage PHP Discussion :

abstract static function génère un E_STRICT, pourquoi? [PHP 5.3]


Sujet :

Langage PHP

  1. #1
    Membre expérimenté
    Homme Profil pro
    Étudiant
    Inscrit en
    Novembre 2009
    Messages
    141
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : Santé

    Informations forums :
    Inscription : Novembre 2009
    Messages : 141
    Par défaut abstract static function génère un E_STRICT, pourquoi?
    Bonjour,

    Je viens de remarquer qu'en php 5.3 une méthode abstraite et statique génère un warning. J'ai remarqué que dans la doc de php cela était brièvement évoqué ici :
    http://php.net/manual/en/migration52.incompatible.php (Dropped abstract static class functions. Due to an oversight, PHP 5.0.x and 5.1.x allowed abstract static functions in classes. As of PHP 5.2.x, only interfaces can have them.)

    Le fait est que je ne comprends pas pourquoi ce paradigme objet nous est interdit.

    Le fait de passer cette méthode dans une interface nous oblige à la rendre publique, or dans certains cas (notamment le mien), cette méthode ne doit pas être accessible depuis l’extérieur (protected) et doit avoir un comportement spécifique pour chaque classe enfant. Donc quel est le problème avec abstract static?

    J'aimerais avoir l'avis d'experts afin de savoir si je dois absolument retirer mes méthodes et repenser la conception globale? J'aimerais aussi avoir des informations sur le pourquoi?
    Merci d'avance.

  2. #2
    Expert confirmé
    Avatar de Benjamin Delespierre
    Profil pro
    Développeur Web
    Inscrit en
    Février 2010
    Messages
    3 929
    Détails du profil
    Informations personnelles :
    Âge : 37
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Février 2010
    Messages : 3 929
    Par défaut
    C'est dû au fait qu'une méthode statique héritée est malgré tout exécutée dans le contexte de la mère et non de la fille (à moins d'utiliser le mot clé static en PHP 5.3 en lieu et place de self).

    Cette erreur invite le programmeur à se protéger du cas suivant:
    Code php : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    abstract class A {
    	public static function foo () {
    		self::bar();
    	}
    	abstract public static function bar ();
    }
     
    class B extends A {
    	public static function bar () {
    		echo __METHOD__;
    	}
    }
     
    B::foo(); // Fatal error: Cannot call abstract method A::bar()

    On pourrait penser que ça fonctionne vu que B hérite naturellement de la méthode foo(), mais vu qu'elle est exécutée dans le contexte de A et que celle-ci n'a pas de définition pour bar(), ça part en fatal error.

  3. #3
    Membre expérimenté
    Homme Profil pro
    Étudiant
    Inscrit en
    Novembre 2009
    Messages
    141
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : Santé

    Informations forums :
    Inscription : Novembre 2009
    Messages : 141
    Par défaut
    Bonjour et merci de votre réponse,

    Je comprends bien ce problème et j'utilise d'ailleurs le late static binding (mot clé static au lieu de self) dans mon programme, mais ce que je ne comprends pas c'est le fait de refuser les méthodes abstraites et statiques. Pour la version 5.3.x, nous n'avons qu'un warning mais dans le futur, pouvons-nous penser que php en fera une erreur (E_PARSE?)?

  4. #4
    Expert confirmé
    Avatar de Benjamin Delespierre
    Profil pro
    Développeur Web
    Inscrit en
    Février 2010
    Messages
    3 929
    Détails du profil
    Informations personnelles :
    Âge : 37
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Février 2010
    Messages : 3 929
    Par défaut
    Disons que ça va à l'encontre du paradigme objet. Normalement, l'héritage statique n'est qu'une vue de l'esprit (Dixit mon prof d'OOP), le late static binding est une spécificité du PHP.

  5. #5
    Membre expérimenté
    Homme Profil pro
    Étudiant
    Inscrit en
    Novembre 2009
    Messages
    141
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : Santé

    Informations forums :
    Inscription : Novembre 2009
    Messages : 141
    Par défaut
    Dans ce cas c'est ma conception qui est à revoir, et pour cela j'aurai bien besoin de conseils.

    J'ai des objets d'abstraction de base de données dont la classe mère est de la forme suivante :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    interface DataAccessOperations{
    	static function getAll();
    	static function delete($id);
    	static function get($id);
    }
     
     
    abstract class ModelObject implements DataAccessOperations{
     
    	protected $id;
     
    	public function getId(){
    		return $this->id;
    	}
    	protected abstract function update();
    	protected abstract function insert();
            //ma méthode abstraite et statique pas trop appréciée par PHP
    	protected abstract static function load($id);
     
    	public static function get($id){
    		$obj = static::load($id);
    		if(!is_null($obj)){
    			return $obj->getConcreteObject();
    		}
    		else{
    			return null;
    		}
    	}
     
    	protected abstract function getConcreteObject();
     
    	public function __toString(){
    		$reflect = new ReflectionClass($this);
    		$props = $reflect->getProperties();
    		$str = $reflect->getName()."{\n";
    		foreach ($props as $prop) {
    			$prop->setAccessible(true);
    			$str .=  "\t".$prop->getName()." : ". $prop->getValue($this)."\n";
    		}
    		$str .= "}\n";
    		return $str;
    	}
    }
    Les classes héritent tous de cette classe ModelObject et proposent donc de manière publique les méthodes de l'interface DataAccessOperations et getId().

    Par contre, au niveau de l'implémentation, elles sont obligées de redéfinir les méthodes insert, update, load, et getConcreteObject.

    Comme on peut le voir la fonction get a pour but de récupérer un objet depuis une base de données (un select + quelques broutilles), et pour cela elle utilise la fonction load définie dans chaque classe.

    Le problème est que ma base de données simule l’héritage (contrainte extérieure, je ne peux pas changer cela), donc mes classes d’abstraction ont aussi des liens d’héritage entre elles. Afin d’éviter que les utilisateurs des classes aient accès à des objets non concrets (ex : la classe Contact qui n'a pas de sens hormis qu'elle est la classe mère de Mail, Fax, Telex,etc...), la méthode get renvoie l'objet concret lié à l'objet actuel. C'est cette méthode qui sera utilisée en dehors de la classe, par contre en interne, la fonction load est obligatoire car on veut pouvoir manipuler les objets de tous types (concrets ou non).

    Auriez-vous des retours à faire sur la conception? Voyez-vous une meilleure manière de faire?

  6. #6
    Expert confirmé
    Avatar de Benjamin Delespierre
    Profil pro
    Développeur Web
    Inscrit en
    Février 2010
    Messages
    3 929
    Détails du profil
    Informations personnelles :
    Âge : 37
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Février 2010
    Messages : 3 929
    Par défaut
    Que des méthodes getAll ou getConcreteObject soient statiques, je veux bien, mais pourquoi toutes tes méthodes CRUD sont statiques ? Elles ont pourtant vocation à travailler sur une instance existante et non dans un contexte global comme c'est le cas dans un contexte de classe.

    Quand à ta méthode __toString, je ne vois vraiment pas pourquoi tu utilise la réflexion, contrairement au comportement du late static binding, les méthodes d'instance sont bien exécutées dans le contexte de leur fille et non celui de leur mère. La classe mère peut donc librement utiliser les membres de sa fille, pas besoin de reflexion.

    C'est ton idée de modéliser le pseudo-héritage du modèle avec un héritage statique en PHP qui n'est pas bonne, tu t'en sortira bien mieux avec une composition voire une façade.

    Personnellement, voici les classes que j'utilise pour la gestion des objets modèle:

    Interface axModel
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    <?php
    /**
     * @brief Model interface file
     * @file axModel.class.php
     */
     
    /**
     * @brief Interface for all model entities
     * 
     * A model entity consist in a single row, extracted from an RDBMS table.
     * 
     * @warning You MUST implement this interface if you want your classes to be generated throught the axDatabase factory.
     * 
     * @interface axModel
     * @author Delespierre
     * @since 1.2.0
     * @ingroup Model
     * @copyright Copyright 2010-2011, Benjamin Delespierre (http://bdelespierre.fr)
     * @license http://www.gnu.org/licenses/lgpl.html Lesser General Public Licence version 3
     */
    interface axModel {
     
        /**
         * @brief Constructor
         * 
         * @param PDO $pdo The database connection instance
         * @param mixed $id @optional @default{null} The ID of the row to match
         */
        public function __construct (PDO $pdo, $id = null);
     
        /**
         * @brief Create a record
         * 
         * Returns the current record in case of success (instance of axModel) or false on failure.
         * 
         * @param array $data The data to be recorded
         * @return axModel
         */
        public function create (array $data);
     
        /**
         * @brief Fetches data from a record according to its id
         * 
         * Returns the current record in case of success (instance of axModel) or false on failure.
         * 
         * @param mixed $id The ID of the record
         * @return axModel
         */
        public function retrieve ($id);
     
        /**
         * @brief Update a record, optionaly added with the @c $data parameter 
         * 
         * Returns the current record in case of success (instance of axModel) or false on failure.
         * 
         * @param array $data @optional @default{array()} The data to add to the record
         * @return axModel
         */
        public function update (array $data = array());
     
        /**
         * @brief Delete a record
         * 
         * Returns the deletion status.
         * 
         * @return boolean
         */
        public function delete ();
     
        /**
         * @brief Get a list of records, optionaly filtered by @c $search_params and @c $options parameters
         * 
         * Returns the list as an instance of axPDOStatementIterator in case of success, false on failure.
         * 
         * @param PDO $pdo
         * @param array $search_params @optional @default{array()} The filtering parameters
         * @param array $options @optional @default{array()} The options parameters
         * @return axPDOStatementIterator
         */
        public static function all (PDO $pdo, array $search_params = array(), array $options = array());
     
        /**
         * @brief Get the record's original table name
         * @return string
         */
        public function getTable ();
     
        /**
         * @brief Get the record's original table columns
         * @return array
         */
        public function getColumns ();
     
        /**
         * @brief Get the record's native data
         * @return array
         */
        public function getData ();
     
    }
     
    /**
     * @brief Model Module
     * 
     * This module contains classes for managing RDBMS entities.
     * 
     * @defgroup Model
     * @author Delespierre
     * @copyright Copyright 2010-2011, Benjamin Delespierre (http://bdelespierre.fr)
     * @license http://www.gnu.org/licenses/lgpl.html Lesser General Public Licence version 3
     */
    classe abstraite axBaseModel
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    <?php
    /**
     * @brief Model base class file
     * @file axBaseModel.class.php
     */
     
    /**
     * @brief Model Base Class
     *
     * @todo axBaseModel long description
     * @warning Only tables with strictly one attribute as primary key can be used with this class. For more complex
     * object types, you should describe your own behavior by implementing axModel.
     * @class axBaseModel
     * @ingroup Model
     * @since 1.2.0
     * @author Delespierre
     * @copyright Copyright 2010-2011, Benjamin Delespierre (http://bdelespierre.fr)
     * @license http://www.gnu.org/licenses/lgpl.html Lesser General Public Licence version 3
     */
    abstract class axBaseModel implements axModel {
     
        /**
         * @brief Database connection object
         * @property PDO $_pdo
         */
        protected $_pdo;
     
        /**
         * @brief Name of the identifying key (the PRIMARY KEY of your table in fact)
         * @property string $_idKey
         */
        protected $_idKey = "id";
     
        /**
         * @brief Record data
         * @property array $_data
         */
        protected $_data = array();
     
        /**
         * @brief Statements cache
         * @property array $_statements
         */
        protected $_statements = array();
     
        /**
         * @brief Initialize a statement
         *
         * This method is intended to create the `PDOStatement` objects used by @e CRUD methods (create, retrieve, update,
         * delete).
         *
         * A statement is identified by its name:
         * @li create   : for record creation
         * @li retrieve : for record retrieving
         * @li update   : for record update
         * @li delete   : for deleting a record
         *
         * @abstract
         * @param string $statement The statement name
         * @return PDOStatement
         */
        abstract protected function _init ($statement);
     
        /**
         * @brief Constructor
         *
         * If @c $id is provided, the axModel::retrieve() method will be called with this parameter.
         *
         * @param PDO $pdo The database connection object
         * @param mixed $id @optional @default{null}
         * @throws InvalidArgumentException If the @c $pdo parameter is null
         * @throws RuntimeException If the @c $id parameter did not match an existing record
         */
        public function __construct (PDO $pdo, $id = null) {
            if (!$pdo)
                throw new InvalidArgumentException('First parameters is expected to be a valid PDO instance');
     
            $this->_pdo = $pdo;
     
            if ($id !== null && $id !== false && !$this->retrieve($id))
                throw new RuntimeException("Cannot instanciate model");
        }
     
        /**
         * @brief __sleep implementation
         * @return array
         */
        public function __sleep () {
            return array('_idKey', '_data');
        }
     
        /**
         * @brief __get implementation
         *
         * Retrieves a record data identified by the @c $key parameter.
         *
         * @param string $key
         * @return mixed
         */
        public function __get ($key) {
            return isset($this->_data[$key]) ? $this->_data[$key] : null;
        }
     
        /**
         * @brief __set implementation
         *
         * Updates a record data identified by the @c $key parameter with the @c $value parameter.
         * @note no implicit call is done to the axModel::update() method. You will have to update manually.
         *
         * @param string $key
         * @param mixed $value
         * @return void
         */
        public function __set ($key, $value) {
            $this->_data[$key] = $value;
        }
     
        /**
         * @brief __isset implementation
         *
         * Tells if a record data exists.
         *
         * @param string $key
         * @return boolean
         */
        public function __isset ($key) {
            return isset($this->_data[$key]);
        }
     
        /**
         * @breif Get record data
         *
         * Will return null if the record hasn't been fetched yet (no call to axModel::retrieve() has been done).
         *
         * @return array
         */
        public function getData () {
            return $this->_data;
        }
     
        /**
         * @brief Create method (Crud)
         *
         * Creates the record over the RDBMS using the @c create prepared statement.
         * Will return false in case of error
         *
         * @param array $data
         * @throws RuntimeException If the @c create statement couldn't be initialized
         * @return axBaseModel
         */
        public function create (array $data) {
            if (!$this->_init("create"))
                throw new RuntimeException("Cannot initialize " . __METHOD__, 2011);
     
            if ($this->_statements['create']->execute($data)) {
                $id = $this->_pdo->lastInsertId();
                return $this->retrieve($id);
            }
            return false;
        }
     
    	/**
         * @brief Retrieve method (cRud)
         *
         * Reads the record over the RDBMS using the @c retrieve prepared statement.
         *
         * @param mixed $id
         * @throws RuntimeException If the @c retrieve statement couldn't be initialized
         * @return axBaseModel
         */
        public function retrieve ($id) {
            if (!$this->_init("retrieve"))
                throw new RuntimeException("Cannot initialize " . __METHOD__, 2010);
     
            if ($this->_statements['retrieve']->execute(array($this->_idKey => $id))) {
                if ($this->_statements['retrieve']->rowCount()) {
                    $this->_data = $this->_statements['retrieve']->fetch(PDO::FETCH_ASSOC);
                    return $this;
                }
            }
            return false;
        }
     
        /**
         * @brief Update method (crUd)
         *
         * Updates the record over the RDBMS using the @c update prepared statement.
         * The @c $data parameter will be merged with the current record data.
         *
         * @param array $data @optional @default{array()} The data to add for updating
         * @throws RuntimeException If the @c update statemetn couldn't be initialized
         * @return axBaseModel
         */
        public function update (array $data = array()) {
            if (!$this->_init("update"))
                throw new RuntimeException("Cannot initialize " . __METHOD__, 2012);
     
            if (!empty($this->_data)) {
                $inputs = array_merge($this->_data, array_intersect_key($data, $this->_data));
                return $this->_statements['update']->execute($inputs) ? $this : false;
            }
            return false;
        }
     
        /**
         * @brief Delete method (cruD)
         *
         * Destruct the record over the RDBMS using the @c delete prepared statement.
         *
         * @throws RuntimeException If the @c delete statement coulnd't be initialized
         * @return boolean
         */
        public function delete () {
            if (!$this->_init("delete"))
                throw new RuntimeException("Cannot initialize " . __METHOD__, 2013);
     
            if (!empty($this->_data))
                return $this->_statements['delete']->execute(array(":{$this->_idKey}" => $this->_data[$this->_idKey]));
            return false;
        }
    }
    classe concrête générique axMySQLObject
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364
    365
    366
    367
    368
    369
    370
    371
    372
    373
    374
    375
    376
    377
    378
    379
    380
    381
    382
    383
    384
    385
    386
    387
    388
    389
    390
    391
    392
    393
    394
    395
    396
    397
    398
    399
    400
    401
    402
    403
    404
    405
    406
    407
    408
    409
    410
    411
    412
    413
    414
    415
    416
    417
    418
    419
    420
    421
    422
    423
    424
    425
    426
    427
    428
    429
    430
    431
    432
    433
    434
    435
    436
    437
    438
    439
    440
    441
    442
    443
    444
    445
    446
    447
    448
    449
    450
    451
    452
    453
    454
    455
    456
    457
    458
    459
    460
    461
    462
    463
    464
    465
    466
    467
    468
    469
    470
    471
    472
    473
    474
    475
    476
    477
    478
    479
    480
    481
    482
    483
    484
    485
    486
    487
    488
    489
    490
    491
    492
    493
    494
    495
    496
    497
    498
    499
    500
    501
    502
    503
    504
    505
    506
    507
    508
    509
    510
    511
    512
    <?php
    /**
     * @brief MySQL object class file
     * @file axMySQLObject.class.php
     */
     
    /**
     * @brief MySQL Object
     *
     * Helper class to map a MySQL row record into an axModel object. Basicaly, this class is just an axModel class
     * capable of understanding a MySQL table structure and to translate it to generic CRUD queries.
     *
     * @warning Only tables with strictly one attribute as primary key can be used with this class. For more complex
     * object types, you should describe your own behavior by implementing `axModel`.
     *
     * @class axMySQLObject
     * @author Delespierre
     * @ingroup Model
     * @copyright Copyright 2010-2011, Benjamin Delespierre (http://bdelespierre.fr)
     * @license http://www.gnu.org/licenses/lgpl.html Lesser General Public Licence version 3
     */
    class axMySQLObject extends axBaseModel {
     
        /**
         * @cond IGNORE
         * Insert query generation constants
         */
        const INSERT  = "INSERT";
        const REPLACE = "REPLACE";
        /**
         * @endcond
         */
     
        /**
         * @brief Table name
         * @property string $_table
         */
        protected $_table;
     
        /**
         * @brief Table structure (describe result)
         * @property array $_structure
         */
        protected $_structure;
     
        /**
         * @copydoc axBaseModel::_init()
         */
        protected function _init ($statement) {
            if (isset($this->_statements[$statement]))
                return $this->_statements[$statement];
     
            if (empty($this->_table) || empty($this->_structure))
                throw new RuntimeException("Invalid instance");
     
            $columns = $this->getColumns();
            if (($offset = array_search($this->_idKey, $columns)) !== false)
                unset($columns[$offset]);
     
            $search_clause = array($this->_idKey => null);
     
            switch ($statement) {
                case 'create':
                    $query = self::_generateInsertQuery(
                        $this->_table,
                        $columns
                    );
                    break;
                case 'retrieve':
                    $scla  = array($this->_idKey => null);
                    $query = $this->_generateSelectQuery(
                        $this->_table,
                        array(),
                        $search_clause
                    );
                    break;
                case 'update':
                    $query = self::_generateUpdateQuery(
                        $this->_table,
                        $columns,
                        $search_clause
                    );
                    break;
                case 'delete':
                    $query = self::_generateDeleteQuery(
                        $this->_table,
                        $search_clause
                    );
                    break;
                default:
                    throw new InvalidArgumentException("Invalid statement $statement");
            }
     
            return $this->_statements[$statement] = $this->_pdo->prepare($query);
        }
     
        /**
         * @brief constructor
         *
         * You may pass an $id to get the row data directly so no further calls to axModel::retrieve is necessary.
         *
         * @param PDO $pdo The database connection instance
         * @param string $tablename The MySQL table name
         * @param string $id @optional @default{null} The ID of the mysql row to match
         */
        public function __construct (PDO $pdo, $id = null) {
            $args = func_get_args();
            if (empty($args))
                throw new InvalidArgumentException('Missing parameters: `$pdo`, `$table`');
     
            if (count($args) === 1)
                throw new InvalidArgumentException('Missing parameter: `$table`');
     
            if (!$args[0] instanceof PDO)
                throw new InvalidArgumentException('`$pdo` parameter must be a valid PDO instance');
     
            if (!is_string($args[1]))
                throw new InvalidArgumentException('`$tablename` parameter must be string');
     
            if (empty($args[1]))
                throw new InvalidArgumentException('`$tablename` parameter cannot be empty');
     
            list($pdo,$tablename,$id) = $args + array(null,'',null);
     
            $this->_table = self::_sanitizeTablename($tablename);
            $this->_pdo   =  $pdo;
     
            if (!$this->_getTableStructure($tablename))
                throw new RuntimeException("Cannot determine {$tablename} structure");
     
            if ($id !== null && $id !== false && !$this->retrieve($id))
                throw new RuntimeException("Cannot instanciate model");
        }
     
        /**
         * @brief Obtain a collection of MySQL Objects optionaly filtered by @c $search_params and @c $options parameters
         *
         * The last @c $object parameter can be either an axModel instance or a valid table name. axMySQLOjbect::all() will
         * generate a generic SQL `SELECT` query to fetch any row that match the @c $search_params and @c $options
         *  conditions (if any). An axPDOStatement instance is returned in case of success, which lets you iterate over the
         * collection. Each collection item is a valid instance of axModel you can obviously perform any CRUD operation on.
         *
         * Usage:
         * @code
         * // Retrive all items in the `mydb`.`users` table
         * axMySQLObject::all($pdo, array(), array(), 'mydb.users');
         *
         * // Retrive all items in the `mydb`.`users` table having the `mail` field set to `foo@bar.com`
         * axMySQLObject::all($pdo, array('mail' => 'foo@bar.com'), array(), 'mydb.users');
         *
         * // Retrieve all items in the `mydb`.`users` and order them by login name
         * axMySQLObject::all($pdo, array(), array('order by' => 'login'), 'mydb.users');
         *
         * // Retrieve all items in the `mydb`.`users` having a `privilege_level` higher than 5
         * axMySQLObject::all($pdo, array('privilege_level >=' => 5), array(), 'mydb.users');
         *
         * // Retrive all users using a valid user instance
         * $user = new User($pdo, $id); // User class implements axModel
         * axMySQLObject::add($pdo, array(), array(), $user);
         * @endcode
         *
         * @warning A field listed in @c $search_params cannot be listed twice, even if you specify the operator.
         * Example:
         * @code
         * // The following call will result as a query error
         * axMySQLObject::all($pdo, array('privilege_level >=' => 5, 'privilege_level <=' => 10), array(), 'mydb.users');
         * @endcode
         *
         * @note you may use the `BETWEEN` operator to restrict results in a given range.
         * Example:
         * @code
         * // Retrieve all items in the `mydb`.`users` having a `privilege_level` higher than 5 and lower than 10
         * axMySQLObject::all($pdo, array('privilege_level BETWEEN' => array(5,10)), array(), 'mydb.users');
         * @endcode
         *
         * @note The `WHERE` clause generation engine will produce prepared statements compliant string (see
         * http://php.net/manual/en/class.pdostatement.php). You should not use invalid replacement values like sub-queries
         * or string containing SQL keywords like 'xxx AND yyy'.
         *
         * The @c $options parameters may have the following parameters (in any order):
         * @li group by : any string or array of strings value describing a field or a list of fields
         * @li limit : an integer or an array containing 2 integers describing the limit bounds
         * @li order by : any string or array of strings value describing a field or a list of fields
         * @li order by type : `ASC` or `DESC`
         * Any other key for the @c $option parameter will be ignored. Any incorrect option parameter will also be ignored.
         * Example:
         * @code
         * $options = array(
         *     'group by'      => array('colA', 'colB'),
         *     'order by'      => 'colC',
         *     'order by type' => 'DESC',
         *     'limit'         => array(0,10)
         * );
         * @endcode
         *
         * Will return false if the generated query execution fails.
         *
         * @warning All parameters are mandatory.
         *
         * @param PDO $pdo
         * @param array $search_params The search parameters
         * @param array $options The query options (group by, order by and limits)
         * @param mixed $object Either a tablename or a valid axObject instance (will trigger an E_USER_WARNING if this
         * parameter is invalid)
         * @throws InvalidArgumentException If the fourth argument is not a string or instance of axModel
         * @return axPDOStatementIterator
         */
        public static function all (PDO $pdo, array $search_params = array(), array $options = array()) {
            if (func_num_args() < 4)
                throw new InvalidArgumentException('Missing fourth parameter');
     
            $arg = func_get_arg(3);
     
            if ($arg instanceof axModel) {
                $mysql_obj = $arg;
            }
            elseif (is_string($arg)) {
                try  {
                    $mysql_obj = new self($pdo, $arg);
                }
                catch (Exception $e) {
                    trigger_error("Unable to create `axMySQLObject` instance: " . $e->getMessage(), E_USER_WARNING);
                    return false;
                }
            }
            else {
                throw new InvalidArgumentException("Fourth argument is expected to be a valid `axModel` instance or ".
                    "string, " . gettype($arg) . " given");
            }
     
            $query  = self::_generateSelectQuery(
                    $mysql_obj->getTable(),
                    $mysql_obj->getColumns(),
                    $search_params,
                    $options
            );
     
            $stmt = $pdo->prepare($query);
            if ($stmt->execute($search_params)) {
                $stmt->setFetchMode(PDO::FETCH_INTO, $mysql_obj);
     
                if (PHP_VERSION_ID < 50200) {
                    $cquery = preg_replace('~SELECT.*FROM~', 'SELECT COUNT(*) FROM', $query);
                    $cstmt = $pdo->prepare($query);
                    !empty($search_params) ? $cstmt->execute($search_params) : $cstmt->execute();
                    $count = (int)$cstmt->fetchColumn();
                    $it->setCount($count);
                }
     
                return new axPDOStatementIterator($stmt);
            }
            return false;
        }
     
        /**
         * @copydoc axModel::getTable()
         */
        public function getTable () {
            return $this->_table;
        }
     
        /**
         * @copydoc axModel::getColumns()
         */
        public function getColumns () {
            return array_keys($this->_structure);
        }
     
        /**
         * @brief Get the complete table structure
         * @return array
         */
        public function getStructure () {
            return $this->_structure;
        }
     
    	/**
         * @brief Get the structure from any table name
         *
         * The structure is parsed from the MySQL DESCRIBE (DESC) query result. Will return true in case of success, false
         * otherwise.
         *
         * @internal
         * @param string $table The tablename
         * @throws InvalidArgumentException If $table parameter is invalid or empty
         * @return boolean
         */
        protected function _getTableStructure ($table) {
            if (!is_string($table) || empty($table))
                throw new InvalidArgumentException("First parameter is expected to be valid string");
     
            $table = self::_sanitizeTablename($table);
     
            if ($stmt = $this->_pdo->query("DESC $table")) {
                $this->_structure = array();
                foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $column) {
                    if (isset($column['Key']) && strpos($column['Key'], 'PRI') !== false)
                        $this->_idKey = $column['Field'];
     
                    $this->_structure[$column['Field']] = array_change_key_case($column, CASE_UPPER);
                }
                return true;
            }
            return false;
        }
     
    	/**
         * @brief Sanitize the tablename
         *
         * Escape properly the given tablename
         * Example:
         * @code
         * $table = self::_sanitizeTablename('database.table');
         * echo $table; // will display `database`.`table`
         * @endcode
         *
         * @param string $table
         * @return string
         */
        protected static function _sanitizeTablename ($table) {
            return '`' . implode('`.`', explode('.', str_replace(array('`', ' '), '', $table))) . '`';
        }
     
        /**
         * @brief Generates a `SELECT` query according to the given parameters
         *
         * See axMySQLObject::all() for more details about @c $search_params and @c $options parameters format.
         *
         * @param string $tablename
         * @param array $columns @optional @default{array()} The column names
         * @param array $search_params @optional @default{array()}
         * @param array $options @optional @default{array()}
         * @throws InvalidArgumentException If the @c $tablename parameter is empty
         * @return string
         */
        protected static function _generateSelectQuery ($tablename,
                                                        array $columns = array(),
                                                        array & $search_params = array(),
                                                        array $options = array()) {
            if (!$tablename)
                throw new InvalidArgumentException('`$tablename` cannot be empty');
     
            if (empty($columns))
                $columns = '*';
            else
                $columns = '`' . implode('`,`', $columns) .  '`';
     
            $query  = "SELECT {$columns} FROM " . self::_sanitizeTablename($tablename);
            $query .= self::_generateWhereClause($search_params);
            $query .= self::_generateOptionClause($options);
     
            return $query;
        }
     
        /**
         * @brief Generate a SQL `INSERT` query
         *
         * @param string $tablename
         * @param array $columns
         * @param string $mode @optional @default{"INSERT"} Possible values are "INSERT" or "REPLACE"
         * @return string
         */
        protected static function _generateInsertQuery ($tablename, array $columns, $mode = self::INSERT) {
            if (!$tablename)
                throw new InvalidArgumentException('`$tablename` cannot be empty');
     
            if (empty($columns))
                throw new InvalidArgumentException('`$columns` cannot be empty');
     
            if ($mode != self::INSERT && $mode!= self::REPLACE)
                throw new InvalidArgumentException("Incorrect insert mode {$mode}");
     
            $tablename = self::_sanitizeTablename($tablename);
     
            foreach ($columns as $value) {
                $cols[] = "`$value`";
                $hold[] = ":{$value}";
            }
            $columns = implode(',', $cols);
            $holders = implode(',', $hold);
     
            return "{$mode} INTO {$tablename} ({$columns}) VALUES ({$holders})";
        }
     
        /**
         * @brief Generate a SQL `UPDATE` query
         *
         * @param string $tablename
         * @param array $columns
         * @param array $search_params @optional @default{array()}
         * @return string
         */
        protected static function _generateUpdateQuery ($tablename,
                                                        array $columns,
                                                        array & $search_params = array()) {
            if (!$tablename)
                throw new InvalidArgumentException('`$tablename` cannot be empty');
     
            if (empty($columns))
                throw new InvalidArgumentException('`$columns` cannot be empty');
     
            $tablename = self::_sanitizeTablename($tablename);
     
            foreach ($columns as $value)
                $pieces[] = "`{$value}`=:{$value}";
     
            $query  = "UPDATE {$tablename} SET " . implode(',', $pieces);
            $query .= self::_generateWhereClause($search_params);
     
            return $query;
        }
     
        /**
         * @brief Generate a SQL `DELETE` query
         *
         * @param string $tablename
         * @param array $search_params @optional @default{array()}
         * @return string
         */
        protected static function _generateDeleteQuery ($tablename, array & $search_params = array()) {
            if (!$tablename)
                throw new InvalidArgumentException('`$tablename` cannot be empty');
     
            $tablename = self::_sanitizeTablename($tablename);
            $query     = "DELETE FROM $tablename";
            $query    .= self::_generateWhereClause($search_params);
     
            return $query;
        }
     
        /**
         * @brief Generates a query `WHERE` clause
         *
         * See axMySQLObject::all() for more details about @c $search_params parameter format.
         * Will return an empty string if the @c $search_params parameter is empty.
         *
         * @param array $search_params
         * @return string
         */
        protected static function _generateWhereClause (array & $search_params) {
            if (!empty($search_params)) {
                $pieces = array();
     
                foreach ($search_params as $key => $value) {
                    if (preg_match('~\s*(?<field>\w+)\s*(?<operator>\W+)\s*~', $key, $matches)) {
                        $field    = $matches['field'];
                        $operator = $matches['operator'];
     
                        // @todo operator detection and replacement (LIKE, WHERE, IN, NOT IN etc.)
     
                        unset($search_params[$key]);
                        $search_params['field'] = $value;
                        $pieces[] = "`{$field}`{$operator}:{$field}";
                    }
                    else {
                        $pieces[] = "`{$key}`=:{$key}";
                    }
                }
     
                return " WHERE " . implode(' AND ', $pieces);
            }
            return "";
        }
     
        /**
         * @brief Generates a query `GROUP BY`, `ORDER BY` and `LIMIT` clauses
         *
         * See axMySQLObject::all() description for more details about the @c $options structure.
         * Will return an empty string if the @c $options parameter is empty.
         *
         * @param array $search_params
         * @return string
         */
        protected static function _generateOptionClause (array $options) {
            $query = "";
     
            if (!empty($options['group by'])) {
                $pieces = array();
     
                foreach((array)$options['group_by'] as $field)
                    $pieces[] = "`{$field}`";
     
                $query .= " GROUP BY ".implode(',' ,$pieces);
            }
     
            if (!empty($options['order by'])) {
                $pieces = array();
     
                foreach($options['order by'] as $field)
                    $pieces[] = "`{$field}`";
     
                $query .= " ORDER BY ".implode(',' ,$pieces);
     
                if (isset($options['order by type'])
                 && in_array(strtoupper($options['order by type']), array('ASC', 'DESC')))
                    $query .= " " . strtoupper($options['order by type']);
            }
     
            if (!empty($options['limit'])) {
                if (count($options['limit']) == 1) {
                    $options['limit'] = (array)$options['limit'];
                    $query .= " LIMIT {$options['limit'][0]}";
                }
     
                if (count($options['limit']) == 2) {
                    $query .= " LIMIT {$options['limit'][0]},{$options['limit'][1]}";
                }
            }
     
            return $query;
        }
    }
    class concrete spécifique FooBar et usage
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <?php
     
    class FooBar extends MySQLObject {
     
      const TABLE = "mydb.foobar";
     
      public function __construct (PDO $pdo, $id = null) {
        parent::__construct($pdo, self::TABLE, $id);
      }
     
      public static function all (PDO $pdo, array $search_params = array(), array $options = array()) {
         return parent::all($pdo, $search_params, $options, new self($pdo));
      }
    }
     
    $pdo = new PDO(...);
     
    foreach (FooBar::all($pdo) as $foobar) {
      $foobar->delete();
    }
    Plutôt simple non ?

  7. #7
    Membre expérimenté
    Homme Profil pro
    Étudiant
    Inscrit en
    Novembre 2009
    Messages
    141
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : Santé

    Informations forums :
    Inscription : Novembre 2009
    Messages : 141
    Par défaut
    Merci de cette réponse très complète.

    Que des méthodes getAll ou getConcreteObject soient statiques, je veux bien, mais pourquoi toutes tes méthodes CRUD sont statiques ? Elles ont pourtant vocation à travailler sur une instance existante et non dans un contexte global comme c'est le cas dans un contexte de classe.
    La méthode getConcreteObject n'est pas statique car elle intervient pour un objet donné (ayant un id d'une table de la base, afin de pouvoir faire les jointures nécessaires pour trouver la table fille et construire l'objet fille).

    A propos de CRUD, seuls get, getAll et delete sont statiques, car pour moi elles n'agissent pas sur une instance d'un objet :
    - Mail::get($id_mail) renvoie un objet contenant toutes les valeurs
    - Mail::getAll() renvoie tous les mails (objets construits par get en interne)
    - Mail::delete($id_mail) supprime un mail de la base (sur ce point j'avoue que ce n'est pas le meilleure manière de faire, je vais probablement la passer en méthode d'instance).

    Par contre, les méthodes update et insert sont des méthodes d'instance car elles interviennent au niveau de l'objet lui-même. Elles ne sont par contre pas accessibles à l’extérieur de la classe (la synchronisation avec la base de données est gérée automatiquement, l'utilisateur n'a pas à s'en soucier).

    De plus chaque classe a une méthode create (statique elle aussi) qui gère la synchonisation et les constructeurs de chaque classe sont protected.

    Quand à ta méthode __toString, je ne vois vraiment pas pourquoi tu utilise la réflexion, contrairement au comportement du late static binding, les méthodes d'instance sont bien exécutées dans le contexte de leur fille et non celui de leur mère. La classe mère peut donc librement utiliser les membres de sa fille, pas besoin de reflexion.
    Cette méthode est implémentée seulement pour la phase de développement et sera supprimée ensuite. La raison d'utiliser la réflexion est simple, chaque classe a des attributs différents (correspondants aux champs en base), et j'affiche simplement leurs valeurs (mêmes les attributs private, ce qui ne peux pas rester lors d'une mise en production).

    C'est ton idée de modéliser le pseudo-héritage du modèle avec un héritage statique en PHP qui n'est pas bonne, tu t'en sortira bien mieux avec une composition voire une façade.
    Je suis désolé mais pourriez-vous me ré-expliquer ce que vous voulez dire, je n'ai pas vraiment compris.

    Concernant vos classes, elles sont je le reconnais simples et efficaces, et respectent de plus les paradigmes objet.

    Je ne peux malheureusement pas m'en inspirer car elles donnent trop de pouvoir à l'utilisateur de la classe.
    Un exemple : dans mon cas, une instruction comme la suivante n'aurait pas de sens :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    $mail = new Mail($pdo_instance);
    car cet objet ne serait pas synchronisé avec la base (pas d'identifiant).

  8. #8
    Expert confirmé
    Avatar de Benjamin Delespierre
    Profil pro
    Développeur Web
    Inscrit en
    Février 2010
    Messages
    3 929
    Détails du profil
    Informations personnelles :
    Âge : 37
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Février 2010
    Messages : 3 929
    Par défaut
    A propos de CRUD, seuls get, getAll et delete sont statiques, car pour moi elles n'agissent pas sur une instance d'un objet
    Donc selon toi, la suppression d'un élément est indépendante de son existence ? Ce serait beaucoup plus logique de pouvoir faire $objet->delete() que Class::delete($id) non ?

    Concernant __toString
    Cette méthode est implémentée seulement pour la phase de développement et sera supprimée ensuite. La raison d'utiliser la réflexion est simple, chaque classe a des attributs différents (correspondants aux champs en base), et j'affiche simplement leurs valeurs (mêmes les attributs private, ce qui ne peux pas rester lors d'une mise en production).
    Pourquoi ne pas utiliser une hashmap portée par un seul attribut protégé ?

    Concernant vos classes, elles sont je le reconnais simples et efficaces, et respectent de plus les paradigmes objet.

    Je ne peux malheureusement pas m'en inspirer car elles donnent trop de pouvoir à l'utilisateur de la classe.
    Un exemple : dans mon cas, une instruction comme la suivante n'aurait pas de sens :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    $mail = new Mail($pdo_instance);
    car cet objet ne serait pas synchronisé avec la base (pas d'identifiant).
    En fait, j'aurais aimé rendre la méthode create statique, c'eut été plus juste de faire
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    $object = FooBar::create($data);
    que
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    $object = new FooBar;
    $object->create($data);
    mais j'ai dû me résoudre à ce compromis car ces classes ont été taillées pour PHP 5.2 et supérieur, donc pas de late static binding et donc impossible de donner à la méthode create() un comportement de fabrique sans devoir la redéfinir dans les classes filles, ce que je ne veux pas.

    Je ne peux malheureusement pas m'en inspirer car elles donnent trop de pouvoir à l'utilisateur de la classe.
    Il ne faut pas confondre l'utilisateur du SI et le programmeur qui le conçoit. Les composants doivent être souples pour permettre au programmeur d'en tirer le meilleur parti, s'il fait n'importe quoi, tant pis pour lui. De plus si tu verrouille tout, tu t'expose au danger suivant:
    "Je dois implémenter le besoin X mais il ne peut pas être exprimé avec le composant Y. Je vais alors soit faire un truc bien sale pour que ça fonctionne ou créer un composant à coté..."

    Si tu veux verrouiller une partie de ton interface, tu peux toujours le faire dans les classes filles en surchargeant les bonnes méthodes, mais les classes de base elles doivent rester génériques, sinon où est l'intérêt ?

    De plus, il ne faut pas confondre la couche métier et la couche modèle. Les classes modèle doivent rester indépendantes du fonctionnement de l'application et vice-versa, sinon on aboutit à des classes qui ont trop de responsabilités et qui, par le fait, ne sont plus du tout pertinentes.

    C'est ton idée de modéliser le pseudo-héritage du modèle avec un héritage statique en PHP qui n'est pas bonne, tu t'en sortira bien mieux avec une composition voire une façade.
    Je suis désolé mais pourriez-vous me ré-expliquer ce que vous voulez dire, je n'ai pas vraiment compris.
    Il faut que je voies concrètement tes tables. Si c'est bien ce que je pense, tu t’emmêles les pinceaux entre héritage et composition.
    La relation "X à un ou plusieurs Y" est une relation de composition. Elle s'exprime par le fait de faire porter au modèle X une ou des propriétés Y ou des méthodes permettant d'y accéder.
    La relation "X est un espèce de Y" est une relation d'héritage. Elle s'exprime par la dérivation de X en Y.

    Normalement avec un une base de données relationnelle, on a beaucoup de relations de composition par le biais des clés étrangères mais très peu de relations d'héritage qui normalement ne sont pas admises par les formes normales si on y réfléchit 5min.

  9. #9
    Membre expérimenté
    Homme Profil pro
    Étudiant
    Inscrit en
    Novembre 2009
    Messages
    141
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : Santé

    Informations forums :
    Inscription : Novembre 2009
    Messages : 141
    Par défaut
    Donc selon toi, la suppression d'un élément est indépendante de son existence ? Ce serait beaucoup plus logique de pouvoir faire $objet->delete() que Class::delete($id) non ?
    Non, j'ai justement précisé plus haut que sur ce point j'allais refaire passé delete en méthode d'instance.

    Pourquoi ne pas utiliser une hashmap portée par un seul attribut protégé ?
    C'est une solution mais cela demanderai une assez lourde modification de mon script de génération de classes, de plus __toString ne sera plus utilisée en production.

    Il ne faut pas confondre l'utilisateur du SI et le programmeur qui le conçoit. Les composants doivent être souples pour permettre au programmeur d'en tirer le meilleur parti, s'il fait n'importe quoi, tant pis pour lui. De plus si tu verrouille tout, tu t'expose au danger suivant:
    "Je dois implémenter le besoin X mais il ne peut pas être exprimé avec le composant Y. Je vais alors soit faire un truc bien sale pour que ça fonctionne ou créer un composant à coté..."

    Il ne faut pas confondre l'utilisateur du SI et le programmeur qui le conçoit. Les composants doivent être souples pour permettre au programmeur d'en tirer le meilleur parti, s'il fait n'importe quoi, tant pis pour lui. De plus si tu verrouille tout, tu t'expose au danger suivant:
    "Je dois implémenter le besoin X mais il ne peut pas être exprimé avec le composant Y. Je vais alors soit faire un truc bien sale pour que ça fonctionne ou créer un composant à coté..."

    Si tu veux verrouiller une partie de ton interface, tu peux toujours le faire dans les classes filles en surchargeant les bonnes méthodes, mais les classes de base elles doivent rester génériques, sinon où est l'intérêt ?
    Je suis tout à fait d'accord, mais la synchronisation "automatique" est un besoin, et pour cela il faut obligatoirement verrouiller un peu. Par contre, il est vrai que mes méthodes ont énormément de limites (notamment pas de clause where dans getAll, etc..), et sur ce point je vais tenter de m'inspirer de votre code pour implémenter quelque chose de fonctionnel et utilisable.

    Il faut que je voies concrètement tes tables. Si c'est bien ce que je pense, tu t’emmêles les pinceaux entre héritage et composition.
    La relation "X à un ou plusieurs Y" est une relation de composition. Elle s'exprime par le fait de faire porter au modèle X une ou des propriétés Y ou des méthodes permettant d'y accéder.
    La relation "X est un espèce de Y" est une relation d'héritage. Elle s'exprime par la dérivation de X en Y.

    Normalement avec un une base de données relationnelle, on a beaucoup de relations de composition par le biais des clés étrangères mais très peu de relations d'héritage qui normalement ne sont pas admises par les formes normales si on y réfléchit 5min.
    En fait, la base de données utilise le concept de Class Table Inherithance (utilisé notamment dans doctrine 2). L'image en pièce jointe en montre un exemple.
    Une table fille contient une clé étrangère la reliant à sa table mère.
    Images attachées Images attachées  

  10. #10
    Expert confirmé
    Avatar de Benjamin Delespierre
    Profil pro
    Développeur Web
    Inscrit en
    Février 2010
    Messages
    3 929
    Détails du profil
    Informations personnelles :
    Âge : 37
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Février 2010
    Messages : 3 929
    Par défaut
    J'ai vu la base de données, ma première réaction est

    ça ressemble à une pseudo-émulation d'une BDD objet en relationnel, ce qui va à l'encontre des pratiques en vigueur depuis environ 40 ans... C'est prendre à contre-pied l'approche ORM en réalité, je suis surpris que Doctrine 2 l'utilise (y'a peut être une raison valable). Ce que j'en dis, c'est que ce n'est pas la bonne approche pour un modèle de données, en particulier pour MySQL. J'ai remarqué que ça faisait partie du P of EAA, mais pour cette fois, je vais me ranger du coté des détracteurs et contredire l'utilité de ce pattern: quel est l'intérêt d'émuler l'objet sur le relationnel quand
    • il existe des mappeurs (Propel, Doctrine)
    • il existe des BDD objet (PostgreSQL, Oracle)


    C'est vrai, pourquoi se simplifier la vie avec `type` ENUM('FIXE','MOBILE','FAX'...) quand on peut faire décliner chaque cas en autant de tables, c'est vachement plus flexible en plus

    Enfin bref, pour moi c'est un ovni. Et je suis tout à fait d'accord avec toi, mes classes ne savent pas exploiter ce genre de mécanisme, elles ont été conçues pour le modèle relationnel.

    Si tu veux vraiment faire de l'héritage, utilise une DBMS objet.

  11. #11
    Membre expérimenté
    Homme Profil pro
    Étudiant
    Inscrit en
    Novembre 2009
    Messages
    141
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : Santé

    Informations forums :
    Inscription : Novembre 2009
    Messages : 141
    Par défaut
    Le problème est que ma base de données simule l’héritage (contrainte extérieure, je ne peux pas changer cela), donc mes classes d’abstraction ont aussi des liens d’héritage entre elles
    Malheureusement comme dit plus haut, cela provient d'une contraint extérieure, même si j'avoue que si je pouvais résoudre cette contrainte, je reviendrai avec plaisir sur ce principe d'héritage. Mais pour évoquer cette contrainte j'ouvrirai une nouvelle discussion.

    Merci quand même, je marque comme résolu car on est loin du sujet de base maintenant.

  12. #12
    Expert confirmé
    Avatar de rawsrc
    Homme Profil pro
    Dev indep
    Inscrit en
    Mars 2004
    Messages
    6 142
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Dev indep

    Informations forums :
    Inscription : Mars 2004
    Messages : 6 142
    Billets dans le blog
    12
    Par défaut
    Bonsoir,

    Voici une discussion qui a abordé le problème de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    abstract static public function ()
    Un peu de lecture ici

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. [PHP 5.3] Créer une méthode abstract static.
    Par Rakken dans le forum Langage
    Réponses: 2
    Dernier message: 01/05/2011, 15h32
  2. [PHP 5.3] Late static binding avec static function et implements
    Par rawsrc dans le forum Langage
    Réponses: 12
    Dernier message: 16/04/2010, 16h53
  3. Abstract / Static : pb conception.
    Par Bacteries dans le forum Langage
    Réponses: 24
    Dernier message: 07/09/2009, 14h37
  4. public static function = public function
    Par Jcpan dans le forum Langage
    Réponses: 4
    Dernier message: 30/03/2009, 15h28
  5. Une méthode "abstract static" ?
    Par Exsilius dans le forum C#
    Réponses: 4
    Dernier message: 01/02/2007, 14h05

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo