Zend Framework et Active Record
Posted by rami on 09/09/2006 at 12:27
Filed Under: PHP
Le framework de Zend propose un mécanisme d'accès aux données très pratique, basé sur Zend_Db_Table. Il permet de faire des CRUD rapidement en héritant de Zend_Db_Table. Cependant, il faut triturer un peu les données en entrées pour que cela se fasse naturellement en objet. Voici le résultat de mes expérimentations pour rendre la persistance des objets plus conviviales au sein du framework Zend.
1 - Use cases
Pour les impatients, commençons par regarder ce que donne le résultat. Pour cet exemple, on travaille avec les classes suivantes :

<?php
// création d'un objet
$marseille= new Equipe();
$marseille->setNom('Marseille');
// persistance en base
$marseille->save();
//jusqu'ici rien d'extraordinaire, créons quelques joueurs
$j1 = new Joueur();
$j1->setNom('Barthez')
->setPrenom('Fabien')
->setEquipe($marseille);
$j2 = new Joueur();
$j2->setNom('Ribéry')
->setPrenom('Franck')
->setEquipe($marseille);
?>
Première constation, les relations du type many-to-one (enfant / parent) sont transparentes. Cela signifie que dans le code PHP, on se s'occupe que des relations entre objets, et non pas entre table.
Voyons comment retrouver nos objets :
<?php
//récupérons l'équipe de Marseille ( identifiant 1);
$mars = new Equipe(1);
echo $mars->getNom(); // affiche Marseille
?>
Là encore, rien d'extrarodinaire. A cause d'une limitation du moteur PHP, nous ne pouvons récupérer nos objets via une méthode statique (Equipe::load(1) par exemple), nous verrons pourquoi ensuite.
Désormais, si on souhaite savoir quels joueurs jouent à Marseille, il suffit de faire :
<?php
//récupérons l'équipe de Marseille ( identifiant 1);
$mars = new Equipe(1);
//affichage de la liste des joueurs de Marseille
foreach($mars->getJoueurs() as $joueur)
{
echo $joueur->getPrenom() . ' '. $joueur->getNom() . '<br /><p>';
}
?>
Sympathique, non? Les collections sont gérées là aussi de manière transparentes pour le développeur.
2- Comment ça marche ?
2.1 - Présentation générale
Voilà comment concrètement fonctionne l'implémentation d'ActiveRecord. Voici son diagramme de classe :

Comme vous pouvez le constater, ActiveRecord hérite de Zend_Db_Table. Active Record est donc un wrapper permettant de donner un style plus objet à Zend_Db_Table.
La surcharge des méthodes insert() et update() permet de construire le tableau des valeurs à partir des valeurs des propriétés de l'objet.
La méthode abstraite mapping() permet de gérer les relations de type many-to-one et one-to-many.
2.2 Gestion des relations
Les relations doivent être définies dans la méthode mapping() des classes qui héritent d'Active Record. Pour cela, 2 méthodes sont disponibles : addManyToOne() et addOneToMany().
2.2.1 Relations many-to-one
Les relations many-to-one permettent de gérer les clés étrangères au niveau relationnel. Dans la méthode mapping, il suffit d'appeler la méthode addManyToOne() avec comme premier argument la propriété qui porte la relation ainsi que le nom de la classe de l'objet.
Exemple :
<?php
class Joueur extends ActiveRecord
{
protected $nom;
protected $prenom;
protected $equipe;
protected function mapping()
{
//définit que la propriété equipe est une relation many-to-one
// avec la classe Equipe
$this->addManyToOne('equipe', 'Equipe');
}
}
?>
2.2.2 Relations one-to-many
Les relations one-to-many permettent de gérer le sens inverse des relations many-to-one. Un enfant a un seul père (quoique ?), mais un père peut avoir plusieurs enfants.
Pour gérer ces relations, il faut là aussi les déclarer dans la méthode mapping(). Pour la classe Equipe, on obtient :
<?php
class Equipe extends ActiveRecord
{
protected $nom;
protected $joueurs;
protected function mapping()
{
//définit que la propriété joueurs est une relation one-to-many
// avec la classe Joueur, et que la colonne de la table joueur qui fait référence à
// l'équipe est "equipe"
$this->addOneToMany('joueurs', 'Joueur', 'equipe');
}
}
?>
Il ne faut pas écrire de getters pour les collections mappées en tant que one-to-many car le mode de chargement LAZY ne fonctionnerait plus.
Mode de chargement des collections
Le champ statique LOADING_MODE permet de définir comment charger les collections. Le mode LAZY (mode par défaut) fait que les collections ne sont chargées qu'à l'appel du getter. C'est pourquoi il ne faut pas définir de getters sur les collections. Active Record capture l'appel aux getters des collections et se charge de construire cette collection si ce n'était pas déjà fait.
Le mode FULL charge les collections en même temps que les propriétés simples. Ce mode n'est pas très performant, surtout si la collection est grande. Il peut être cependant très utile lorsque l'on souhaite afficher des listes car il limite le nombre de lignes de code.
3 - Conventions
Pour qu'Active Record fonctionne comme attendu, il faut néanmoins respecter certaines conditions :
- chaque table doit avoir un identifiant, même les tables d'association.
- les champs des objets héritant d'Active Record doivent avoir une visibilité protected ou public (plutôt déconseillé). En effet, il n'est pas possible aujourd'hui d'accéder à des propriétés private dans du code exécuté dans un parent. C'est une limitation qui peut être levée si les classes qui héritent d'Active Record ont des getters / setters. Pour ne pas forcer cela, seules les propriétés protected ou public sont acceptées actuellement.
- il ne faut pas faire de getter sur les collections dans les classes héritant d'Active Record. Comme évoqué plus haut, Active Record "capture" les appels aux getters des collections pour pouvoir les charger (en mode LAZY).
Téléchargez la source : ECV-ActiveRecord.tar
edit : je viens de voir qu'un proposal a été fait pour Zend_Db_Model regroupant ce que fait la classe ActiveRecord, je vais certainement proposer mon implémentation ;), qui sait ?



Edouard Says
Sunday, September 10. 2006 at 20:18 (Link) (Reply)
En tout cas, c'est clair, propre... ça m'a l'air très bien pour des diagrammes de classes raisonnables.