Une nouvelle intégration au coeur de CakePHP 1.2 est le Behavior « Containable »
ContainableBehavior
. Ce behavior vous permet de filtrer et de
limiter les opérations de récupération de données « find ». Utiliser Containable
vous aidera a réduire l’utilisation inutile de votre base de données et
augmentera la vitesse et la plupart des performances de votre application. La
classe vous aidera aussi a chercher et filtrer vos données pour vos
utilisateurs d’une façon propre et cohérente.
Le behavior « Containable » vous permet de rationaliser et de simplifier les
opérations de construction du model. Il agit en modifiant temporairement ou
définitivement les associations de vos models. Il fait cela en utilisant
des « containements » pour générer une série d’appels bindModel
et
unbindModel
. Étant donné que Containable modifie seulement les relations
déjà existantes, il ne vous permettra pas de restreindre les résultats pour
des associations distantes. Pour cela, vous devriez voir les
Tables jointes.
Pour utiliser le nouveau behavior, vous pouvez l’ajouter à la propriété $actAs de votre model:
class Post extends AppModel {
public $actsAs = array('Containable');
}
Vous pouvez aussi attacher le behavior à la volée:
$this->Post->Behaviors->attach('Containable');
Pour voir comment Containable fonctionne, regardons quelques exemples.
Premièrement, nous commencerons avec un appel find()
sur un model nommé
“Post”. Disons que ce “Post” a plusieurs (hasMany) “Comment”, et “Post” a et
appartient à plusieurs (hasAndBelongsToMany) “Tag”. La quantité de données
récupérées par un appel find()
normal est assez étendue:
debug($this->Post->find('all'));
[0] => Array
(
[Post] => Array
(
[id] => 1
[titre] => Premier article
[contenu] => aaa
[created] => 2008-05-18 00:00:00
)
[Comment] => Array
(
[0] => Array
(
[id] => 1
[post_id] => 1
[auteur] => Daniel
[email] => dan@example.com
[siteweb] => http://example.com
[commentaire] => Premier commentaire
[created] => 2008-05-18 00:00:00
)
[1] => Array
(
[id] => 2
[post_id] => 1
[auteur] => Sam
[email] => sam@example.net
[siteweb] => http://example.net
[commentaire] => Second commentaire
[created] => 2008-05-18 00:00:00
)
)
[Tag] => Array
(
[0] => Array
(
[id] => 1
[name] => A
)
[1] => Array
(
[id] => 2
[name] => B
)
)
)
[1] => Array
(
[Post] => Array
(...
Pour certaines interfaces de votre application, vous pouvez ne pas avoir
besoin d’autant d’information depuis le model Post. Le
Behavior containable
permet de réduire ce que le find() retourne.
Par exemple, pour ne récupérer que les informations liées au post vous pouvez faire cela:
$this->Post->contain();
$this->Post->find('all');
Vous pouvez utiliser la magie de « Containable » à l’intérieur d’un appel find():
$this->Post->find('all', array('contain' => false));
Après avoir fait cela, vous vous retrouvez avec quelque chose de plus concis:
[0] => Array
(
[Post] => Array
(
[id] => 1
[titre] => Premier article
[contenu] => aaa
[created] => 2008-05-18 00:00:00
)
)
[1] => Array
(
[Post] => Array
(
[id] => 2
[titre] => Second article
[contenu] => bbb
[created] => 2008-05-19 00:00:00
)
)
Ceci n’est pas nouveau: en fait, vous pouvez obtenir le même résultat sans
le behavior Containable
en faisant quelque chose comme:
$this->Post->recursive = -1;
$this->Post->find('all');
Le behavior Containable
s’impose vraiment quand vous avez des associations
complexes, et que vous voulez rogner le nombre d’information au même niveau. La
propriété $recursive des models est utile si vous voulez éviter un niveau de
récursivité entier, mais pas pour choisir ce que vous garder à chaque niveau.
Regardons ensemble comment la methode contain()
agit.
Le premier argument de la méthode accepte le nom, ou un tableau de noms, des models à garder lors du find. Si nous désirons aller chercher tous les posts et les tags annexes (sans aucune information de commentaire), nous devons essayer quelque chose comme:
$this->Post->contain('Tag');
$this->Post->find('all');
Nous pouvons à nouveau utiliser la clé contain dans l’appel find():
$this->Post->find('all', array('contain' => 'Tag'));
Sans le behavior Containable, nous finirions par utiliser la méthode
unbindModel()
du model, plusieurs fois si nous épluchons plusieurs models.
Le behavior Containable
fournit un moyen plus propre pour accomplir cette
même tâche.
Containable permet également d’aller un peu plus loin : vous pouvez filtrer les données des models associés. si vous regardez les résultats d’un appel find() classique, notez le champ « auteur » dans le model « Comment ». Si vous êtes intéressés par les posts et les noms des commentaires des auteurs - et rien d’autre - vous devez faire quelque chose comme:
$this->Post->contain('Comment.auteur');
$this->Post->find('all');
// ou..
$this->Post->find('all', array('contain' => 'Comment.auteur'));
ici, nous avons dit au behavior Containable de nous donner les informations du post, et uniquement le champ auteur du model Comment associé. Le résultat du find ressemble à:
[0] => Array
(
[Post] => Array
(
[id] => 1
[titre] => Premier article
[contenu] => aaa
[created] => 2008-05-18 00:00:00
)
[Comment] => Array
(
[0] => Array
(
[auteur] => Daniel
[post_id] => 1
)
[1] => Array
(
[auteur] => Sam
[post_id] => 1
)
)
)
[1] => Array
(...
Comme vous pouvez le voir, les tableaux de Comment ne contiennent uniquement que le champ auteur (avec le post_id qui est requis par CakePHP pour présenter le résultat)
Vous pouvez également filtrer les données associées à Comment en spécifiant une condition:
$this->Post->contain('Comment.author = "Daniel"');
$this->Post->find('all');
//ou...
$this->Post->find('all', array('contain' => 'Comment.author = "Daniel"'));
Ceci nous donne comme résultat les posts et commentaires dont daniel est l’auteur:
[0] => Array
(
[Post] => Array
(
[id] => 1
[title] => Premier article
[content] => aaa
[created] => 2008-05-18 00:00:00
)
[Comment] => Array
(
[0] => Array
(
[id] => 1
[post_id] => 1
[author] => Daniel
[email] => dan@example.com
[website] => http://example.com
[comment] => Premier commentaire
[created] => 2008-05-18 00:00:00
)
)
)
Il y a un important gain à utiliser Containable quand on filtre sur des associations plus profondes. Dans l’exemple précédent, imaginez que vous avez 3 posts dans votre base de données et que Daniel a commenté sur 2 de ces posts. L’opération $this->Post->find(“all”, array(“contain” => “Comment.author = « Daniel »”)); retournerait TOUS les 3 posts, pas juste les 3 posts que Daniel a commenté. Cela ne va pas retourner tous les comments cependant, juste les comments de Daniel.
[0] => Array
(
[Post] => Array
(
[id] => 1
[title] => First article
[content] => aaa
[created] => 2008-05-18 00:00:00
)
[Comment] => Array
(
[0] => Array
(
[id] => 1
[post_id] => 1
[author] => Daniel
[email] => dan@example.com
[website] => http://example.com
[comment] => First comment
[created] => 2008-05-18 00:00:00
)
)
)
[1] => Array
(
[Post] => Array
(
[id] => 2
[title] => Second article
[content] => bbb
[created] => 2008-05-18 00:00:00
)
[Comment] => Array
(
)
)
[2] => Array
(
[Post] => Array
(
[id] => 3
[title] => Third article
[content] => ccc
[created] => 2008-05-18 00:00:00
)
[Comment] => Array
(
[0] => Array
(
[id] => 22
[post_id] => 3
[author] => Daniel
[email] => dan@example.com
[website] => http://example.com
[comment] => Another comment
[created] => 2008-05-18 00:00:00
)
)
)
Si vous voulez filtrer les posts selon les comments, pour que les posts non commentés par Daniel ne soient pas retournés, le plus simple est de trouver tous les comments de Daniel et de faire un contain sur les Posts.
$this->Comment->find('all', array(
'conditions' => 'Comment.author = "Daniel"',
'contain' => 'Post'
));
Des filtres supplémentaires peuvent être utilisées en utilisant les options de recherche standard find:
$this->Post->find('all', array('contain' => array(
'Comment' => array(
'conditions' => array('Comment.author =' => "Daniel"),
'order' => 'Comment.created DESC'
)
)));
Voici un exemple d’utilisation de ContainableBehavior
quand vous avez des
relations profondes et complexes entre les models.
Examinons les associations des models suivants:
User->Profile
User->Account->AccountSummary
User->Post->PostAttachment->PostAttachmentHistory->HistoryNotes
User->Post->Tag
Voici comment nous récupérons les associations ci-dessus avec le behavior Containable:
$this->User->find('all', array(
'contain' => array(
'Profile',
'Account' => array(
'AccountSummary'
),
'Post' => array(
'PostAttachment' => array(
'fields' => array('id', 'name'),
'PostAttachmentHistory' => array(
'HistoryNotes' => array(
'fields' => array('id', 'note')
)
)
),
'Tag' => array(
'conditions' => array('Tag.name LIKE' => '%happy%')
)
)
)
));
Gardez à l’esprit que la clé “contain” n’est utilisée qu’une seule fois dans le model principal, vous n’avez pas besoin d’utiliser “contain” à nouveau dans les models liés.
Note
En utilisant les options “fields” et “contain” - n’oubliez pas d’inclure toutes les clés étrangères que votre requête requiert directement ou indirectement. Notez également que pour que le behavior Containable puisse fonctionner avec le contain pour tous les models, vous devez l’attacher à votre AppModel.
Le Behavior Containable
a plusieurs options qui peuvent être définies
quand le behavior est attaché à un model. Ces paramètres vous permettent
d’affiner le behavior de Containable et de travailler plus facilement avec
les autres behaviors.
recursive (boolean, optional), définir à true pour permettre au behavior
Containable, de déterminer automatiquement le niveau de récursivité nécessaire
pour récupérer les models spécifiés et pour paramétrer la récursivité du model
à ce niveau. Le définir à false désactive cette fonctionnalité. La valeur par
défaut est true
.
notices (boolean, optional), émet des alertes E_NOTICES pour les liaisons référencées dans un appel containable et qui ne sont pas valides. La valeur par défaut est true.
autoFields (boolean, optional), ajout automatique des champs nécessaires
pour récupérer les liaisons requêtées. La valeur par défaut est true
.
order: (string, optional) l’ordre dans lequel les elements contenus sont triés.
A partir de l’exemple précédent, ceci est un exemple de la façon de forcer les Posts à être triés selon la date de dernière modification:
$this->User->find('all', array(
'contain' => array(
'Profile',
'Post' => array(
'order' => 'Post.updated DESC'
)
)
));
Vous pouvez changer les paramètres du Behavior Containable à l’exécution, en ré-attachant le behavior comme vu au chapitre Behaviors (Comportements) (Utiliser les Behaviors).
Le behavior Containable peut quelque fois causer des problèmes avec d’autres
behaviors ou des requêtes qui utilisent des fonctions d’agrégations et/ou des
clauses GROUP BY. Si vous obtenez des erreurs SQL invalides à cause du mélange
de champs agrégés et non-agrégés, essayer de désactiver le paramètre
autoFields
:
$this->Post->Behaviors->load('Containable', array('autoFields' => false));
En incluant le paramètre “contain” dans la propriété $paginate
,
la pagination sera appliquée à la fois au find(“count”) et au find(“all”) dans
le model.
Voir la section Utilisation de Containable pour plus de détails.
Voici un exemple pour limiter les associations en paginant:
$this->paginate['User'] = array(
'contain' => array('Profile', 'Account'),
'order' => 'User.username'
);
$users = $this->paginate('User');
Note
Si vous faîtes un contain des associations à travers le model à la place, il n’honorera pas l”option récursive de Containable. Donc si vous définissez à -1 par exemple pour le model, cela ne marchera pas:
$this->User->recursive = -1;
$this->User->contain(array('Profile', 'Account'));
$users = $this->paginate('User');