Laravel 4

Laravel 4 : chapitre 33 : Les relations avec Eloquent 1/2

Eloquent est un ORM é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 article.

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 :

img66

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 :

img92

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 :

img68

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 :

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 Tongue Out. 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…

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 :

img69

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 raison de performances si on a des données volumineuses pas souvent accédées, 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 Laughing) et se poser les bonnes questions. On a donc deux cas.

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 :

img70

Pour traduire cela de façon concrète on crée dans le modèle Editeur une méthode :

public function contact()
{
	return $this->hasOne('Contact');
}

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 :

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 :

public function contact()
{
	return $this->hasOne('Contact', 'editeur');
}

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 !

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 :

img71

Pour traduire cela de façon concrète on crée dans le modèle Editeur une méthode :

public function editeur()
{
    return $this->belongsTo('Editeur');
}

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

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 :

public function editeur()
{
    return $this->belongsTo('Editeur', 'editeur');
}

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 encore particulièrement élégante et lisible !

La relation 1:n

Cette relation est de loin la plus courante ! Dans la base d’exemple on trouve cette relation par exemple entre les pays et les villes :

img72

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.

hasMany

Commençons par nous situer dans la table des pays. Je me dis « j’ai beaucoup de villes » :

img73

Pour traduire cela de façon concrète on crée dans le modèle Pays une méthode :

public function villes()
{
    return $this->hasMany('Ville');
}

Voyons cette méthode dans la classe Model :

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 :

public function villes()
{
    return $this->hasMany('Ville', 'pays');
}

Le troisième paramètre sert en 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 autre méthodes ci-dessus.

Je peux maintenant écrire cela :

Pays::find(4)->villes->each(function($ville)
{
    echo $ville->nom, '<br>';
});

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

belongsTo

Voyons maintenant la relation à partir de la table des villes :

img74

Là je me dis « 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.

hasManyThrough

Voyons maintenant une extension de hasMany avec hasManyThrough. Regardez cette partie de la base d’exemple :

img75

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 :

img76

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 :

public function auteurs()
{
	return $this->hasManyThrough('Auteur', 'Ville');
}

Voyons cette méthode dans la classe Model :

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 :

  1. C’est le modèle de destination, dans notre cas Auteur
  2. C’est le modèle intermédiaire, dans notre cas Ville
  3. 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
  4. 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 :

Pays::find(4)->auteurs->each(function($auteur)
{
	echo $auteur->nom, '<br>';
});

J’obtiens ce résultat dans mon cas :

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. Elégant non ?

La relation n:n

La relation n:n est la plus complexe des 3. Voyons un exemple dans la base pour comprendre de quoi il s’agit :

img77

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

  1. la clé auteur_id qui référence un enregistrement de la table des auteurs
  2. la clé livre_id qui référence un enregistrement de la table des livres

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 Tongue Out) et réciproquement du côté de la table des livres je me dis « j’appartiens à plusieurs auteurs » :

img78

Pour concrétiser ça on crée une méthode livres dans le modèle Auteur :

public function livres()
{
	return $this->belongsToMany('Livre');
}

Et évidemment la réciproque auteurs dans le modèle Livre :

public function auteurs()
{
	return $this->belongsToMany('Auteur');
}

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 :

  1. là on met le nom du modèle de la table visée
  2. 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é)
  3. là on met le nom de la clé étrangère de la table de départ s’il n’est pas conventionnel
  4. 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 :

Auteur::find(4)->livres->each(function($livre)
{
	echo $livre->titre, '<br>';
});

Dans mon cas j’obtiens :

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 :

Livre::find(4)->auteurs->each(function($auteur)
{
	echo $auteur->nom, '<br>';
});

Ici j’obtiens :

Auteur 1
Auteur 13

Relation polymorphique de type 1:n

Maintenant que vous êtes chaud on peut aborder un sujet un peu plus délicat. Regardez cette partie de la base d’exemple :

img85

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 :

  1. un éditeur peut correspondre à plusieurs livres mais un livre ne peut appartenir qu’à un éditeur
  2. un auto-éditeur peut correspondre à plusieurs livres mais un livre ne peut appartenir qu’à un auto-éditeur
  3. un livre appartient soit à un éditeur, soit à un auto-éditeur, mais pas aux deux.

On se rend compte qu’on aurait du mal pour 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 on la même structure mais changent dans leur forme, d’où l’appelation 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 :

  1. le nom de la table en relation ou de son modèle
  2. l’id de l’enregistrement concerné

