3.7.6 Associations: relier les modèles entre eux

Une des caractéristiques les plus puissantes de CakePHP est sa capacité d'établir les liens nécessaires entre les modèles d'après les informations fournies. Dans CakePHP, les liens entre modèles sont gérés par des associations.

Definir les relations entre différents objets à l'intérieur de votre application devrait être une tâche naturelle. Par exemple : dans une base de données de recettes, une recette peut avoir plusieurs versions, chaque version n'a qu'un seul auteur et les auteurs peuvent avoir plusieurs recettes. Le fait de définir le fonctionnement de ces relations vous permet d'accéder à vos données de manière intuitive et puissante.

Le but de cette section est de vous montrer comment concevoir, définir et utiliser les associations entre les modèles au sein de CakePHP.

Bien que les données peuvent être issues d'une grande variété de sources, la forme de stockage la plus répandue dans les applications web est la base de données relationnelle. La plupart de ce qui est couvert par cette section le sera dans ce contexte.

3.7.6.1 Types de relations

Les quatre types d'associations dans CakePHP sont : hasOne (a un seul), hasMany (a plusieurs), belongsTo (appartient à), et hasAndBelongsToMany (HABTM) (appartient à et est composé de plusieurs).

Relation Type d'association Exemple
un vers un hasOne Un utilisateur a un profil.
un vers plusieurs hasMany Les utilisateurs dans un système peuvent avoir plusieurs recettes.
appartient à belongsTo Une recette appartient à un utilisateur.
plusieurs vers plusieurs hasAndBelongsToMany Les recettes ont, et appartiennent à plusieurs tags.

Les associations se définissent en créant une variable de classe nommée comme l'association que vous souhaitez définir. La variable de classe peut quelquefois se limiter à une chaîne de caractère, mais peut également être un tableau multi-dimensionnel utilisé pour définir les spécificité de l'association.

<?php

class Utilisateur extends AppModel {
    var $name = 'Utilisateur';
    var $hasOne = 'Profil';
    var $hasMany = array(
        'Recette' => array(
            'className'  => 'Recette',
            'conditions' => 'Recette.acceptee = 1',
            'order'      => 'Recette.created DESC'
        )
    );
}

?>
  1. <?php
  2. class Utilisateur extends AppModel {
  3. var $name = 'Utilisateur';
  4. var $hasOne = 'Profil';
  5. var $hasMany = array(
  6. 'Recette' => array(
  7. 'className' => 'Recette',
  8. 'conditions' => 'Recette.acceptee = 1',
  9. 'order' => 'Recette.created DESC'
  10. )
  11. );
  12. }
  13. ?>

Dans l'exemple ci-dessus, la première instance du mot 'Recette' est ce que l'on appelle un 'Alias'. C'est un identifiant pour la relation et cela peut être ce que vous souhaitez. En règle générale, on choisit le même nom que la classe qu'il référence. Toutefois, les alias doivent être uniques à la fois dans un modèle et de part et d'autre d'une relation belongsTo/hasMany ou belongsTo/hasOne. Choisir des noms non-uniques pour des alias de modèle peut engendrer des comportements non souhaités.

3.7.6.2 hasOne

Mettons en place un modèle Utilisateur avec une relation de type hasOne vers un modèle Profil.

Tout d'abord, les tables de votre base de données doivent être saisies correctement. Pour qu'une relation de type hasOne fonctionne, une table doit contenir une clé étrangère qui pointe vers un enregistrement de l'autre. Dans notre cas la table profils contiendra un champ nommé utilisateur_id. Le motif de base est :

hasOne: *l'autre* modèle contient la clé étrangère.
Pomme hasOne Banane      => bananes.pomme_id
Utilisateur hasOne Profil      => profils.utilisateur_id
Docteur hasOne Maitre     => maitres.docteur_id
  1. hasOne: *lautre* modèle contient la clé étrangère.
  2. Pomme hasOne Banane => bananes.pomme_id
  3. Utilisateur hasOne Profil => profils.utilisateur_id
  4. Docteur hasOne Maitre => maitres.docteur_id

Le fichier du modèle Utilisateur sera sauvegardé dans /app/models/utilisateur.php. Pour définir l'association 'Utilisateur hasOne Profil', ajoutez la propriété $hasOne à la classe du modèle. Pensez à avoir un modèle Profil dans /app/models/profil.php, sans quoi l'association ne fonctionnera pas.

