Dans ce nouvelle article, j’ai décidé d’expliquer comment travailler avec les relations M:M de doctrine. La définition de celle-ci dans le fichier schema.yml est assez spéciale. Il faut bien comprendre son fonctionnement pour pouvoir optimiser vos requêtes. Pour cela, j’ai monté un petit exemple concret.
Nous allons commencer par l’écriture de notre modèle au format yml:
--- User: tableName: user actAs: Timestampable: ~ columns: id: type: integer(4) primary: true autoincrement: true unsigned: true lastname: type: string(80) notnull: true firstname: type: string(80) notnull: true relations: MCategories: class: Category local: user_id foreign: category_id refClass: UserCategory foreignAlias: Users UserCategory: tableName: user_category columns: user_id: type: integer(4) unsigned: true primary: true category_id: type: integer(4) unsigned: true primary: true relations: User: onDelete: CASCADE Category: onDelete: CASCADE Category: tableName: category actAs: Timestampable: ~ columns: id: type: integer(4) primary: true autoincrement: true unsigned: true name: type: string(80) notnull: true relations: MUsers: class: User local: category_id foreign: user_id refClass: UserCategory foreignAlias: Categories
Comme vous pouvez le voir sur la définition de nos relations, nous n’écrivons pas sur la table de liaison mais sur la table principale (ici User et Category). Nous commençons par leur donner un nom. Ensuite, nous allons insérer toutes les options:
- Class: correspond au modèle de la liaison finale (Category)
- local et foreign: correspondent aux champs définis dans votre table de liaison.
- refClass: Nom du modèle de liaison (UserCategory)
- foreignAlias: Le nom de l’alias qui sera donné à notre table finale (Category)
Un petit fichier de fixtures pour avoir des données de test:
--- User: user_1: firstname: Adrien lastname: Loutier MCategories: [cat_1, cat_2, cat_3] user_2: firstname: Simon lastname: Jacquemoud MCategories: [cat_2, cat_4] user_3: firstname: Raphaëlle lastname: Tabouret MCategories: [cat_1, cat_2, cat_3, cat_4] user_4: firstname: Justine lastname: Simonin MCategories: [cat_2, cat_3, cat_4, cat_5] Category: cat_1: name: Niveau 1 cat_2: name: Niveau 2 cat_3: name: Niveau 3 cat_4: name: Niveau 4 cat_5: name: Niveau 5
J’ai ensuite généré un module « user » pour pouvoir y insérer mon code. Je passe sur les explications de cette génération.
Nous avons maintenant deux solutions. Soit nous laissons travailler doctrine sans optimisation, soit nous définissons notre requête pour avoir un minimum d’appels sur la base de données.
Dans le premier exemple, nous allons laisser faire doctrine. Nous allons simplement appeler nos users dans le fichier actions.class.php de notre module:
class userActions extends sfActions { public function executeIndex(sfWebRequest $request) { $this->users = Doctrine_Core::getTable('User') ->createQuery() ->execute(); } }
Affichage de nos données dans notre template. Ici indexSuccess.php
<h1>Liste des utilisateur</h1> <?php foreach ($users as $user): ?> <p> <?php echo $user->firstname; ?> <?php echo $user->lastname; ?> <ul> <?php foreach($user->getCategories() as $categorie): ?> <li class="list"><?php echo $categorie->name; ?></li> <?php endforeach; ?> </ul> </p> <?php endforeach; ?>
Dans le code ci-dessus, j’utilise le get{Categories} (foreignAlias de la relation sur la table User) pour récupérer mes catégories. Vous n’avez pas besoin d’appeler les enregistrements de la table de liaison.
Nous avons le résultat suivant en html:
Nous allons maintenant visualiser le nombre de requêtes effectuées par doctrine dans notre barre de debug (Cliquez sur l’image pour visualiser):
Nous constatons que doctrine exécute 5 requêtes (une par personne pour récupérer les catégories).
Nous allons maintenant optimiser notre récupération de données en écrivant une requête DQL dans le modèle User. Le fichier se trouve dans lib/model/doctrine/UserTable.class.php:
class UserTable extends Doctrine_Table { public function getActiveCategories() { return $this->createQuery('u') ->leftJoin('u.Categories g') ->execute(); } }
Dans la requête ci-dessus, nous utilisons également le nom de la foreignAlias pour écrire notre jointure.
Nous allons changer notre précédente requête dans l’action index par celle-ci:
class userActions extends sfActions { public function executeIndex(sfWebRequest $request) { $this->users = Doctrine_Core::getTable('User')->getActiveCategories(); } }
Nous retournons dans notre barre de debug pour visualiser les requêtes (Cliquez sur l’image pour visualiser):
Comme vous pouvez le constater ci-dessus, avec l’optimisation du DQL, Doctrine exécute une seule requête.
Voilà. J’espère que ce petit exemple pourra vous servir pour vos prochains développements. N’hésitez pas à me laisser vos commentaires.