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’.

Pierre-Emmanuel Fringant

Articles connexes

Commentaires

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…

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ères equipelocauxid et equipevisiteursid, or la table rencontres a bien sa propre clé primaire id, et il n’y a aucune contrainte d’unicité sur le couple de clés étrangères. Ai-je bien cerné ta question ?

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 !

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

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.

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 ?!

« 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.

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

Bonjour,

Je me demandais : Comment peut on obtenir la liste (id par exemple) de toutes les rencontres effectuées par une équipe donnée ?

  • Je récupère la liste des matchs à domicile et ceux à l’exterieur, et je merge les réponses ?
  • Je récupère la liste des matchs à domicile (par exemple) avec une condition sur le statut de l’equipe permettant qu’elle soit aussi ‘visiteurs’ :
// Je veux les rencontres de l'equipe 1
$conditions = array('OR'=>array(array('EquipeLocaux.id'=>1),array('EquipeVisiteurs.id'=>1)));
$rencontres = $this->Equipe->RencontreDomicile->find('all',array('conditions'=>$conditions));

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 ?

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:

array(
'clasName'=>'Personne',                      'joinTable'=>'Personne_Personne',
'foreignKey'=>'pasresponsable_id',
'associationForeignKey'=>'responsable_id'
 ),                               'Responsable'=>array(                                       'clasName'=>'Personne',                      'joinTable'=>'Personne_Personne',            'foreignKey'=>'responsable_id'                'associationForeignKey'=>'personnel_id'
   )
);
}
?>

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.

array('className'=>'LdapUser','foreignKey'=>'login'),'Service','Regime','Category'); 
    var $hasMany = array('Demande');
    var $hasAndBelongsToMany = array(
                'Personnel'=>array(
                                    'clasName'=>'Personne',
                    'joinTable'=>'Personne_Personne',
                                    'foreignKey'=>'personnel_id',
                    'associationForeignKey'=>'responsable_id'
                                    ),
                'Responsable'=>array(
                                        'clasName'=>'Personne',
                    'joinTable'=>'Personne_Personne',
                                        'foreignKey'=>'responsable_id',
                    'associationForeignKey'=>'personnel_id'
                                        )
                );
}
?>

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 :)

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?

Participez

Pour insérer une portion de code, utilisez <pre lang="php">...</pre>