<?php

class Utilisateur extends AppModel {
    var $name = 'Utilisateur';                
    var $hasOne = 'Profil';   
}
?>
  1. <?php
  2. class Utilisateur extends AppModel {
  3. var $name = 'Utilisateur';
  4. var $hasOne = 'Profil';
  5. }
  6. ?>

Il y a deux manières de décrire cette relation dans vos fichiers de modèle. La méthode la plus simple est de fixer comme valeur à l'attribut $hasOne une chaîne de caractères contenant le nom de la classe associée au modèle, comme nous l'avons fait ci-dessus.

Si vous avez besoin de plus de contrôle, vous pouvez définir vos associations en utilisant un tableau. Par exemple, vous pourriez vouloir classer les colonnes par date décroissante, ou limiter l'association afin qu'elle n'inclue que certains enregistrements.

<?php

class Utilisateur extends AppModel {
    var $name = 'Utilisateur';          
    var $hasOne = array(
        'Profil' => array(
            'className'    => 'Profil',
            'conditions'   => 'Profil.publie = 1',
            'dependent'    => true
        )
    );    
}
?>
  1. <?php
  2. class Utilisateur extends AppModel {
  3. var $name = 'Utilisateur';
  4. var $hasOne = array(
  5. 'Profil' => array(
  6. 'className' => 'Profil',
  7. 'conditions' => 'Profil.publie = 1',
  8. 'dependent' => true
  9. )
  10. );
  11. }
  12. ?>

Les clés possibles pour un tableau décrivant une association $hasOne sont :

  • className : le nom de la classe du modèle que l'on souhaite associer au modèle actuel. Si l'on souhaite définir la relation 'Utilisateur a un Profil', la valeur associée à la clé 'className' devra être 'Profil'.
  • foreignKey : le nom de la clé etrangère que l'on trouve dans l'autre modèle. Ceci sera particulièrement pratique si vous avez besoin de définir des relations hasOne multiples. La valeur par défaut de cette clé est le nom du modèle actuel (avec des underscores) suffixé avec '_id'. Dans l'exemple ci-dessus la valeur par défaut aurait été 'utilisateur_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 : "Profil.approuve = 1" sera toujours mieux qu'un simple "approuve = 1".
  • fields: une liste des champs à récupérer lorsque les données du modèle associé sont parcourues. Par défaut, cela retourne tous les champs.
  • dependent: lorsque la valeur de la clé 'dependent' est true et que la méthode delete() du modèle est appelée avec le paramètre 'cascade' valant true également, les enregistrements des modèles associés sont supprimés.

Une fois que cette association aura été définie, les opérations de recherche sur le modèle Utilisateur récupèreront également les enregistrements Profil liés si il en existe :

//Exemple de résultats d'un appel à $this->Utilisateur->find().

Array
(
    [Utilisateur] => Array
        (
            [id] => 121
            [nom] => Gwoo le Kungwoo
            [created] => 2007-05-01 10:31:01
        )
    [Profil] => Array
        (
            [id] => 12
            [utilisateur_id] => 121
            [competences] => Cuisiner des gâteaux
            [created] => 2007-05-01 10:31:01
        )
)
  1. //Exemple de résultats d'un appel à $this->Utilisateur->find().
  2. Array
  3. (
  4. [Utilisateur] => Array
  5. (
  6. [id] => 121
  7. [nom] => Gwoo le Kungwoo
  8. [created] => 2007-05-01 10:31:01
  9. )
  10. [Profil] => Array
  11. (
  12. [id] => 12
  13. [utilisateur_id] => 121
  14. [competences] => Cuisiner des gâteaux
  15. [created] => 2007-05-01 10:31:01
  16. )
  17. )

3.7.6.3 belongsTo

Maintenant que nous avons accès aux données du Profil depuis le modèle Utilisateur, définissons une association belongsTo (appartient a) dans le modèle Profil afin de pouvoir accéder aux données Utilisateur liées. L'association belongsTo est un complément naturel aux associations hasOne et hasMany : elle permet de voir les données dans le sens inverse.

Lorsque vous définissez les clés de votre base de données pour une relation de type belongsTo, suivez cette convention :

belongsTo : le modèle *actuel* contient la clef étrangère.

Banane belongsTo Pomme      => bananes.pomme_id
Profil belongsTo Utilisateur      => profils.utilisateur_id
Maitre belongsTo Docteur     => maitres.docteur_id

Si une table contient une clef étrangère, elle appartient a ...
  1. belongsTo : le modèle *actuel* contient la clef étrangère.
  2. Banane belongsTo Pomme => bananes.pomme_id
  3. Profil belongsTo Utilisateur => profils.utilisateur_id
  4. Maitre belongsTo Docteur => maitres.docteur_id
  5. Si une table contient une clef étrangère, elle appartient a ...

On peut définir l'association belongsTo dans notre modèle Profil (/app/models/profil.php) en utilisant une chaîne de caractère de cette manière :

<?php

class Profil extends AppModel {
    var $name = 'Profil';                
    var $belongsTo = 'Utilisateur';   
}
?>
  1. <?php
  2. class Profil extends AppModel {
  3. var $name = 'Profil';
  4. var $belongsTo = 'Utilisateur';
  5. }
  6. ?>

Nous pouvons également définir une relation plus spécifique en utilisant un tableau :

<?php

class Profil extends AppModel {
    var $name = 'Profil';                
    var $belongsTo = array(
        'Utilisateur' => array(
            'className'    => 'Utilisateur',
            'foreignKey'    => 'utilisateur_id'
        )
    );  
}
?>
  1. <?php
  2. class Profil extends AppModel {
  3. var $name = 'Profil';
  4. var $belongsTo = array(
  5. 'Utilisateur' => array(
  6. 'className' => 'Utilisateur',
  7. 'foreignKey' => 'utilisateur_id'
  8. )
  9. );
  10. }
  11. ?>

Les clés possibles pour le tableau d'association belongsTo sont :

  • className : le nom de classe du modèle associé au modèle courant. Si l'on souhaite définir la relation 'Profil appartient à Utilisateur', la valeur associée à la clef 'className' devra être 'Utilisateur'.
  • foreignKey : 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 belongsTo 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 : "Utilisateur.actif = 1" sera toujours mieux qu'un simple "actif = 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.
  • counterCache : (booléen) si il vaut true le Modèle associé incrémentera ou décrémentera automatiquement le champ "[nom_du_modele_au_singulier]_count" dans la table étrangère dès qu'un appel a save() ou delete() sera effectué. La valeur dans le champ du compteur représente le nombre d'enregistrements liés.

Une fois que cette association aura été définie, les opérations de recherche sur le modèle Profil récupèreront également les enregistrements Utilisateur liés si il en existe :

//Exemple de résultats d'un appel a $this->Profil->find().

Array
(
	[Profil] => Array
        (
            [id] => 12
            [utilisateur_id] => 121
            [competences] => Cuisiner des gâteaux
            [created] => 2007-05-01 10:31:01
        )
    [Utilisateur] => Array
        (
            [id] => 121
            [nom] => Gwoo le Kungwoo
            [created] => 2007-05-01 10:31:01
        )
)
  1. //Exemple de résultats d'un appel a $this->Profil->find().
  2. Array
  3. (
  4. [Profil] => Array
  5. (
  6. [id] => 12
  7. [utilisateur_id] => 121
  8. [competences] => Cuisiner des gâteaux
  9. [created] => 2007-05-01 10:31:01
  10. )
  11. [Utilisateur] => Array
  12. (
  13. [id] => 121
  14. [nom] => Gwoo le Kungwoo
  15. [created] => 2007-05-01 10:31:01
  16. )
  17. )

3.7.6.4 hasMany

Prochaine étape : définir une association "Utilisateur hasMany Commentaire". Une association hasMany nous permettra de récupérer les commentaires d'un utilisateur lors de la récupération d'un enregistrement Utilisateur.

Lorsque vous définissez les clés de votre base de données pour une relation de type hasMany, suivez cette convention :

hasMany: l'*autre* modèle contient la clé étrangère.

Utilisateur hasMany Commentaire     => Commentaire.user_id
Cake hasMany Vertue                 => Vertue.cake_id
Produit hasMany Option              => Option.produit_id
  1. hasMany: l*autre* modèle contient la clé étrangère.
  2. Utilisateur hasMany Commentaire => Commentaire.user_id
  3. Cake hasMany Vertue => Vertue.cake_id
  4. Produit hasMany Option => Option.produit_id

