Object Relational Mapping en PHP

foxdiecs
Invité n'ayant pas de compte PHPfrance

25 juil. 2009, 11:28

salut!

Il existe différents ORM en PHP ( Doctrine, PHPMyObject, ... ) mais après jetté un oeil sur leur façon de fonctionner je me rends compte que ceux-ci violent les principes fondamentaux de la programmation orientée objet.

En effet, lorsqu'il s'agit d'initialiser les attributs d'une instance d'une classe, les classes de mapping accèdent directement aux membres de la classe à mapper qui doivent être déclarés publiques ou bien énumérés dans un tableau qui lui sera déclaré publique.

Au contraire, en java, Hibernate à la possibilité d'accéder directement aux membres déclarés private ou protected d'une classe lors du mapping, ce qui à ma connaissance, n'existe pas en PHP.

Je suis à la recherche d'un pattern qui permettrait à une classe de ne donner accès à ses attributs private ou protected qu'à une classe de mapping donnée. J'ai imaginé le code suivant :

Code : Tout sélectionner

interface MappedObject{ //retourne la map qui décrit les propriétés de l'objet et leur emplacement dans la bdd public function Map getMap(); //donner accès aux attributs privés à la classe de mapping public function setDataMemberAccess( Mapper &$mapper, $dataMember ); }

Code : Tout sélectionner

class User implements MappedObject{ private $id; private $login; public function __construct( $id ){ $this->id = intval( $id ); } public function getMap(à{ return new UserMap(); } public function setDataMemberAccess( Mapper &$mapper, $dataMember ){ //l'attribut privé sera récupéré par référence par le mappeur, //je n'ai pas testé si cela était possible $mapper->setCurrentDataMember( $this->$dataMember ); } }

Code : Tout sélectionner

class Mapper{ //récupérer une référence vers l'attribut privé de la classe à mapper //aucune idée si cela fonctionne public function setCurrentDataMember( &$dataMember ){ $this->currentDataMember = $dataMember; } public function map( $instance ){ $map = $instance->getMap(); //pour chaque attributs définit dans la map on récupère la valeur dans la bdd //et on inititalise l'attribut de la classe foreach( ....as $dataMember => $value ){ $instance->setDataMemberAccess( $this, $dataMember ); //récupérer l'accès $this->currentDataMember = $value; //initialiser } } }
finalement le mappage s'effectuerait comme ceci :

Code : Tout sélectionner

$user = new User( 1 ); $mapper = new Mapper(); $mapper->map( $user );
Bref, la méthode est n'est peut être pas très élégante. Si quelqu'un a une meilleur approche je suis prenneur :lol:

ViPHP
fab
ViPHP | 2657 Messages

25 juil. 2009, 16:01

Seul l'intelligent a le pouvoir de se trouver con
try { work(); } catch(FlemmeExeption $e) { sleep(84600); }

foxdiecs
Invité n'ayant pas de compte PHPfrance

25 juil. 2009, 16:57

merci mais je me suis peut être mal exprimé :?
En fait je ne souhaite pas qu'un développeur ou qu'une autre classe puisse accéder aux membres privés des objets afin d'en modifier le contenu. Seule la classe de mapping doit y être autorisée. C'est cette restriction que j'essaie de modéliser.

Eléphant du PHP | 217 Messages

25 juil. 2009, 20:45

Bonjour,
vous pouvez vous orienter du coté du pattern Visiteur peut être ?

ViPHP
ViPHP | 4674 Messages

26 juil. 2009, 20:59

Hey :-),

Je ne sais pas comment Hibernate fonctionne mais voici ce qu'il est possible de faire.
Tout d'abord, on ne peut pas définir des valeurs définies comme privées en dehors de la classe. Donc même avec un héritage, c'est impossible. Tu peux essayer toutes les circonvolutions possibles, tu ne pourras pas.
Par contre, pour ce qui est des valeurs protégées, c'est tout à fait possible. Un bête héritage, __get(), et le tour est joué. Un truc du genre :
class Map {

    public function query ( ) {

        // quand on lance la requête, cette méthode est exécutée.
        foreach($fields as $i => $field)
            $field = ...;

        // on peut récupérer les champs (attributs) des enfants (ici dans $fields) depuis la classe mère.
        // on peut également attribuer une valeur, même si c'est protégé.
    }
}

class Table extends Map {

    public $id;
    protected $name;
}
Il existe plusieurs moyens pour récupérer les attributs des enfants. C'est un peu galère, mais c'est possible. Tu as beaucoup de façon de faire.

Pour donner l'exemple de mon framework, Hoa_Database permet de faire ce genre de chose (la documentation est en cours). Mais on ne peut pas accéder aux attributs privés, car ils sont ... privés !
L'astuce pour les attributs protégés, c'est de les atteindre depuis la classe Mère (c'est l'inverse de ce qu'on fait d'habitude). C'est la seule façon de le faire.

Sinon Mojorisin, le modèle de conception visiteur n'a rien à voir avec ce genre de problème ;-).
« Un handicap est le résultat d'une rencontre entre une déficience ou différence et une incapacité de la société à répondre à celle-ci. »

Hoa : http://hoa-project.net (sur @hoaproject).

foxdiecs
Invité n'ayant pas de compte PHPfrance

26 juil. 2009, 22:49

lol, je ne savais pas qu'il était possible d'accéder aux membres protégés d'une classe en passant par sa mère ( sous-entendu que la mère elle ne possède pas ces membres ).

C'est une bonne astuce, cela me force à faire un héritage que je souhaitais éviter au début mais tant pis.

Pour ce qui est pattern Visitor ( que je ne connaissais pas avant ), c'est assez proche de ce que j'avais ebauché : la classe User était visitée par la classe Mapper.
J'ai testé une autre chose intéréssante et qui fonctionne à merveille :

- on demande à la classe Mapper de mapper une instance de User
- la classe Mapper connait la structure de la classe User ( comme dans tous les ORM, soit grâce à un fichier XML, ou autre,... )
- pour chaque attribut à initialiser, la classe Mapper visite la classe User ( exactement comme je l'ai écrit tout au début ) en demandant l'accès au member à l'aide de MappedObject::setDataMemberAccess()
- à ce moment-là la classe User appele la méthode Mapper::setCurentDataMember() en passant son attribut privés ou protégé à mapper par référence
Donc ca y est, la classe Mapper peut manipuler les membres privés des objets à mapper.

Merci pour votre aide :D [/b]

ViPHP
ViPHP | 4674 Messages

27 juil. 2009, 09:55

Mais arrêtez de parler de visiteur, ça n'a rien à voir avec le sujet … On ne visite pas une classe mais un arbre, c'est tout à fait différent. Ce n'est pas le bon terme. Tu peux dire que tu inspectes une classe à la limite.

Tu arrives à casser la visibilité en utilisant les références ? J'aimerais bien voir ça. Un exemple ?
« Un handicap est le résultat d'une rencontre entre une déficience ou différence et une incapacité de la société à répondre à celle-ci. »

Hoa : http://hoa-project.net (sur @hoaproject).

foxdiecs
Invité n'ayant pas de compte PHPfrance

27 juil. 2009, 19:35

soit pour le Visiteur, je n'insiste pas, je viens de découvrir le pattern, je relirai ça tranquilou.

Pour ce qui est de la manipulation des membres privés passés par références voila un petit exemple de ce qu'autorise le langage aujourd'hui :

//-------------------------------------------------------------------------------------------------

abstract class MappedObject{

   //méthode qui permet de donner accès à un membre privé à une instance de Mapper
   public function setDataMemberAccess( Mapper &$mapper, $dataMember ){

      $mapper->setCurrentDataMember( $this->$dataMember );

   }

}

//-------------------------------------------------------------------------------------------------

class User extends MappedObject{ //la classe User peut donner accès à ses membres privés à une instance de Mapper car elle dérive de MappedObject

   private $id;
   private $login;

   public function __construct( $id ){ 

      $this->id = intval( $id ); 

   }

}

//-------------------------------------------------------------------------------------------------

class Mapper{

   private $currentDataMember; //utilisé pour récupéré les accès vers les membres privés objets à mapper

   public function map( MappedObject &$instance ){ //initialiser les membres privés d'un MappedObject

     //je ne détaille pas comment, mais on supposera qu'à ce stade le mapper connaît la structure
     //de l'objet User et l'emplacement des infos à récupérer dans la base de données
     
       foreach( ....as dataMember => $value ){ //pour chaque attribut à initialiser

         //demander à l'objet User un accès privilégié au membre privé concerné
         //l'objet User appelera la méthode Mapper::setCurrentDataMember() pour donner une référence vers son membre privé
         $instance->setDataMemberAccess( $this, $dataMember ); //récupérer l'accès

         //à ce moment là, $this->currentDataMember est une référence vers l'attribut privé de l'objet User. On peut donc le manipuler
         $this->currentDataMember = $value;

      }

   }

   public function setCurrentDataMember( &$dataMember ){

      $this->currentDataMember =& $dataMember;

   }

}

//-------------------------------------------------------------------------------------------------

//pour éxécuter tout ça :

$user = new User( 1 );
$mapper = new Mapper();
$mapper->map( $user );

//-------------------------------------------------------------------------------------------------
[/php]

ViPHP
ViPHP | 4674 Messages

28 juil. 2009, 11:06

Et comment scannes-tu les membres privés d'une classe ?
« Un handicap est le résultat d'une rencontre entre une déficience ou différence et une incapacité de la société à répondre à celle-ci. »

Hoa : http://hoa-project.net (sur @hoaproject).

Invité
Invité n'ayant pas de compte PHPfrance

28 juil. 2009, 13:42

je n'ai pas mis tout le code dans l'exemple mais la classe MappedObject possède une méthode abstraite qui oblige ses filles à retourne une instance de Map. Il s'agit d'une classe qui décrit la structure l'objet, la liste des membres/champs dans la base est chargée depuis un fichier XML ( comme dans beaucoup d'ORM ).

Ca donne ça
abstract class MappedObjet{

   /**
    * @return map
   **/
   public abstract function getMap();

}
dans la classe User, la méthode devient :
class User extends MappedObjet{

   public function getMap(){ return new Map( "chemin_de_config/user.xml" ); }

}
La classe, chez moi, hérite de DOMDocument :
class Map extends DomDocument{

   public function __contruct( $filename ){

      parent::loadXML( $filename );

   }

   //puis une méthode qui retourne un itérateur vers la liste des membres/champ dans la base

}
Le fichier XML est un truc du genre :

Code : Tout sélectionner

<?xml version="1.0" encoding="UTF-8"?> <class name="User"> <tables> <table name="user"> <columns> <column name="iduser" ... /> </colums> </table> </tables> <relationships> ... pour éventuellements mapper des objets membres ou des collections d'objets </relationships> </class>

ViPHP
ViPHP | 4674 Messages

28 juil. 2009, 13:52

Ah ok, tu le récupères depuis l'XML. Bah tu triches aussi. Avec l'intropsection (voir http://php.net/reflection) tu peux le savoir. C'est plus propre, car ça différencie bien tes couches. Tu peux te passer de ton XML et ton système fonctionne encore.
« Un handicap est le résultat d'une rencontre entre une déficience ou différence et une incapacité de la société à répondre à celle-ci. »

Hoa : http://hoa-project.net (sur @hoaproject).

foxdiecs
Invité n'ayant pas de compte PHPfrance

28 juil. 2009, 19:54

ah nan, ce qui m'importe c'est de conserver un maximum de souplesse au sein de mon application.
En effet, certains membres ne doivent pas être initialisés betement à partir de données récupérées dans la base, d'autres sont des collection d'objets, etc...
Les fichers XML me permettent d'autre part de définir les relations existant entre les différentes tables de la base.
Par contre l'introspection me paraît très intéréssante, pour d'autres choses, je vais y regarder de plus près, merci pour l'info. :lol: