3.7.6.5 hasAndBelongsToMany (HABTM)
Impeccable. A ce niveau, vous pouvez déjà vous considérer comme un professionnel des associations de modèles CakePHP. Vous vous êtes déjà assez compétents dans les 3 typs d'associations afin de pouvoir effectuer la plus grande partie des relations entre les objets.
Abordons maintenant le dernier type de relation : hasAndBelongsToMany (a et appartient à plusieurs), ou HABTM. Cette association est utilisée lorsque vous avez deux modèles qui ont besoin d'être reliés, de manière répétée, plusieurs fois, de plusieurs façons différentes.
La principale différence entre les relations hasMany et HABTM est que le lien entre les modèles n'est pas exclusif dans le cadre d'une relation HABTM. Par exemple, relions notre modèle Recette avec un modèle Tag en utilisant HABTM. Le fait d'attacher le tag "Italien" à la recette de Gnocchi de ma grand-mère ne "consomme" pas le tag. Je peux aussi taguer mes Spaghettis Caramélisées au miel comme "Italien" si je le souhaite.
Les liens entre des objets liés par une association hasMany sont exclusif. Si mon Utilisateur "hasMany" Commentaires, un commentaire ne sera lié qu'à un utilisateur spécifique. Il ne sera plus disponible pour d'autres.
Continuons. Nous aurons besoin de mettre en place une table supplémentaire dans la base de données qui contiendra les associations HABTM. Le nom de cette nouvelle table de jointure doit inclure les noms des deux modèles concernés, dans l'ordre alphabétique. La table doit contenir au minimum deux champs, chacune des clés étrangères (qui devraient être des entiers) pointant sur les deux clés primaires des modèles concernés.
| Relation | Schéma |
|---|---|
| Recette HABTM Tag | recettes_tags.recette_id, recettes_tags.tag_id |
| Cake HABTM Fan | cakes_fans.cake_id, cakes_fans.fan_id |
| Foo HABTM Bar | bars_foos.foo_id, bars_foos.bar_id |
Le nom des tables est par convention dans l'ordre alphabétique.
Une fois que cette nouvelle table a été créée, on peut définir l'association HABTM dans les fichiers de modèle. Cette fois ci, nous allons directement voir la syntaxe tabulaire :
<?php
class Recette extends AppModel {
var $name = 'Recette';
var $hasAndBelongsToMany = array(
'Tag' =>
array('className' => 'Tag',
'joinTable' => 'recettes_tags',
'foreignKey' => 'recette_id',
'associationForeignKey' => 'tag_id',
'conditions' => '',
'order' => '',
'limit' => '',
'unique' => true,
'finderQuery' => '',
'deleteQuery' => '',
'insertQuery' => ''
)
);
}
?>
<?phpclass Recette extends AppModel {var $name = 'Recette';var $hasAndBelongsToMany = array('Tag' =>array('className' => 'Tag','joinTable' => 'recettes_tags','foreignKey' => 'recette_id','associationForeignKey' => 'tag_id','conditions' => '','order' => '','limit' => '','unique' => true,'finderQuery' => '','deleteQuery' => '','insertQuery' => ''));}?>
Les clés possibles pour un tableau définissant une association HABTM sont :
- className : le nom de classe du modèle associé au modèle courant. Si l'on souhaite définir la relation 'Utilisateur HABTM Commentaire', la valeur associée à la clef 'className' devra être 'Commentaire'.
- joinTable : Le nom de la table de jointure utilisée dans cette association (si la table ne colle pas à la convention de nommage des tables de jointure HABTM).
- foreignKey : le nom de la clef étrangère que l'on trouve dans l'autre modèle. Ceci sera particulièrement pratique si vous avez besoin de définir des relations HABTM multiples. La valeur par défaut de cette clef est le nom de l'autre modèle (avec des underscores) suffixé avec '_id'.
- associationForeignKey : le nom de la clef étrangère que l'on trouve dans le modèle actuel. Ceci sera particulièrement pratique si vous avez besoin de définir des relations HABTM multiples. La valeur par défaut de cette clef est le nom de l'autre modèle (avec des underscores) suffixé avec '_id'.
- conditions : un fragment de code SQL utilisé pour filtrer les enregistrements du modèle relié. C'est une bonne pratique que d'utiliser les noms des modèles dans ces portions de code : "Commentaire.statut = 1" sera toujours mieux qu'un simple "statut = 1".
- fields : une liste des champs a récupérer lorsque les données du modèle associé sont parcourues. Par défaut, cela retourne tous les champs.
- order : un fragment de code SQL qui définit l'ordre des entrées associées.
- limit : le nombre maximum d'entrées associées qui seront retournées.
- offset : le nombre d'entrées associées à sauter (les conditions et l'ordre de classement étant donnés) avant de récupérer de nouveaux enregistrements et de les associer.
- finderQuery, deleteQuery, insertQuery : une requête SQL complète que CakePHP peut utiliser pour récupérer, supprimer, ou créer les nouveaux enregistrements des modèles associés. Ceci peut être utilisé dans des situations qui nécessitent des résultats très personnalisés.
Une fois que cette association a été définie, les opérations de recherche sur le modèle Recette récupèreront également les enregistrements Tag associés si il en existe :
// Exemple de résultat d'un appel à $this->Recette->find().
Array
(
[Recette] => Array
(
[id] => 2745
[nom] => Bombes de sucres au chocolat glacé
[created] => 2007-05-01 10:31:01
[utilisateur_id] => 2346
)
[Tag] => Array
(
[0] => Array
(
[id] => 123
[nom] => Petit déjeuner
)
[1] => Array
(
[id] => 123
[nom] => Dessert
)
[2] => Array
(
[id] => 123
[nom] => Déprime sentimentale
)
)
)
N'oubliez pas de définir une association HABTM dans le modèle Tag si vous souhaitez retrouver les données de Recette lorsque vous manipulez le modèle Tag.
Il est également possible d'exécuter des requêtes de recherche personnalisées basées sur des relations HABTM. Regardez les exemples suivants :
En supposant que nous avons la même structure que dans les exemples ci-dessus (Recette HABTM Tag), disons que nous voulons récupérer toutes les Recettes avec le Tag "Dessert". Une solution rapide (mais incorrecte) pour faire ceci serait d'utiliser une condition complexe sur le modèle Recette :
$this->Recette->find('all', array('conditions'=>array('Tag.nom'=>'Dessert')));
$this->Recette->find('all', array('conditions'=>array('Tag.nom'=>'Dessert')));
// Données retournées
Array
(
0 => Array
{
[Recette] => Array
(
[id] => 2745
[nom] => Bombes de sucres au chocolat glacé
[created] => 2007-05-01 10:31:01
[utilisateur_id] => 2346
)
[Tag] => Array
(
[0] => Array
(
[id] => 123
[nom] => Dessert
)
)
)
1 => Array
{
[Recette] => Array
(
[id] => 2745
[nom] => Gâteau au crabe
[created] => 2008-05-01 10:31:01
[utilisateur_id] => 2349
)
[Tag] => Array
(
}
}
}
Notez que cet exemple retourne TOUTES les recettes, mais seulement les tags "Dessert". Pour parvenir proprement à notre but, nous n'avons qu'à effectuer la même requête mais sur l'association HABTM.
$this->Recette->RecettesTag->find('all', array('conditions'=>array('Tag.nom'=>'Dessert')));
$this->Recette->RecettesTag->find('all', array('conditions'=>array('Tag.nom'=>'Dessert')));
// Données retournées
Array
(
0 => Array
{
[Recette] => Array
(
[id] => 2745
[nom] => Bombes de sucres au chocolat glacé
[created] => 2007-05-01 10:31:01
[utilisateur_id] => 2346
)
[Tag] => Array
(
[0] => Array
(
[id] => 123
[nom] => Dessert
)
)
)
}