On peut définir l'association hasMany dans notre modèle Utilisateur (/app/models/utilisateur.php) en utilisant une chaîne de caractère de cette manière :

<?php

class Utilisateur extends AppModel {
    var $name = 'Utilisateur';                
    var $hasMany = 'Commentaire';   
}
?>
  1. <?php
  2. class Utilisateur extends AppModel {
  3. var $name = 'Utilisateur';
  4. var $hasMany = 'Commentaire';
  5. }
  6. ?>

Nous pouvons également définir une relation plus spécifique en utilisant un tableau :

<?php

class Utilisateur extends AppModel {
    var $name = 'Utilisateur';                
    var $hasMany = array(
        'Commentaire' => array(
            'className'     => 'Commentaire',
            'foreignKey'    => 'utilisateur_id',
            'conditions'    => 'Commentaire.statut = 1',
            'order'         => 'Commentaire.created DESC',
            'limit'         => '5',
            'dependent'=> true
        )
    );  
}
?>
  1. <?php
  2. class Utilisateur extends AppModel {
  3. var $name = 'Utilisateur';
  4. var $hasMany = array(
  5. 'Commentaire' => array(
  6. 'className' => 'Commentaire',
  7. 'foreignKey' => 'utilisateur_id',
  8. 'conditions' => 'Commentaire.statut = 1',
  9. 'order' => 'Commentaire.created DESC',
  10. 'limit' => '5',
  11. 'dependent'=> true
  12. )
  13. );
  14. }
  15. ?>

Les clés possibles pour les tableaux d'association hasMany sont :

  • className : le nom de classe du modèle associé au modèle courant. Si l'on souhaite définir la relation 'Utilisateur a plusieurs Commentaire', la valeur associée à la clef 'className' devra être 'Commentaire'.
  • foreignKey : 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 hasMany 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.
  • dependent : lorsque dependent vaut true, une suppression récursive du modèle est possible. Dans cet exemple, les enregistrements Commentaires seront supprimés lorsque leur Utilisateur associé l'aura été.

    Le second paramètre de la méthode Modele->delete() doit être true afin que la suppression récursive ait lieu.

  • finderQuery: une requête SQL complète que CakePHP peut utiliser pour retrouver les enregistrements associés au modèle. Ceci ne devrait être utilisé que dans les 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 Utilisateur récupèreront également les Commentaires reliés si ils existent :

// Exemple de résultat d'un appel à $this->Utilisateur->find().

Array
(  
    [Utilisateur] => Array
        (
            [id] => 121
            [nom] => Gwoo le Kungwoo
            [created] => 2007-05-01 10:31:01
        )
    [Commentaire] => Array
        (
            [0] => Array
                (
                    [id] => 123
                    [utilisateur_id] => 121
                    [titre] => Sur Gwoo le Kungwoo
                    [corps] => La Kungwooité est assez Gwooteuse
                    [created] => 2006-05-01 10:31:01
                )
            [1] => Array
                (
                    [id] => 123
                    [utilisateur_id] => 121
                    [titre] => Plus sur Gwoo
                    [corps] => Mais qu'en est-il ?
                    [created] => 2006-05-01 10:41:01
                )
        )
)
  1. // Exemple de résultat d'un appel à $this->Utilisateur->find().
  2. Array
  3. (
  4. [Utilisateur] => Array
  5. (
  6. [id] => 121
  7. [nom] => Gwoo le Kungwoo
  8. [created] => 2007-05-01 10:31:01
  9. )
  10. [Commentaire] => Array
  11. (
  12. [0] => Array
  13. (
  14. [id] => 123
  15. [utilisateur_id] => 121
  16. [titre] => Sur Gwoo le Kungwoo
  17. [corps] => La Kungwooité est assez Gwooteuse
  18. [created] => 2006-05-01 10:31:01
  19. )
  20. [1] => Array
  21. (
  22. [id] => 123
  23. [utilisateur_id] => 121
  24. [titre] => Plus sur Gwoo
  25. [corps] => Mais quen est-il ?
  26. [created] => 2006-05-01 10:41:01
  27. )
  28. )
  29. )

