XXXIII. Chapitre 33 : Les relations avec Eloquent 1/2▲
Eloquent est un ORM (Object-Relational Mapping) élégant et efficace. Son utilité essentielle se trouve dans le traitement de données relationnelles. Il est parfois délicat de le mettre en œuvre, surtout pour ceux qui ne sont pas vraiment habitués aux subtilités du modèle relationnel. Dans cet article, je vais m'attacher à présenter les bases de ce domaine avec l'application d'Eloquent. Je vais faire un tour d'horizon complet. Alors c'est parti pour une visite guidée.
Dans cette première partie je vais présenter la construction des relations, je traiterai les problèmes de gestion des enregistrements liés dans un prochain chapitre.
XXXIII-A. La base d'exemple▲
Pour que le voyage soit efficace on va avoir besoin d'une base de données pour tester les différents cas de figure. J'ai créé un modèle de base sur le site laravelsd.com :
Ce site propose un outil simple et efficace pour créer des tables et un schéma relationnel de façon visuelle. La base que j'ai créée couvre tous les cas possibles :
Ce n'est évidemment pas une situation très réaliste, son seul but est de donner l'occasion de présenter toutes les situations utiles pour cet article. L'outil génère gentiment les migrations, les modèles et même des vues ! Vous pouvez récupérer tout ça en cliquant simplement sur ce bouton :
Alors avec une version neuve de Laravel, et après avoir créé et configuré une base, effectuez les migrations téléchargées et copiez les modèles. Comme il va nous falloir des enregistrements pour les tests, j'ai créé ce seed :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
class DatabaseSeeder extends
Seeder {
/**
* Run the database seeds.
*
*
@return
void
*/
public
function
run()
{
Eloquent::
unguard();
for
($i
=
1
;
$i
<
11
;
$i
++
) {
DB::
table('categories'
)->
insert(array
('nom'
=>
'Categorie '
.
$i
));
DB::
table('periodes'
)->
insert(array
('nom'
=>
'Periode '
.
$i
));
DB::
table('pays'
)->
insert(array
('nom'
=>
'Pays '
.
$i
));
}
for
($i
=
1
;
$i
<
21
;
$i
++
) {
DB::
table('themes'
)->
insert(array
('nom'
=>
'Theme '
.
$i
));
DB::
table('editeurs'
)->
insert(array
('nom'
=>
'Editeur '
.
$i
));
DB::
table('autoedites'
)->
insert(array
('nom'
=>
'Autoedite '
.
$i
));
DB::
table('contacts'
)->
insert(array
('telephone'
=>
'00 00 00 00 00'
,
'editeur_id'
=>
$i
));
DB::
table('villes'
)->
insert(array
('nom'
=>
'Ville '
.
$i
,
'pays_id'
=>
rand(1
,
10
)));
}
for
($i
=
1
;
$i
<
21
;
$i
++
) {
DB::
table('auteurs'
)->
insert(array
('nom'
=>
'Auteur '
.
$i
,
'ville_id'
=>
rand(1
,
20
)));
}
for
($i
=
1
;
$i
<
41
;
$i
++
) {
$choix
=
array
('Autoedite'
,
'Editeur'
);
DB::
table('livres'
)->
insert(array
(
'titre'
=>
'Titre '
.
$i
,
'livrable_id'
=>
rand(1
,
20
),
'theme_id'
=>
rand(1
,
20
),
'livrable_type'
=>
$choix
[
rand(0
,
1
)]
));
}
for
($i
=
1
;
$i
<
21
;
$i
++
) {
$number
=
rand(2
,
8
);
for
($j
=
1
;
$j
<=
$number
;
$j
++
) {
DB::
table('auteur_livre'
)->
insert(array
(
'livre_id'
=>
rand(1
,
40
),
'auteur_id'
=>
$i
));
}
}
for
($i
=
1
;
$i
<
81
;
$i
++
) {
$choix
=
array
('Categorie'
,
'Periode'
);
DB::
table('themables'
)->
insert(array
(
'theme_id'
=>
rand(1
,
20
),
'themable_id'
=>
rand(1
,
10
),
'themable_type'
=>
$choix
[
rand(0
,
1
)]
));
}
}
}
Là aussi c'est un remplissage artificiel mais efficace . Il peut y avoir des redondances parasites mais c'est admissible pour des tests. Maintenant que l'intendance est en place voyons un peu tout ça en détail…
XXXIII-B. La relation 1:1▲
Cette relation est la plus simple mais la moins utile. De quoi s'agit-il ? Prenons le cas de notre exemple :
J'ai une table editeurs et une table contacts. Un éditeur a un numéro de téléphone, celui-ci se trouve enregistré dans la table contact. Comment est établie la relation ? On trouve dans la table contacts le champ editeur_id qui correspond à l'ID de l'éditeur. Je l'ai surligné pour mieux le visualiser. C'est comme cela qu'on peut savoir qu'un numéro de téléphone dans la table contacts correspond à un éditeur dans la table editeurs. On appelle ce champ clé étrangère parce que c'est une valeur qui appartient à une autre table et qui est juste insérée ici pour la relation.
Il ne faut pas analyser longtemps cette situation pour se rendre compte qu'on aurait tout aussi bien pu prévoir le champ telephone dans la table editeurs ! C'est toujours le cas avec une relation de type 1:1 mais alors pourquoi créer deux tables ? En général c'est pour des raisons de performance : si on a des données volumineuses auxquelles on accède rarement, il devient alors intéressant de les mettre dans une table séparée. Autrement dit vous ne rencontrerez pas souvent cette situation ! Mais son intérêt est qu'elle est facile à comprendre et constitue une bonne introduction aux suivantes.
Pour comprendre une relation, il faut se positionner dans une table (en pensée ) et se poser les bonnes questions. On a donc deux cas.
XXXIII-B-1. hasOne▲
Je suis dans la table editeurs. Je regarde la table contacts et je me dis « j'ai là un numéro de téléphone ». En langage Eloquent on traduit ça par hasOne :
Pour traduire cela de façon concrète, on crée dans le modèle Editeur une méthode :
Pour établir la relation, Eloquent part du principe que la clé étrangère est construite à partir du nom du modèle auquel on adjoint « _id », ce qui donne bien « editeur_id ». On a d'autres possibilités, regardez la méthode dans la classe Model :
2.
3.
4.
5.
6.
7.
8.
9.
10.
public function hasOne($related
,
$foreignKey
=
null,
$localKey
=
null)
{
$foreignKey
=
$foreignKey
?:
$this
->
getForeignKey();
$instance
=
new $related
;
$localKey
=
$localKey
?:
$this
->
getKeyName();
return new HasOne($instance
->
newQuery(),
$this
,
$instance
->
getTable().
'
.
'
.
$foreignKey
,
$localKey
);
}
Le deuxième paramètre permet de renseigner la clé étrangère si elle ne respecte pas la norme, donc on peut écrire par exemple :
pour le cas où la clé étrangère se nomme « editeur ». Vous remarquez aussi qu'on dispose d'un troisième paramètre, il sert à renseigner le nom de l'ID de la table d'origine si celui-ci ne se nomme pas « id ». Le cas peut se présenter si vous utilisez Eloquent sur une table existante et que vous ne désirez pas tout changer. Je peux maintenant écrire cela :
$telephone
=
Editeur::
first()->
contact()->
first()->
telephone;
Et je trouve le numéro de téléphone de l'éditeur sélectionné. Eloquent génère ces deux requêtes :
select
*
from
`editeurs`
limit
1
select
*
from
`contacts`
where
`contacts`
.`editeur_id`
=
'1'
limit
1
Avec le query builder ce serait plus laborieux pour obtenir le même résultat :
$editeur
=
DB::
table('
editeurs
'
)->
first();
$contact
=
DB::
table('
contacts
'
)->
where('
editeur_id
'
,
$editeur
->
id)->
first();
$telephone
=
$contact
->
telephone;
Comme Eloquent est très conciliant vous pouvez même écrire cela :
$telephone
=
Editeur::
first()->
contact->
telephone;
Ce qui rend la syntaxe particulièrement élégante et lisible !
XXXIII-B-2. belongsTo▲
Envisageons maintenant l'inverse. Cette fois je me place dans la table des contacts. Là je me dis « j'ai un numéro de téléphone qui appartient à un éditeur ». En langage Eloquent on traduit ça par belongsTo :
Pour traduire cela de façon concrète, on crée dans le modèle Editeur une méthode :
Attention ! Pour établir la relation, Eloquent cette fois utilise le nom de la méthode ! La clé étrangère est construite à partir du nom de la méthode auquel on adjoint « _id », ce qui donne bien editeur_id dans notre cas. On a d'autres possibilités, regardez la méthode dans la classe Model :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
public function belongsTo($related
,
$foreignKey
=
null,
$otherKey
=
null,
$relation
=
null)
{
// If no relation name was given, we will use this debug backtrace to extract
// the calling method's name and use that as the relationship name as most
// of the time this will be what we desire to use for the relatinoships.
if (is_null($relation
))
{
list(,
$caller
) =
debug_backtrace(false);
$relation
=
$caller
[
'
function
'
];
}
// If no foreign key was supplied, we can use a backtrace to guess the proper
// foreign key name by using the name of the relationship function, which
// when combined with an "_id" should conventionally match the columns.
if (is_null($foreignKey
))
{
$foreignKey
=
snake_case($relation
).
'
_id
'
;
}
$instance
=
new $related
;
// Once we have the foreign key names, we'll just create a new Eloquent query
// for the related models and returns the relationship instance which will
// actually be responsible for retrieving and hydrating every relations.
$query
=
$instance
->
newQuery();
$otherKey
=
$otherKey
?:
$instance
->
getKeyName();
return new BelongsTo($query
,
$this
,
$foreignKey
,
$otherKey
,
$relation
);
}
On peut donc renseigner le nom de la clé étrangère si elle ne correspond pas à la norme :
Vous pouvez aussi avec le troisième paramètre indiquer le nom de la clé de la table editeurs si elle ne s'appelle pas « id ».
Je peux maintenant écrire cela :
$nom
=
Contact::
find(5
)->
editeur()->
first()->
nom;
Et je trouve le nom de l'éditeur pour le contact sélectionné. Eloquent génère ces deux requêtes :
select
*
from
`contacts`
where
`id`
=
'5'
limit
1
select
*
from
`editeurs`
where
`editeurs`
.`id`
=
'5'
limit
1
Comme Eloquent est très conciliant vous pouvez même écrire cela :
$nom
=
Contact::
find(5
)->
editeur->
nom;
Ce qui rend la syntaxe particulièrement élégante et lisible !
XXXIII-C. La relation 1:n▲
La relation 1:n est de loin la plus courante ! Dans la base d'exemple on trouve cette relation entre les pays et les villes :
J'ai une table pays et une table villes. Un pays peut avoir plusieurs villes, par contre une ville est forcément située dans un seul pays. Ce type de relation est dissymétrique, contrairement à la relation 1:1 que nous avons vu ci-dessus.
Comment est établie la relation ? On trouve dans la table villes le champ pays_id qui correspond à l'id du pays. Je l'ai entouré pour mieux le visualiser. C'est comme cela qu'on peut savoir qu'une ville correspond à un certain pays. On appelle ce champ « clé étrangère » parce que c'est une valeur qui appartient à une autre table et qui est juste insérée ici pour la relation.
On va à nouveau envisager la relation en se positionnant dans chacune des tables.
XXXIII-C-1. hasMany▲
Commençons par nous situer dans la table des pays. Je me dis « j'ai beaucoup de villes » :
Pour traduire cela de façon concrète on crée dans le modèle Pays une méthode :
Voyons cette méthode dans la classe Model :
2.
3.
4.
5.
6.
7.
8.
9.
10.
public function hasMany($related
,
$foreignKey
=
null,
$localKey
=
null)
{
$foreignKey
=
$foreignKey
?:
$this
->
getForeignKey();
$instance
=
new $related
;
$localKey
=
$localKey
?:
$this
->
getKeyName();
return new HasMany($instance
->
newQuery(),
$this
,
$instance
->
getTable().
'
.
'
.
$foreignKey
,
$localKey
);
}
Si la clé étrangère n'est pas renseignée, elle est construite à partir du nom du modèle. Dans notre cas on a donc « pays_id ». Vous avez aussi la possibilité de renseigner le deuxième paramètre si votre champ ne correspond pas à la norme :
Le troisième paramètre sert à indiquer le nom de la clé pour la table des pays si ce n'est pas « id », comme on l'a déjà vu pour les autres méthodes ci-dessus.
Je peux maintenant écrire cela :
Je trouve toutes les villes pour le pays d'id 5. J'itère dans la collection obtenue pour récupérer les noms des villes. Eloquent génère ces deux requêtes :
select
*
from
`pays`
where
`id`
=
'4'
limit
1
select
*
from
`villes`
where
`villes`
.`pays_id`
=
'4'
Dans mon cas j'obtiens ce résultat :
Ville 1
Ville 2
Ville 10
Vous pouvez évidemment avoir un résultat différent étant donné que j'ai rempli les tables avec des valeurs aléatoires.
XXXIII-C-2. belongsTo▲
Voyons maintenant la relation à partir de la table des villes :
Là je me dis que « j'appartiens à un seul pays ». On retrouve donc le belongsTo qu'on à déjà vu pour la relation de type 1:1. La situation est strictement la même et donc le traitement identique. Tout ce que j'ai dit ci-dessus est donc valable aussi pour la relation de type 1:n. En fait Eloquent est totalement ignorant du type de relation, son seul repère est la méthode utilisée. Donc quand vous employez la méthode belongsTo il ne sait absolument pas si vous avez mis en place une relation 1:1 ou 1:n et il n'en a pas besoin. Il agit de façon localisée.
XXXIII-C-3. hasManyThrough▲
Voyons maintenant une extension de hasMany avec hasManyThrough. Regardez cette partie de la base d'exemple :
On a la relation 1:n vue ci-dessus entre les pays et les villes. On a le même type de relation entres les villes et les auteurs. La méthode hasManyThrough permet de « traverser » la table des villes pour mettre directement en relation la table des pays avec celle des auteurs :
Autrement dit on veut tous les auteurs d'un pays. Pour traduire cela de façon concrète, on crée dans le modèle Pays une méthode auteurs :
Voyons cette méthode dans la classe Model :
2.
3.
4.
5.
6.
7.
8.
9.
10.
public function hasManyThrough($related
,
$through
,
$firstKey
=
null,
$secondKey
=
null)
{
$through
=
new $through
;
$firstKey
=
$firstKey
?:
$this
->
getForeignKey();
$secondKey
=
$secondKey
?:
$through
->
getForeignKey();
return new HasManyThrough(with(new $related
)->
newQuery(),
$this
,
$through
,
$firstKey
,
$secondKey
);
}
Analysons les paramètres :
- C'est le modèle de destination, dans notre cas Auteur ;
- C'est le modèle intermédiaire, dans notre cas Ville ;
- Là on peut mettre la clé étrangère dans la table intermédiaire si elle n'est pas à la norme, nous on a bien pays_id ;
- Là on peut mettre la clé étrangère dans la table finale si elle n'est pas à la norme, nous on a bien ville_id.
Maintenant on peut écrire ça :
J'obtiens ce résultat dans mon cas :
2.
3.
4.
5.
6.
7.
Auteur 2
Auteur 6
Auteur 13
Auteur 19
Auteur 14
Auteur 8
Auteur 15
Eloquent s'en sort avec deux requêtes dont une jointure :
select
*
from
`pays`
where
`id`
=
'4'
limit
1
select
`auteurs`
.*
, `villes`
.`pays_id`
from
`auteurs`
inner
join
`villes`
on
`villes`
.`id`
=
`auteurs`
.`ville_id`
where
`villes`
.`pays_id`
=
'4'
Maintenant comment faire l'inverse de hasManyThrough ? Pas besoin d'inventer quelque chose de nouveau, il suffit d'enchaîner deux belongsTo. Par exemple ce code :
Auteur::
find(2
)->
ville->
pays->
nom;
va nous donner le nom du pays pour l'auteur d'id 2. Élégant non ?
XXXIII-D. La relation n:n▲
La relation n:n est la plus complexe des trois. Voyons un exemple dans la base pour comprendre de quoi il s'agit :
On a une table des auteurs et une table des livres. Un auteur peut écrire plusieurs livres et réciproquement un livre peut être écrit par plusieurs auteurs. Il est impossible de relier directement les deux tables avec des clés étrangères pour régler cette situation. On est donc obligé de créer une table intermédiaire appelée « pivot » chargée de mémoriser les deux clés étrangères, et d'effectuer ainsi la liaison entre les deux tables. Ici c'est la table auteur_livre.
Par convention la table pivot doit comporter le nom des deux modèles séparés par un soulignage (underscore). Ici j'ai choisi auteur_livre, j'aurais pu évidemment choisir aussi livre_auteur puisque la relation est strictement symétrique. On trouve dans la table pivot les deux clés étrangères :
- La clé auteur_id qui référence un enregistrement de la table des auteurs ;
- La clé livre_id qui référence un enregistrement de la table des livres.
XXXIII-D-1. belongsToMany▲
Comme la relation est symétrique on a une seule méthode : belongsToMany. En effet si je me place du côté de la table des auteurs je me dis « j'appartiens à plusieurs livres » (bon c'est une image, il ne faut pas que les auteurs se sentent possédés par leurs œuvres ) et réciproquement du côté de la table des livres je me dis « j'appartiens à plusieurs auteurs » :
Pour concrétiser ça, on crée une méthode livres dans le modèle Auteur :
Et évidemment on crée la réciproque auteurs dans le modèle Livre :
Regardez la signature de la méthode belongsToMany :
public function belongsToMany($related
,
$table
=
null,
$foreignKey
=
null,
$otherKey
=
null,
$relation
=
null)
Les paramètres doivent vous être maintenant familiers parce qu'ils se répètent au fil des méthodes :
- Là on met le nom du modèle de la table visée ;
- Là on met le nom de la table pivot s'il n'est pas conventionnel, nous on a bien choisi auteur_livre (livre_auteur aurait aussi marché) ;
- Là on met le nom de la clé étrangère de la table de départ s'il n'est pas conventionnel ;
- Là on met le nom de la clé étrangère de la table d'arrivée s'il n'est pas conventionnel.
Maintenant si on veut connaître tous les titres des livres d'un auteur c'est facile :
Dans mon cas j'obtiens :
2.
3.
4.
Titre 8
Titre 24
Titre 7
Titre 39
Voici les requêtes générées :
select
*
from
`auteurs`
where
`id`
=
'4'
limit
1
select
`livres`
.*
, `auteur_livre`
.`auteur_id`
as
`pivot_auteur_id`
, `auteur_livre`
.`livre_id`
as
`pivot_livre_id`
from
`livres`
inner
join
`auteur_livre`
on
`livres`
.`id`
=
`auteur_livre`
.`livre_id`
where
`auteur_livre`
.`auteur_id`
=
'4'
De la même manière on peut récupérer le nom des auteurs d'un livre :
Ici j'obtiens :
Auteur 1
Auteur 13
XXXIII-E. Relation polymorphique de type 1:n▲
Maintenant que vous êtes chauds on peut aborder un sujet un peu plus délicat. Regardez cette partie de la base d'exemples :
On retrouve notre table livres déjà vue pour une autre relation. On a aussi deux autres tables : editeurs et autoedites. Voici ce que l'on veut :
- Un éditeur peut correspondre à plusieurs livres mais un livre ne peut appartenir qu'à un éditeur ;
- Un autoéditeur peut correspondre à plusieurs livres mais un livre ne peut appartenir qu'à un autoéditeur :
- Un livre appartient soit à un éditeur, soit à un autoéditeur, mais pas aux deux.
On se rend compte qu'on aurait du mal à s'en sortir avec deux relations 1:n comme on l'a fait précédemment. On a deux relations de même type qui s'adressent à la même table. Ces deux relations ont la même structure mais changent dans leur forme, d'où l'appellation de « polymorphique » qui veut tout simplement dire « plusieurs formes ».
Dans une relation classique 1:n, on établit le lien entre les tables avec une clé étrangère dans la table du côté n et ça suffit pour s'y retrouver sans ambiguïté. Mais là comment va-t-on faire ? Il nous faut deux renseignements :
- Le nom de la table en relation ou de son modèle ;
- L'id de l'enregistrement concerné.
Regardez dans la table des livres, vous trouvez deux champs :
- livrable_id qui est chargé de contenir l'id de l'enregistrement lié ;
- livrable_type qui est chargé de contenir le nom du modèle de la table en relation.
Si on sait quelle est la table reliée et qu'on connaît aussi l'id de l'enregistrement, alors la relation est parfaitement connue. Voici une visualisation de la relation :
XXXIII-E-1. morphTo▲
Eloquent propose la méthode morphTo pour établir une relation polymorphique à partir de la table qui est du côté n. Il faut donc prévoir cette méthode dans le modèle Livre :
Voici la signature de la méthode morphTo :
Voyons les paramètres :
- C'est le nom de la relation, et si on n'en donne pas c'est le nom de la méthode qui est utilisé ;
- C'est le nom du champ qui récupère le type du modèle en liaison, mais si on n'en donne pas il est nommé à partir du nom de la relation auquel on ajoute « _type » ; dans notre cas on a déjà livrable_type ;
- C'est le nom du champ qui récupère la clé étrangère, et si on n'en donne pas il est nommé à partir du nom de la relation auquel on ajoute « _id », dans notre cas on a déjà livrable_id.
Bon maintenant vous vous demandez peut-être pourquoi se donner autant de peine ? Regardez ce code :
Je prends les cinq premiers livres et dans une boucle je récupère le nom dans la table en relation. La méthode livrable va créer une instance du modèle correspondant, soit Editeur, soit Autoedite selon que le livre est en relation avec l'un ou l'autre. Voilà ce que j'obtiens :
2.
3.
4.
5.
Editeur 4
Editeur 4
Editeur 11
Autoedite 2
Autoedite 18
XXXIII-E-2. morphMany▲
Voyons à présent la relation inverse avec morphMany. Par exemple pour les éditeurs, on doit ajouter la méthode dans le modèle :
Voici la signature de cette méthode :
Voyons les paramètres :
- C'est le nom du modèle de la table ciblée, dans notre cas Livre ;
- C'est le nom de la relation, dans notre cas livrable ;
- C'est le nom du champ qui récupère le type du modèle en liaison, et si on n'en donne pas il est nommé à partir du nom de la relation auquel on ajoute « _type » ; dans notre cas on a déjà livrable_type ;
- C'est le nom du champ qui récupère la clé étrangère, et si on n'en donne pas il est nommé à partir du nom de la relation auquel on ajoute « _id » ; dans notre cas on a déjà livrable_id ;
- C'est le nom de l'id de la table de départ, et dans notre cas on a « id », valeur par défaut.
Pour le fonctionnement c'est exactement comme un hasMany. On peut par exemple trouver les livres d'un éditeur avec ce code :
Dans mon cas j'obtiens ça :
Titre 1
Titre 2
Titre 26
XXXIII-F. Relation polymorphique de type n:n▲
Voyons à présent la situation la plus complexe, qui est apparue avec la version 4.1 de Laravel, avec les relations polymorphiques de type n:n. Ce que nous avons vu précédemment devrait vous aider à comprendre de quoi il s'agit. Regardez cette partie de la base d'exemple :
On a une table themes et deux tables : categories et periodes. On veut relier la table des thèmes aux deux autres tables avec ces possibilités :
- Une catégorie peut avoir plusieurs thèmes ;
- Une période peut avoir plusieurs thèmes ;
- Un thème peut avoir plusieurs catégories et plusieurs thèmes.
On est donc dans le cadre d'une relation de type n:n. On a vu précédemment qu'on résout ce genre de situation avec une table pivot. Ici cette table pivot est themables. Dans une relation classique n:n on a vu que cette table pivot contient les clés des deux tables en relation. Ici nous n'avons pas deux tables mais trois. On peut évidemment s'en sortir en créant deux tables pivot. La solution polymorphique est plus élégante. Il y a eu une discussion intéressante sur le sujet lors de sa soumission initiale. Voici comment c'est mis en œuvre :
- du côté de la table des thèmes on n'a pas de souci parce qu'on a une seule table, donc on peut mettre dans la table pivot la clé theme_id ;
- du côté des tables categories et periodes, on adopte ce qu'on a vu pour la relation polymorphique précédente avec deux champs : themable_id pour la clé soit d'une catégorie, soit d'une période, et themable_type pour le nom du modèle de la table en liaison : Categorie ou Periode.
Voici une visualisation de ce que nous allons mettre en place :
XXXIII-F-1. morphToMany▲
Du côté des deux tables on utilise la méthode morphToMany. Dans le modèle Categorie :
Et dans le modèle Periode :
Voici la signature de la méthode morphToMany :
public function morphToMany($related
,
$name
,
$table
=
null,
$foreignKey
=
null,
$otherKey
=
null,
$inverse
=
false)
Pas moins que six paramètres :
- C'est le nom du modèle de la table ciblée, dans notre cas Theme ;
- C'est le nom de la relation, dans notre cas themable ;
- C'est le nom de la table pivot construit à partir du deuxième paramètre auquel on ajoute « s », dans notre cas on a déjà themables ;
- C'est le nom du champ qui récupère la clé étrangère ; si on n'en donne pas il est nommé à partir du nom de la relation auquel on ajoute « _id », dans notre cas on a déjà themable_id ;
- C'est le nom du champ qui récupère le type du modèle en liaison, si on n'en donne pas il est nommé à partir du nom de la relation auquel on ajoute « _type », dans notre cas on a déjà themable_type ;
- ce dernier paramètre m'intrigue, je ne l'ai pas encore vraiment compris donc il méritera des tests complémentaires…
Je peux maintenant trouver tous les thèmes pour une catégorie :
Ou pour une période :
XXXIII-F-2. morphedByMany▲
Voyons maintenant la relation inverse morphedByMany. On se place cette fois du côté de la table des thèmes. On doit déclarer une méthode par table reliée :
La méthode morphedByMany a cette signature :
public function morphedByMany($related
,
$name
,
$table
=
null,
$foreignKey
=
null,
$otherKey
=
null)
Je ne commente pas ces paramètres qui sont les mêmes que ceux de la méthode morphToMany. On peut alors trouver par exemple toutes les catégories pour un thème :
Avec cette relation polymorphique s'achève ce tour d'horizon des relations traitées par Eloquent. Il existe aussi la méthode morphOne pour les relations de type 1:1 mais franchement c'est anecdotique étant donné la rareté de la chose ; elle n'est d'ailleurs même pas citée dans la documentation.
J'aborderai dans un prochain chapitre la gestion de tout ça. En effet quand on crée des relations, ça a évidemment une conséquence sur la manipulation des enregistrements dans les tables et ce n'est pas toujours rose .