Une clé étrangère présente deux fois dans une table
Imaginons une application de gestion de résultats sportifs : nous avons une table « equipes » et une table « rencontres ». La table « recontres » va comporter deux fois la clé primaire de la table « equipes » : equipelocauxid pour l’équipe locale et equipevisiteursid pour l’équipe des visiteurs. Comment gérer cette association particulière dans CakePHP ?
1. Définition des alias de Modèle
Voyons le Modèle « Equipe » :1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class Equipe extends AppModel { var $name = "Equipe"; var $hasMany = array( 'RencontreDomicile' => array( 'className' => 'Rencontre', 'foreignKey' => 'equipe_locaux_id' ), 'RencontreExterieur' => array( 'className' => 'Rencontre', 'foreignKey' => 'equipe_visiteurs_id' ) ); } |
Et le Modèle « Rencontre » :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class Rencontre extends AppModel { var $name = "Rencontre"; var $belongsTo = array( 'EquipeLocaux' => array( 'className' => 'Equipe', 'foreignKey' => 'equipe_locaux_id' ), 'EquipeVisiteurs' => array( 'className' => 'Equipe', 'foreignKey' => 'equipe_visiteurs_id' ) ); } |
Grâce à cette définition de l’association, nous pouvons faire référence à la même table « equipes » mais en différenciant bien le statut de chaque équipe pour une rencontre donnée.
Dans le Contrôleur « RencontresController », nous pouvons obtenir les informations sur les deux équipes qui ont participé à une rencontre donnée :
1 2 3 4 5 6 7 8 9 10 11 12 13 | // /controllers/rencontres_controller.php function adversaires($id) { $this->set( 'equipe_locaux', $this->Rencontre->EquipeLocaux->findByRencontreId($id) ); $this->set( 'equipe_visiteurs', $this->Rencontre->EquipeVisiteurs->findByRencontreId($id) ); } |
Dans le Contrôleur « EquipesController », nous pouvons savoir pour une équipe donnée, les matchs joués à domicile et ceux joués à l’extérieur :
1 2 3 4 5 6 7 8 9 10 11 12 13 | // /controllers/equipes_controller.php function rencontres_jouees($id) { $this->set( 'rencontres_domicile', $this->Equipe->RencontreDomicile->findAllByEquipeId($id) ); $this->set( 'rencontres_exterieur', $this->Equipe->RencontreExterieur->findAllByEquipeId($id) ); } |
2. Utilisation des alias à l’intérieur du Modèle
Grâce à la définition des associations faites dans le Modèle, nous avons défini des alias différents pour la même classe Model. Nous devons faire attention à employer l’alias plutôt que le nom du Modèle à l’intérieur des méthodes de la classe Model : l’alias en cours est accessible via $this->alias.
Voyons par exemple le callback afterFind du Modèle Equipe, à qui l’on va demander de tester l’existence d’un drapeau en GIF portant le nom de l’id de l’équipe dans le répertoire img/drapeaux, et de compléter le tableau de données renvoyé par la base avec le nom de l’image trouvée.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // /app/model/equipe.php function afterFind($results) { $drapeaux_dir = 'img/drapeaux/'; foreach($results as $k => $result) { if($result[$this->alias]['id']) { $drapeau_img = $result[$this->alias]['id'].'.gif'; if(@file_exists($drapeaux_dir.$drapeau_img)) { $results[$k][$this->alias]['drapeau_img'] = $drapeaux_dir.$drapeau_img; } else { $results[$k][$this->alias]['drapeau_img'] = $drapeaux_dir.'no_flag.gif'; } } } return $results; } |
Si nous avions écrit, intuitivement, $result['Equipe']['id'] au lieu de $result[$this->alias]['id'], nous aurions eu droit à un message d’erreur, car ‘Equipe’ est bien le nom du Modèle, mais pas le nom de l’instance en cours. Ce nom peut être, selon la méthode qui appelle le find ou le findAll :
- soit effectivement ‘Equipe’,
- soit ‘EquipeLocaux’,
- soit ‘EquipeVisiteurs’.
Commentaires
13 février 2008 à 11:57
Salut,
Dirais-tu que ton exemple permet de modéliser sous Cake une « association réflexive » ?
Parce qu’en cas de multiples rencontres entre les mêmes équipes (relation n:m), l’association « hasMany / bleongsTo » ne tiendrait plus… or Cake semble ne pas pouvoir modéliser correctement ce genre de relation (SQLellement parlant bien entendu).
Je suis à la recherche de solutions sur le sujet, car je dois utiliser des schémas de BDD tout fait, dont je ne peux modifier les associations et les clés/index…
13 février 2008 à 14:39
Le modèle que je propose ne pose aucun problème en cas de multiples rencontres entre les mêmes équipes. Cela coincerait si la clé primaire de la table
rencontresétait composée des deux clés étrangèresequipelocauxidetequipevisiteursid, or la tablerencontresa bien sa propre clé primaireid, et il n’y a aucune contrainte d’unicité sur le couple de clés étrangères. Ai-je bien cerné ta question ?13 février 2008 à 18:00
Oui tu as bien cerné, mais je dois encore analyser ta réponse pour être sûr de mon fait, mais là je pars du bureau, donc ce sera pour demain !
14 février 2008 à 14:57
Ce que je voulais dire, c’est que théoriquement parlant, la table « rencontres » que tu décris est en fait une « relation réflexive » de la table « équipes », c’est à dire qu’elle se fait une HABTM sur elle même.
Donc en modélisation relationnelle, la table « rencontres » ne devrait pas contenir de champ id auto-increment, mais simplement deux fois la clé primaire de la table « équipes ». Et par ailleurs, la clé primaire de la table « rencontres » devrait se faire sur les deux colonnes à la fois, car nous sommes dans un cas « plusieurs à plusieurs ».
Or, Cake saute allègrement à pieds joints sur le concept de relation réflexive et sur les clés primaires multiples ou composées.
Je me bats avec cela depuis quelques jours, car l’ajout « artificiel » d’un champ id dans ce type de relation entraîne une baisse de perforamnce qui peut vite être conséquente, en plus d’être discutable SQLellement parlant.
Je nuance toutefois, car dans ton cas, on pourrait très bien numéroter les rencontres et là, la colonne id aurait une justification possible. Cependant, dans ce cas, la table rencontres contiendrai d’autres champs, comme « résultat », « cartons jaune », « nombre de buts », etc. et ne serait pas une simple table de liaison…
Voilà un lien vers une discussion que j’ai eu avec des gourous SQL, qui conforte mon idée que Cake pêche un peu à ce niveau : http://www.developpez.net/forums/showthread.php?t=486821
et ensuite : http://www.developpez.net/forums/showthread.php?t=490905
14 février 2008 à 15:42
Je suis tout à fait d’accord avec toi, j’ai eu le problème avec une relation HABTM (gestion de frais de port par article et par pays) et il est impossible de faire proprement (conceptuellement parlant) sans faire les choses à la main. J’espère que cela sera amélioré dans les prochaines versions.
14 février 2008 à 16:05
Ouf, cela me rassure que tu penses comme moi ! Car en lisant le Google Group sur ce thème, j’avais l’impression de passer pour un ch… qui casse les pieds avec sa théorie et sa modélisation !
Donc d’après toi, pour toute table qui n’est pas « cake compliant », nous devons rajouter un champ id auto-increment ? Mais alors, au niveau de la création de la table (par exemple avec DbDesigner), comment on fait ? On déclare juste la PK sur id auto-increment et on pose deux index simple sur les autres champs ? Et si je migre mon appli sur un autre SGBD ou sous un autre langage de script, je sui bon pour refaire toutes mes tables ?!
14 février 2008 à 17:04
« On déclare juste la PK sur id auto-increment et on pose deux index simple sur les autres champs ? » - C’est la solution que j’ai adoptée, ça n’est pas très satisfaisant, mais cela permet de continuer à utiliser certains automatismes de Cake. « Et si je migre mon appli sur un autre SGBD ou sous un autre langage de script, je suis bon pour refaire toutes mes tables ?! » - Et oui ! D’où une sérieuse attente d’amélioration sur ce point.
14 février 2008 à 18:04
Je me sens moi seul d’un coup ;o))
Il faudrait en parler à la communauté, mais mon niveau d’anglais écrit est vraiment faible pour être sûr de tout transcrire correctement, notamment les arguments pointus de mes gourous SQL…
J’ai un léger doute quant à une amélioration sur ce point, voici une affirmation de Nate en réponse à un ticket : « Cake does not support compound primary keys in any form, fashion, or capacity. »
Ou cette discussion animée du Google group
12 juillet 2009 à 13:05
Bonjour,
Je me demandais : Comment peut on obtenir la liste (id par exemple) de toutes les rencontres effectuées par une équipe donnée ?
Cette solution n’apparaît pas très logique (d’un point de vue écriture, pas programmation) car je recherche les Matchs à domicile même s’ils sont à l’exterieur en fait… Cela revient à considérer que chaque match est un match à domicile pour l’une des équipes. Est ce juste ?
3 décembre 2009 à 14:06
Bonjour,
Je souhaiterais avoir vôtre avis sur un problème que je rencontre en ce moment, je réalise une application avec Cake qui contient une table personne
Personnes{ id auto-increment nom varchar(24) prenom .. … responsable_id int }
Sachant qu’une personne peut avoir un responsable et que le responsable est lui même une personne, je souhaiterais donc mettre en place une relation reflective.
Mais si j’ai bien compris vos explications
je dois donc rajouter un champs (ex:pasresponsableid) à ma table Personne: Personnes{ id auto-increment nom varchar(24) prenom .. … responsableid int pasresponsable_id int }
et définir mon model comme suit:
J’ai implémenté cette solution et malheureusement elle ne fonctionne pas,et m’affiche l’erreur suivante:
Missing Database Table Error: Database table personnels for model Personnel was not found.
Je voudrais savoir si vous auriez une idée de ce qui cloche..ou tout simplement si j’ai mal implémenté ma solution?
Je vous remercie d’avance.
3 décembre 2009 à 14:09
3 novembre 2010 à 21:12
Bonjour, Je suis en train de refaire mon site de foot avec CakePhp. Un outil formidable qui me facilite bien des choses.
Cependant, je comprends bien le principe mais avec le principe de ne pas utiliser les RequestAction, je suis bloqué.
Quelle serait donc la solution pour afficher les équipes locales et visiteuses avec la méthode de ne plus utiliser les RequestAction.
Ca me bloque pas mal de nuits pour le moment
Merci beaucoup en tout cas pour ces tutos qui me sont bien utiles. Tout est très bien expliqué. Bravo pour votre travail
9 décembre 2010 à 23:28
Je viens de découvrir cake, donc je ne suis pas vraiment un expert
Mais dans ton exemple il y a une faute c’est className et non clasName.
Ensuite le message d’erreur signifie qu’il ne trouve pas la table personnel pour le model personnel ne serait ce pas du à la convention d’ecriture des id et de ton personnel_id?