Une chose dont il faut se rappeler est que vous aurez besoin d'une association "Commentaire belongsTo Utilisateur" en complément, afin de pouvoir récupérer les données dans les deux sens. Ce que nous avons défini dans cette section vous donne la possibilité d'obtenir les données de Commentaire depuis l'Utilisateur. En ajoutant l'association "Commentaire belongsTo Utilisateur" dans le modèle Commentaire, vous aurez la possibilité de connaître les données de l'Utilisateur depuis le modèle Commentaire - cela complète la connexion entre eux et permet un flot d'informations depuis n'importe lequel des deux modèles.

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.

HABTM : Nécessite une table de jointure séparée qui contient les deux noms de modèles.
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'            => ''
            )
        );             
}
?>
  1. <?php
  2. class Recette extends AppModel {
  3. var $name = 'Recette';
  4. var $hasAndBelongsToMany = array(
  5. 'Tag' =>
  6. array('className' => 'Tag',
  7. 'joinTable' => 'recettes_tags',
  8. 'foreignKey' => 'recette_id',
  9. 'associationForeignKey' => 'tag_id',
  10. 'conditions' => '',
  11. 'order' => '',
  12. 'limit' => '',
  13. 'unique' => true,
  14. 'finderQuery' => '',
  15. 'deleteQuery' => '',
  16. 'insertQuery' => ''
  17. )
  18. );
  19. }
  20. ?>

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')));
  1. $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')));
  1. $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
                    )
            )
    )
}

3.7.6.6 Créer et détruire des Associations à la volée

Quelquefois il devient nécessaire de créer et détruire les associations de modèles à la volée. Cela peut être le cas pour un certain nombre de raisons :

  • Vous voulez réduire la quantité de données associées qui seront récupérées, mais toutes vos associations sont sur le premier niveau de récursion.
  • Vous voulez changer la manière dont une association est définie afin de classer ou filtrer les données associées.

La création et destruction se font en utilisant les méthodes de modèles CakePHP bindModel() et unbindModel(). Mettons en place quelques modèles pour pouvoir ensuite voir comment fonctionnent bindModel() et unbindModel(). Nous commencerons avec deux modèles :

<?php

class Meneur extends AppModel {
    var $name = 'Meneur';

    var $hasMany = array(
        'Suiveur' => array(
            'className' => 'Suiveur',
            'order'     => 'Suiveur.rang'
        )
    );
}

?>

<?php

class Suiveur extends AppModel {
    var $name = 'Suiveur';
}

?>
  1. <?php
  2. class Meneur extends AppModel {
  3. var $name = 'Meneur';
  4. var $hasMany = array(
  5. 'Suiveur' => array(
  6. 'className' => 'Suiveur',
  7. 'order' => 'Suiveur.rang'
  8. )
  9. );
  10. }
  11. ?>
  12.  
  13. <?php
  14. class Suiveur extends AppModel {
  15. var $name = 'Suiveur';
  16. }
  17. ?>

Maintenant, dans le contrôleur MeneursController, nous pouvons utiliser la méthode find() du modèle Meneur pour retrouver un Meneur et les Suiveurs associés. Comme vous pouvez le voir ci-dessus, le tableau d'association dans le modèle Meneur définit une relation "Meneur hasMany (a plusieurs) Suiveurs". Dans un but démonstratif, utilisons unbindModel() pour supprimer cette association dans une action du contrôleur.