Regardez dans la table des livres, vous trouvez deux champs :

  1. livrable_id qui est chargé de contenir l’id de l’enregistrement lié
  2. 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 connait aussi l’id de l’enregistrement alors la relation est parfaitement connue. Voici une visualisation de la relation :

img86

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 :

public function livrable()
{
	return $this->morphTo();
}

Voici la signature de la méthode morphTo :

public function morphTo($name = null, $type = null, $id = null)

Voyons les paramètres :

  1. c’est le nom de la relation, si on en donne pas c’est le nom de la méthode qui est utilisé
  2. c’est le nom du champ qui récupère le type du modèle en liaison, si on 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
  3. c’est le nom du champ qui récupère la clé étrangère, si on 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 :

Livre::take(5)->get()->each(function($livre)
{
	echo $livre->livrable->nom, '<br>';
});

Je prends les 5 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 :

Editeur 4
Editeur 4
Editeur 11
Autoedite 2
Autoedite 18

morphMany

Voyons à présent la relation inverse avec morphMany. Par exemple pour les éditeurs on doit ajouter la méthode dans le modèle :

public function livres()
{
	return $this->morphMany('Livre', 'livrable');
}

Voici la signature de cette méthode :

public function morphMany($related, $name, $type = null, $id = null, $localKey = null)

Voyons les paramètres :

  1. c’est le nom du modèle de la table ciblée, dans notre cas Livre
  2. c’est le nom de la relation, dans notre cas livrable
  3. c’est le nom du champ qui récupère le type du modèle en liaison, si on 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
  4. c’est le nom du champ qui récupère la clé étrangère, si on 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
  5. c’est le nom de l’id de la table de départ, 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 :

Editeur::find(4)->livres->each(function($livre)
{
    echo $livre->titre, '<br>';
});

Dans mon cas j’obtiens ça :

Titre 1
Titre 2
Titre 26

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 :

img91

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 :

  1. une catégorie peut avoir plusieurs thèmes
  2. une période peut avoir plusieurs thèmes
  3. 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 2 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 oeuvre :

  • du côté de la table des thèmes on 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 :

img90

morphToMany

De côté des deux tables on utilise la méthode morphToMany. Dans le modèle Categorie :

public function themes()
{
	return $this->morphToMany('Theme', 'themable');
}

Et dans le modèle Periode :

public function themes()
{
	return $this->morphToMany('Theme', 'themable');
}

Voici la signature de la méthode morphToMany :

public function morphToMany($related, $name, $table = null, $foreignKey = null, $otherKey = null, $inverse = false)

Pas moins que 6 paramètres :

  1. c’est le nom du modèle de la table ciblée, dans notre cas Theme
  2. c’est le nom de la relation, dans notre cas themable
  3. 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
  4. c’est le nom du champ qui récupère la clé étrangère, si on 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
  5. c’est le nom du champ qui récupère le type du modèle en liaison, si on 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
  6. 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 :

Categorie::find(2)->themes->each(function($theme)
{
    echo $theme->nom, '<br>';
});

Ou pour une période :

Periode::find(2)->themes->each(function($theme)
{
    echo $theme->nom, '<br>';
});

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 :

public function categories()
{
	return $this->morphedByMany('Categorie', 'themable');
}

public function periodes()
{
	return $this->morphedByMany('Periode', 'themable');
}

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 la catégories pour un thème :

Theme::find(2)->categories->each(function($categorie)
{
    echo $categorie->nom, '<br>';
});

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 article 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 Laughing.

Print Friendly, PDF & Email

