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.