Laravel 4 : chapitre 15 : Les bases de données 3/3
Maintenant que nous savons mettre en place la structure d’une base de données et intervenir sur les enregistrements que nous reste-t-il à voir ? Et bien Laravel nous propose un ORM vraiment efficace nommé Eloquent. C’est quoi un ORM ? C’est un « Object Relational Mapper ». Mapper parce qu’on va « mapper » des objets PHP sur les tables et les colonnes d’une base. Relational parce qu’on va aussi pouvoir gérer les relations de notre base. Mais tout cela va devenir plus clair avec des exemples. Vous n’avez pas besoin d’un ORM pour gérer une base mais vous le ferez avec beaucoup plus de facilité avec lui
Je pars du principe que vous avez une base nommée laravel telle que décrite lors du chapitre précédent avec deux tables : auteurs et livres. Vous avez aussi correctement configuré le fichier app/config/database.php pour accéder à la base.
Créer un modèle avec Eloquent
Voyons comment créer un modèle avec Eloquent. Par exemple pour notre table auteurs. Créez un fichier avec ce code et enregistrez-le dans app/models/Auteur.php :
<?php class Auteur extends Eloquent {}
Et voilà vous avez créé un modèle et vous pouvez maintenant l’utiliser . Bon il a quand même fallu prendre quelques précautions, j’ai nommé la classe Auteur, autrement dit le nom de la table avec le « s » en moins. En effet la table peut contenir plusieurs auteurs mais la classe s’adresse à un auteur à chaque fois. D’autre part il est impératif que la table contienne une clé nommée primaire « id », ce qui est notre cas.
Extraire des données
Il ne nous reste plus qu’à utiliser notre nouvelle classe. Dans une route entrez ce code :
$auteur = Auteur::find(1); var_dump($auteur);
On demande l’enregistrement avec l’index 1. Voyons ce que nous avons en retour :
object(Auteur)[118] protected 'connection' => null protected 'table' => null protected 'primaryKey' => string 'id' (length=2) protected 'perPage' => int 15 public 'incrementing' => boolean true public 'timestamps' => boolean true protected 'attributes' => array (size=4) 'id' => int 1 'nom' => string 'Flaubert' (length=8) 'prenom' => string 'Gustave' (length=7) 'naissance' => string '1821-12-12' (length=10) protected 'original' => array (size=4) 'id' => int 1 'nom' => string 'Flaubert' (length=8) 'prenom' => string 'Gustave' (length=7) 'naissance' => string '1821-12-12' (length=10) protected 'relations' => array (size=0) empty protected 'hidden' => array (size=0) empty protected 'fillable' => array (size=0) empty protected 'guarded' => array (size=0) empty protected 'dates' => array (size=0) empty public 'exists' => boolean true
C’est maintenant que nous comprenons mieux la notion de mappage. Nous obtenons un objet que nous pouvons exploiter pour extraire des informations. Ce qui est bien pratique aussi c’est que tout ce que nous avons vu au chapitre précédent concernant la gestion des enregistrements reste valable. Ainsi nous pouvons écrire :
$auteur = Auteur::where('nom', '=', 'Saulers')->get(); var_dump($auteur[0]->nom);
Ce qui donne :
string 'Saulers' (length=7)
Eloquent nous renvoie une collection qui implémente l’interface IteratorAggregate. Nous pouvons donc agir comme si nous avions un tableau en retour. En effet notre requête est susceptible de renvoyer plusieurs lignes, contrairement au cas précédent où on appelait juste une ligne en précisant son index. Il devient alors facile de traiter les lignes lorsqu’elles nous arrivent sous cette forme :
$auteurs = Auteur::where('nom', '>', 'q')->get(); foreach ($auteurs as $auteur) var_dump($auteur->nom);
Ce qui donne :
string 'Raspail' (length=7) string 'Sibran' (length=6) string 'Saulers' (length=7)
Insérer des données
Nous allons voir qu’il est aussi très facile d’insérer de nouvelles données. Mais auparavant je dois vous donner encore une petite indication. Par défaut Eloquent gère deux colonnes dans chaque table de type DateTime nommées respectivement updated_at et created_at. Ces colonnes sont automatiquement mises à jour à chaque création ou modification. Mais vous n’êtes pas obligé de les avoir. Si ces informations ne vous intéressent pas il suffit de compléter le modèle ainsi :
<?php class Auteur extends Eloquent { public $timestamps = false; }
C’est ce que nous allons faire dans notre cas parce que nous n’avons pas prévu ces colonnes dans nos table. Sachez juste que cette possibilité existe et noue l’utiliserons dans un article ultérieur sur la création d’un blog. Maintenant voilà le code pour faire une insertion :
$auteur = new Auteur; $auteur->nom = 'Frain'; $auteur->prenom = 'Irène'; $auteur->naissance = '1950-06-22'; $auteur->save();
On vérifie dans la base :
Apparemment tout se passe bien. Vous pouvez aussi passer un tableau au constructeur :
$auteur = new Auteur(array( 'nom' => 'Frain', 'prenom' => 'Irène', 'naissance' => '1950-06-22' )); $auteur->save();
Modifier et supprimer des données
Pour modifier une donnée existante la syntaxe est tout aussi simple, voici un exemple :
$auteur = Auteur::find(8); $auteur->prenom = 'Claire'; $auteur->save();
Pour supprimer des données c’est tout aussi simple :
$auteur = Auteur::find(8); $auteur->delete();
Les relations
Par définition dans une base de données relationnelles nous avons des… relations. Celles-ci peuvent être explicites dans la base ou implicites (comme c’est le cas dans notre exemple où on a juste introduit une ligne dans la table des livres pour préciser l’id de l’auteur mais sans demander à MySQL de vérifier l’intégrité de cette relation. On aurait pu le faire en précisant que nous utilisons le moteur InnoDB et en déclarant cette ligne comme clé étrangère).
Eloquent a une façon très élégante de gérer les relations. Nous allons prendre comme exemple celle qui existe entre nos deux tables. Mais auparavant il nous faut créer aussi un modèle pour la table des livres (app/models/Livre.php) :
class Livre extends Eloquent { public $timestamps = false; }
Il existe 3 sortes de relations : « un vers un », « un vers plusieurs » et « plusieurs vers plusieurs ». Notre cas est le plus classique avec une relation « un vers plusieurs », en effet un auteur peut écrire plusieurs livres et on admet qu’un livre n’est écrit que par un auteur (bon on oublie les co-auteurs ).
Pour que Eloquent reconnaisse automatiquement la clé étrangère dans une table il faut qu’elle réponde à une certaine syntaxe : le nom de la table suivi d’un soulignement puis de « id ». Manque de chance j’ai fait juste l’inverse dans la table des livres. On va corriger ça :
Cette norme n’est pas une contrainte absolue parce que d’une part on peut préciser le nom de la clé étrangère comme paramètre des méthodes relationnelles, d’autre part elle n’est pas toujours respectée par Eloquent (par exemple pour belongsTo c’est le nom de la méthode qui sert de référence). Pour avoir une vue d’ensemble du sujet référez vous au chapitre 33 de ce blog.
Maintenant nous pouvons utiliser toute la puissance et l’élégance d’Eloquent. Nous modifions la classe Auteur pour déclarer la relation :
class Auteur extends Eloquent { public $timestamps = false; public function livres() { return $this->hasMany('Livre'); } }
Et maintenant ça devient facile :
$livres = Auteur::find(2)->livres; foreach($livres as $livre) var_dump($livre->titre);
Résultat :
string 'Secouons le cocotier' (length=20) string 'Les Royaumes de Borée' (length=22)
On peut mettre en œuvre toutes les possibilités de manipulation de données. Par exemple si on ne veut que le premier livre :
$livre = Auteur::find(2)->livres()->first(); var_dump($livre->titre);
Résultat :
string 'Secouons le cocotier' (length=20)
Inverse d’une relation
Considérons à nouveau notre relation en la regardant dans l’autre sens. Nous avons dit qu’un auteur peut écrire plusieurs livres. Nous avons traduit cela en précisant dans la classe Auteur ce code :
public function livres() { return $this->hasMany('Livre'); }
On pourrait aussi dire qu’un livre appartient à un auteur. Cette fois au lieu de renseigner la relation dans la classe Auteur on le fait dans la classe Livre :
<?php class Livre extends Eloquent { public $timestamps = false; public function auteur() { return $this->belongsTo('Auteur'); } }
Testons cela :
$auteur = Livre::find(1)->auteur; var_dump($auteur->nom);
Résultat :
string 'Raspail' (length=7)
Chargement dynamique
Avec la relation précédente considérez ce code :
foreach(Livre::all() as $livre) echo '"'.$livre->titre.'" a été écrit par '.$livre->auteur->nom.'<br>';
Et son résultat :
"Secouons le cocotier" a été écrit par Raspail "Pêcheurs de Lune" a été écrit par Avril "Les Royaumes de Borée" a été écrit par Raspail "Le Salon des artistes" a été écrit par July
Le résultat correspond à notre attente mais… nous ne sommes pas très efficace parce que nous multiplions les requêtes. Il y en a une pour pour récupérer tous les livres et ensuite une pour chaque chaque livre pour trouver l’auteur. Imaginez si nous avons des milliers de livres ! Heureusement il existe une solution :
foreach(Livre::with('auteur')->get() as $livre) echo '"'.$livre->titre.'" a été écrit par '.$livre->auteur->nom.'<br>';
Cette fois on a une seule requête dans la boucle qui prend la forme :
SELECT * FROM auteurs WHERE id IN (2,3,7);
Il y aurait encore beaucoup à dire sur Eloquent. je vous renvoie encore une fois à la documentation ou sa version Française. Vous pouvez également consulter le chapitre 33 de ce blog qui est consacré à ce sujet.
13 commentaires
christoff
Je me pose la question quand faut-il utiliser le Query builder ou l’ORM Eloquent ?
Chacun a des avantages et des inconvénients
Avec l’ORM Eloquent, on a un code plus compacte et plus lisible, il n’y a pas besoin de décrire une méthode dans le modèle qui est presque vide.
Par contre, il faut bien respecter certaines conventions de nommage : Singulier ou pluriel selon le nom des tables ou du modèle, nom des champs qui contiennent des id.
Personnellement j’utilise l’ORM Eloquent pour des requêtes simples.
Par contre je préfère utiliser le Query Builder, pour des requêtes plus complexes. Le code est peut-être plus long, mais je passe moins de temps à trouver la solution.
Par exemple : des jointure de tables + sélection d’élément dans 2 tables différentes + ORDERBY sur une donnée. Je n’ai pas trouvé comment le faire avec Eloquent car j’obtenais des erreurs malgré un code scrupuleusement conforme à la documentation officielle. Autre exemple : trouver l’enregistrement précédant ou l’enregistrement suivant d’un champ qui est ordonné. Je n’ai pas pu le faire avec l’ORM.
Dans ces deux cas j’ai été obligé de créer une méthode dans un des modèles. Pour que la méthode puisse être appelée depuis le controller, j’ai été obligé de lui donner l’attribut « public static function(){} ». Est-ce que c’est la bonne façon de faire ?
Ce qui est également très déroutant pour un novice comme moi, c’est que les résultats peuvent avoir des formats très différents : des tableaux indexés ou associatifs, des objets ORM, une valeur unique. J’ai l’habitude de faire d’abord un return sur ma requête pour voir sous quel format se présentent les données dans le navigateur. C’est seulement après que je return les résultats sur une vue blade.
bestmomo
Clairement Eloquent n’est pas fait pour les opérations complexes. On passe effectivement plus de temps à trouver comment faire, quand on y arrive ! Donc il ne faut pas hésiter d’utiliser le Query builder dès que ça ne devient plus évident avec Eloquent.
Le fait de créer une méthode statique te permet de faire un appel statique. C’est plus pratique.
C’est vrai que les formats retournés sont un peu déroutants au départ 😉
ChDUP
Hello
J’ai une table « matchs » avec ces entrées : id / team_id_1 / team_id_2
Du coup, les relations avec la table « teams » ne peut pas fonctionner à cause des intitulés _1 et _2.
Chaque match a 2 teams, que j’ai besoin d’identifier comme team_1 et team_2.
Chaque team est présente dans plusieurs matchs, parfois en team_1 parfois en team_2
Comment gérer ce cas au mieux ?
merci
ChDUP
*ne peuvent
pardon
gil
en surchargeant les foreign keys
(‘Team’, ‘team_id_1); ou (‘Match’, ‘team_id_1’)
D’ailleurs bestmomo aurait pu conserver son champ id_auteur et écrire
return $this->hasMany(‘Livre’, ‘id_auteur’);
ChDUP
Entre temps, j’ai procédé comme suit:
mes champs dans la base s’appellent team1_id et team2_id
et dans ma class Match :
public function team1()
{
return $this->belongsTo(‘Team’);
}
ensuite dans mes vues : {{$match->team1->name}}
Ça fonctionne bien et ça me parait pas trop moche ?
bestmomo
Ça fonctionne bien parce qu’avec la méthode belongsTo la clé étrangère est construite à partir du nom de la méthode appelante. Ce qui me semble personnellement n’être pas logique mais qui dans ton cas résout ton problème. Il me semble plus sûr de transmettre le nom de la clé en paramètre pour te prémunir d’une modification éventuelle du framework dans le futur.
ChDUP
Je vois
J’ai rectifié donc simplement en rajoutant le parametre dans belongsTo
return $this->belongsTo(‘Team’,’team1_id’);
si je ne trompe pas.
(bizarre, je n’avais pas de lien pour répondre à ton commentaire, bestmomo), Il doit y avoir un niveau max de hierarchisation.
bestmomo
J’avais raté ce commentaire, désolé, mais gil a parfaitement répondu. Pour info j’ai posté récemment un article complet sur ce sujet.
gil
Mais c’est le chapitre 33, je n’en suis qu’au 15 😀
En tout cas, merci pour ces articles; J’ai déjà survolé la doc laravel, mais la mise en situation dans ces articles permet beaucoup mieux de fixer les idées que la doc d’origine, souvent un peu « light ». Je m’en suis rendu compte durant mes propres expérimentations. Merci !
maxgosset
bonjour , me revoilà de nouveau avec un nouveau probleme
j’ai en fait une page modification sur laquelle on choisit le contact à modifier avec par la suite un formulaire qui indique les données deja inscrites dans la table
jusque là tout va bien , tout s’affiche parfaitement
mais une fois sur la page de verification du nouveau formulaire
il me met une erreur « Creating default object from empty value » et je ne comprend vraiment pas
voici mon code de la page vérification
$id = Input::get(‘id’);
$contact =contact::find($id);
$contact->nom =Input::get(‘nom’);
$contact->save();
si quelqu’un pouvait m’aider ce serait vraiment cool
merci
bestmomo
Salut !
Il te manquerait pas la majuscule à « contact » sur cette ligne :
$contact = Contact::find($id);
leir
Un grand merci pour ce tuto qui tombe à pique dans mon apprentissage de Laravel. Je me demandais justement si j’allais devoir créer une classe complète en déclarant tous les attributs pour chacune de mes tables, j’ai ma réponse ^^. Merci encore !