function uneAction() {
    // Ceci récupère tous les Meneurs, ainsi que leurs Suiveur
    $this->Meneur->findAll();

    // Supprimons la relation hasMany() ...
    $this->Meneur->unbindModel(
        array('hasMany' => array('Suiveur'))
    );

    // Désormais l'utilisation de la fonction find() retournera
    // des Meneurs, sans aucun Suiveurs
    $this->Meneur->findAll();

    // NOTE : unbindModel n'affecte que la prochaine fonction find.
    // Un autre appel à find() utilisera les informations d'association
    // telles que configurée.

    // Nous avons déjà utilisé findAll() après unbindModel(),
    // ainsi cette ligne récupèrera une fois encore les Meneurs 
    // avec leurs Suiveurs ...
    $this->Meneur->findAll();
}
  1. function uneAction() {
  2. // Ceci récupère tous les Meneurs, ainsi que leurs Suiveur
  3. $this->Meneur->findAll();
  4. // Supprimons la relation hasMany() ...
  5. $this->Meneur->unbindModel(
  6. array('hasMany' => array('Suiveur'))
  7. );
  8. // Désormais l'utilisation de la fonction find() retournera
  9. // des Meneurs, sans aucun Suiveurs
  10. $this->Meneur->findAll();
  11. // NOTE : unbindModel n'affecte que la prochaine fonction find.
  12. // Un autre appel à find() utilisera les informations d'association
  13. // telles que configurée.
  14. // Nous avons déjà utilisé findAll() après unbindModel(),
  15. // ainsi cette ligne récupèrera une fois encore les Meneurs
  16. // avec leurs Suiveurs ...
  17. $this->Meneur->findAll();
  18. }

Encore un rappel. Enlever ou ajouter des associations en utilisant bindModel() et unbindModel() ne fonctionne que pour la prochaine opération sur le modèle à moins que le second paramètre ait été fixé à true. Si le second paramètre a été fixé à true, le lien reste en place pour le rete de la requête. Voici un modèle basique de l'usage de unbindModel() :

$this->Modele->unbindModel(
    array('associationType' => array('nomDeClasseModeleAssocie'))
);
  1. $this->Modele->unbindModel(
  2. array('associationType' => array('nomDeClasseModeleAssocie'))
  3. );

Maintenant que nous sommes arrivés à supprimer une association à la volée, ajoutons-en une. Notre Meneur jusqu'à présent sans Principes a besoin d'être associé à quelques Principes. Le fichier de modèle pour notre modèle Principe est dépouillé, il n'y a que la ligne var $name. Associons à la volée des Principes à notre Meneur (mais rappelons-le, seulement pour la prochaine opération find). Cette fonction apparaît dans le contrôleur MeneursController :

function uneAutreAction() {
    // Il n'y a pas d'association Meneur hasMany Principes
    // dans le fichier de modèle meneur.php, ainsi un find
    // situé ici ne récupèrera que les Meneurs.
    $this->Meneur->findAll();

    // Utilisons bindModel() pour ajouter une nouvelle association
    // au modèle Meneur :
    $this->Meneur->bindModel(
        array('hasMany' => array(
                'Principe' => array(
                    'className' => 'Principe'
                )
            )
        )
    );

    // Maintenant que nous les avons associés correctement,
    // nous pouvons utiliser la fonction find une seule fois
    // pour récupérer les Meneurs avec leurs Principes associés :
    $this->Meneur->findAll();
}
  1. function uneAutreAction() {
  2. // Il n'y a pas d'association Meneur hasMany Principes
  3. // dans le fichier de modèle meneur.php, ainsi un find
  4. // situé ici ne récupèrera que les Meneurs.
  5. $this->Meneur->findAll();
  6. // Utilisons bindModel() pour ajouter une nouvelle association
  7. // au modèle Meneur :
  8. $this->Meneur->bindModel(
  9. array('hasMany' => array(
  10. 'Principe' => array(
  11. 'className' => 'Principe'
  12. )
  13. )
  14. )
  15. );
  16. // Maintenant que nous les avons associés correctement,
  17. // nous pouvons utiliser la fonction find une seule fois
  18. // pour récupérer les Meneurs avec leurs Principes associés :
  19. $this->Meneur->findAll();
  20. }

Ca y est, vous y êtes. L'utilisation basique de bindModel() est l'encapsulation d'un tableau d'association classique dans tableau dont la clé est le nom du type d'association que vous essayez de créer :

$this->Modele->bindModel(
        array('nomAssociation' => array(
                'nomDeClasseModeleAssocie' => array(
                    // les clés normales d'une association sont à mettre ici ...
                )
            )
        )
    );
  1. $this->Modele->bindModel(
  2. array('nomAssociation' => array(
  3. 'nomDeClasseModeleAssocie' => array(
  4. // les clés normales d'une association sont à mettre ici ...
  5. )
  6. )
  7. )
  8. );

Bien que le modèle nouvellement associé n'ait besoin d'aucune définition d'association dans son fichier de modèle, il devra tout de même contenir les clés afin que la nouvelle association fonctionne bien.