12 commentaires

  • gAb09

    Salut BestMomo

    Je crains avoir relevé 2 coquilles :

    Section HasOne 3e paragraphe
    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 « contact_id ». On a d’autres possibilités, regardez la méthode dans la classe Model : N’est ce pas plutôt editeur_id ?

    Section morphToMany paramètre 5
    c’est le nom du champ qui récupère le type du modèle en liaison, si on en donne pas il est nommé à partir du nom de la relation auquel on ajoute « _string », dans notre cas on a déjà themable_string N’est ce pas plutôt themable_type ?

    Excellent tuto auquel j’espère avoir fait honneur en montrant ma lecture attentive… o_O

  • zazeur

    Bonjour,

    Mon premier commentaire par ici donc je tenais tout d’abord à te féliciter et te remercier pour ce tutoriel qui est d’une grande aide.

    Je suis actuellement sur un petit projet et j’essaye d’appliquer Eloquent à mes tables mais j’ai une petite question sur deux points.

    J’ai uploadé une imagepar ici.

    Pour chaque rapport il peut y avoir plusieurs taches concernées. Du coup je dois pour tache je dois mettre « task belongsToMany report » si je me trompe pas. Mais dois-je mettre la même chose dans l’autre sens (report belongsToMany task) ? La liaison n:n se fait automatiquement via la table intermédiaire ?
    On ne se sert pas d’un modèle intermédiaire du coup ou il faut aussi faire les liaisons avec la table intermédiaire ?

    Mon second soucis se pause sur la table des tâches. Je dispose d’un champ, extend_task_id, qui se peut être l’id d’une autre tâche (qu’on étend).
    Il faut bien spécifier une clef étrangère pour extend_task_id sur tasks.id ?
    Faut-il aussi mentionner une relation Eloquent sur la table elle-même ? (j’aurai dis non)

    Merci d’avance pour ta réponse !

    • bestmomo

      Salut,
      Le lien de ton image n’est pas passé mais je te fais une réponse générale. Dans une relation n:n tu n’as pas besoin d’un modèle intermédiaire. Tu prévoies les méthodes belongsToMany qui te seront utiles, mais ce n’est pas gênant d’en avoir une qui sert jamais.

      Pour ta question sur la clé étrangère c’est pas facile sans voir tes tables mais d’une façon général tu prévoies les relations Eloquent qui t’intéressent et qui te sont vraiment utiles. Il m’arrive des fois d’en utiliser pour un contexte qui n’était pas celui de départ. A partir du moment où tu as une clé étrangère dans une table pour la rendre dépendante d’une autre table il y a forcément une méthode d’Eloquent qui te permet de faire ce que tu as envie de faire. L’idéal est de tomber sur un cas de base bien identifié mais des fois ce n’est pas aussi évident. Alors on doit un peu pervertir les méthodes 🙂

      En espérant que ma réponse t’est utile…

  • weip

    Je design une base donnée et je me demandais, lorsqu’on crée nos tables dans les migrations, est-ce qu’on doit toujours assigner nos foreign keys ou, par convention, Eloquent est capable de faire le lien par lui même?

    Deuxième point, lorsque j’ai une énumération, par exemple une catégorie pour un produit, est-ce que je fais référence à une table externe avec une foreign key, ou, comme dans certains cas que j’ai vu, c’est une colonne de type Enum, et c’est le nom de la catégorie qui est écrit directement dans la colonne de la table produit. C’est la première fois que je vois ça et ça semble aussi performant qu’une foreign key.

    • bestmomo

      Bonjour,

      Il faut bien différencier le traitement côté Eloquent et côté MySQL. Quand on déclare une clé étrangère c’est exclusivement pour MySQL, ça permet d’avoir un contrôle de l’intégrité référentielle. Un avantage peut résider dans les modifications ou suppressions en cascade mais il n’est pas trop conseillé de s’en servir à moins de bien maîtriser la situation.

      Si on code correctement avec Eloquent en ordonnant bien les modifications dans la base on ne devrait jamais tomber sur un problème d’intégrité.

      Le type ENUM est parfait lorsqu’on a peu de valeurs et qu’elles ne doivent pas changer.

  • bestmomo

    Salut,
    En fait la bonne syntaxe est livrable_type, j’avais laissé trainer des livrables_string que j’ai corrigés. C’est vrai que livrable est pas forcément explicite mais je n’ai pas trouvé mieux 🙂 .
    Les paramètres 2 et 3 de morphTo étaient bien inversés. Pour l’autre l’inversion était au niveau de paramètres 3 et 4.
    Merci pour la lecture attentive 😉

  • thetis

    Dans le paragraphe Relation polymorphique de type 1:n.
    Au niveau du schéma, table « livres », le champ livrable_type ne devrait pas s’appeler livrable_string ? Ce serait plus cohérent avec le reste de la démo (même si on peut lui donner le nom de son choix) ? Et pour la clarté du propos, pourquoi ne pas avoir choisi edite_par_id et edite_par_string avec la function « edite_par » ? Pour ma part « livrable » ne me paraît pas très « causant »… mais les goûts et les couleurs 😉
    Dans les paramètres n’y aurait-il pas inversion des param 2 et 3 ? Idem pour les params 4 & 5 de la relation inverse ? Super tuto quand même !
    Très bon tuto quand même.

Laisser un commentaire