Les données
Pour effectuer des tests, nous aurons besoin de données. Dans le précédent chapitre on a construit avec le concepteur de schéma 4 tables reliées par des relations. On a ainsi récupéré des migrations et des modèles. On a aussi utilisé les migrations pour créer les tables dans la base de données. On va avoir besoin de tout ça pour ce chapitre. Pour rappel voici ce qu'on a dans la base : Au niveau des relations :- une relation 1:n entre les éditeurs et les livres
- une relation n:n entre les auteurs et les livres
<?php $factory->define(App\Editeur::class, function (Faker\Generator $faker) { return [ 'nom' => $faker->name, ]; }); $factory->define(App\Auteur::class, function (Faker\Generator $faker) { return [ 'nom' => $faker->name, ]; }); $factory->define(App\Livre::class, function (Faker\Generator $faker) { return [ 'titre' => $faker->sentence(rand(2, 3)), 'description' => $faker->text, 'editeur_id' => $faker->numberBetween(1, 40), ]; });Ensuite on prévoit ce code dans DatabaseSeeder :
<?php use Illuminate\Database\Seeder; use Faker\Factory; class DatabaseSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { factory(App\Editeur::class, 40)->create(); factory(App\Auteur::class, 40)->create(); factory(App\Livre::class, 80)->create(); for ($i = 1; $i < 41; $i++) { $number = rand(2, 8); for ($j = 1; $j <= $number; $j++) { DB::table('auteur_livre')->insert([ 'livre_id' => rand(1, 40), 'auteur_id' => $i ]); } } } }Lancez ensuite la population :
php artisan db:seedOn aura ainsi 40 éditeurs et 40 auteurs avec des noms aléatoires. On aura aussi 80 livres affectés à des éditeurs et aussi des relations entre les livres et les auteurs. Donc de quoi effectuer tranquillement des requêtes.
N'oubliez pas de placer aussi les 3 modèles dans l'application.
Tinker
Artisan est plein de possibilités. Il y a en particulier un outil console, Tinker, qui permet d'effectuer des actions élémentaires. On appelle cela un REPL (Read-Eval-Print Loop). On peut entrer une expression, l'évaluer et on obtient le résultat. Pour démarrer Tinker c'est tout simple : Et maintenant il n'y a plus qu'à entrer des expressions, elles seront évaluée dans le contexte de Laravel. Voici un exemple avec notre application : Tinker est bien pratique pour faire des actions simples de ce genre. Vous pouvez donc l'utiliser s'il vous convient, en particulier pour les exemples de ce chapitre.Les sélections simples
L'action la plus fréquente est sans doute le fait d'aller chercher des informations dans la base, on parle de sélection. On a rencontré de nombreux exemples dans les chapitres précédents.Liste
Commençons par des choses simples, on veut tous les éditeurs. Avec Eloquent c'est facile : Les réponses d'Eloquent sont toujours des collections (Illuminate\Database\Eloquent\Collection). Ce sont des objets bien plus puissants et pratiques que de simples tableaux. Pour vous donner une idée des possibilités regardez toutes les méthodes disponibles. Autrement dit vous avez la possibilité d'effectuer simplement des traitements puissants sur les données retournées par Eloquent.Enregistrement particulier
On peut retrouver un éditeur avec son identifiant :Lorsqu'un seul résultat est retourné on n'a pas une collection mais un seul modèle.
On peut aussi le retrouver par son nom : On peut aussi sélectionner les colonnes qu'on veut :Lignes distinctes
On peut aussi utiliser la méthode distinct pour avoir des lignes distinctes :Plusieurs conditions
On peut combiner des where :Encadrer des valeurs
On peut encadrer des valeurs avec whereBetween :Prendre des valeurs dans un tableau
On peut aussi prendre des valeurs dans un tableau avec whereIn :Aggrégations
On peut aussi compter, calculer...Les erreurs
Que se passe-t-il si on ne trouve pas un enregistrement avec find ou first ? Regardez ces exemples : Donc si vous voulez générer une erreur Illuminate\Database\Eloquent\ModelNotFoundException utilisez findOrFail ou firstOrFail.Les sélections avec plusieurs tables
Pour le moment on a vu des requêtes qui ne concernent qu'une seule table, ce qui n'est pas le plus répandu. Lorsque deux tables sont concernées on doit classiquement faire une jointure. Mais on a vu qu'Eloquent nous offre des possibilités bien plus pratiques avec les relations.Trouver les titres des livres pour un éditeur dont on a l'id
Par exemple pour trouver tous les livres de l'éditeur avec l'identifiant 11 : Sans Eloquent le Query Builder devrait recourir à une jointure :Remarquez que le Query Builder retourne lui aussi une collection. C'est une nouveauté bienvenue de la version 5.3.
Trouver les livres d'un auteur dont on connaît le nom
Maintenant cherchons les livres d'un auteur dont on connaît le nom. On s'en sort avec la méthode whereHas :Attention aux requêtes imbriquées !
Il faut être prudent dans certaines situations. Par exemple supposez que vous voulez avoir la liste des auteurs avec pour chaque nom d'auteur la liste de ses ouvrages. Vous pourriez écrire ce genre de code :$auteurs = App\Auteur::all(); foreach ($auteurs as $auteur) { echo '<h1>' . $auteur->nom . '</h1>'; foreach($auteur->livres as $livre) { echo $livre->titre, '<br>'; } }Tout se passe correctement à l'affichage :
Ça fonctionne très bien mais... si vous regardez vos requêtes vous allez être effrayé !
Dans mon cas j'en trouve 41 ! Tout simplement parce que pour chaque auteur vous lancez une requête pour trouver ses livres. Dans ce genre de situation il faut absolument utiliser le chargement lié (eager loading), c'est-à-dire demander à Eloquent de charger la table livres avec la méthode with :$auteurs = App\Auteur::with('livres')->get(); foreach ($auteurs as $auteur) { echo '<h1>' . $auteur->nom . '</h1>'; foreach($auteur->livres as $livre) { echo $livre->titre, '<br>'; } }Le changement peut paraître minime mais on n'a plus que 2 requêtes maintenant :
select * from `auteurs` 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` in ('1', '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')Comment faire la même chose avec le Query Builder ? Ce n'est pas si simple, voici une solution avec utilisation d'une expression brute :
$results = DB::table('auteurs') ->select('nom', DB::raw('group_concat(titre) as titres')) ->groupBy('nom') ->join('auteur_livre', 'auteurs.id', '=', 'auteur_livre.auteur_id') ->join('livres', 'livres.id', '=', 'auteur_livre.livre_id') ->get(); foreach ($results as $result) { echo '<h1>' . $result->nom . '</h1>'; $titres = explode(',', $result->titres); foreach($titres as $titre) { echo $titre, '<br>'; } }Maintenant on n'a plus qu'une seule requête :
select `nom`, group_concat(titre) as titres from `auteurs` inner join `auteur_livre` on `auteurs`.`id` = `auteur_livre`.`auteur_id` inner join `livres` on `livres`.`id` = `auteur_livre`.`livre_id` group by `nom`
Si vous utilisez une expression brute dans une requête (par exemple ci-dessus avec DB::raw) celle-ci n'est pas immunisée contre les injections SQL, vous devez donc prendre vous-même des mesures de sécurité.
Le chargement lié peut même utiliser plusieurs relations successives. Regardez cet exemple : Ici on charge les livres et les auteurs liés en même temps que les éditeurs en une seule fois. On s'en sort avec 3 requêtes :select * from `editeurs` limit 2 select * from `livres` where `livres`.`editeur_id` in ('1', '2') select `auteurs`.*, `auteur_livre`.`livre_id` as `pivot_livre_id`, `auteur_livre`.`auteur_id` as `pivot_auteur_id` from `auteurs` inner join `auteur_livre` on `auteurs`.`id` = `auteur_livre`.`auteur_id` where `auteur_livre`.`livre_id` in ('21')
Remarquez l'utilisation de la méthode take pour limiter le nombre de résultats.
Vous n'arriverez pas toujours à réaliser ce que vous désirez avec seulement Eloquent, il vous faudra alors utiliser le Query Builder.Insérer des enregistrements
Méthode "save"
Une façon simple d'ajouter un enregistrement est d'utiliser la méthode save : On crée une instance du modèle, on renseigne les attribut, on enregistre dans la base avec save. Si tout se passe bien il est retourné true. Avec cette méthode save on peut aussi ajouter un enregistrement à travers une relation pour renseigner automatiquement la clé étrangère : On voit que la colonne editeur_id a été renseignée.On peut d'ailleurs mettre un tableau de modèles comme argument de la méthode save pour ajouter plusieurs enregistrements d'un coup.
Méthode "create"
Une autre façon de procéder est d'utiliser la méthode create : On transmet les attributs sous la forme d'un tableau.On voit qu'il est retourné une instance du modèle, ce qui est pratique si on veut par exemple connaître l'identifiant de l'enregistrement créé.
Toutefois cette façon de procéder constitue ce qu'on appelle un assignement de masse. Imaginez que le tableau soit constitué de la saisie d'un formulaire et qu'on transmette ainsi le paquet à la méthode. Qu'est-ce qui empêche un utilisateur mal intentionné d'ajouter un attribut ? Pour éviter ça on doit définir précisément les colonnes qui sont autorisées à être ainsi renseignées avec la méthode $fillable dans le modèle. Regardez cet exemple : Apparemment la colonne editeur_id n'est pas renseignée alors qu'on l'a bien prévue dans le tableau. Regardez dans le modèle App\Auteur :protected $fillable = ['titre', 'description'];On a prévu seulement titre et description, du coup le reste ne passe pas et on a un erreur à la création.
Il y a des cas ou aucune erreur n'est déclenchée et vous ne vous apercevez de rien dans l'immédiat !
Si vous modifiez ainsi la valeur de la propriété :protected $fillable = ['titre', 'description', 'editeur_id'];Cette fois ça fonctionne :
Il existe la propriété $guarded qui est exactement l'inverse de $fillable.
A vous de voir quelles colonnes peuvent présenter des risques de sécurité de ce genre ! Avec cette méthode create on peut aussi ajouter un enregistrement à travers une relation pour renseigner automatiquement la clé étrangère :Création si un enregistrement n'existe pas
On n'est pas toujours sûr de ce qui se trouve dans la base. Il arrive des fois où on aimerait créer un enregistrement si celui-ci n'existe pas déjà.
On peut évidemment commencer par aller vérifier sa présence et le créer au besoin. Mais Laravel nous offre une possibilité beaucoup plus simple avec les méthodes firstOrNew et firstOrCreate : A partir de ce scénario je vous laisse deviner la différence entre les deux méthodes.Agir sur la table pivot
Attacher
Pour ajouter un enregistrement dans la table pivot on dispose de la méthode attach. Par exemple mon auteur 1 est relié à 4 livres : Je veux le relier à un autre livre, voilà comment faire : On trouve bien l'enregistrement dans la table pivot :Détacher
A l'inverse on peut détacher avec la méthode detach :Synchroniser
Parfois on veut mettre à jour globalement les attachements, on le fait avec la méthode sync : On attache les nouveaux, on détache ce que l'on ne veut plus.On peut synchroniser sans détacher avec la méthode syncWithoutDetaching.
Mettre à jour des enregistrements
Méthode "save"
On peut utiliser la méthode save pour aussi mettre à jour des enregistrements :Méthode "update"
On peut aussi utiliser la méthode update : L'avantage de cette méthode est qu'on peut mettre à jour plusieurs enregistrements : On voit ici qu'on a modifié 5 enregistrements en changeant l'éditeur de ces livres.Supprimer des enregistrements
Méthode "delete"
On peut utiliser la méthode delete pour supprimer un enregistrement. Etant donné que j'ai prévu de ne pas avoir de suppression en cascade il devient plus difficile de donner un exemple. Par exemple pour supprimer un auteur il faut commencer par détacher tous ses livres : Ce chapitre est loin d'épuiser le sujet pour Eloquent et le Query Builder. Vous pourrez trouver tous les compléments utiles sur cette page de la documentation pour le Query Builder et sur cette page et celle-ci pour Eloquent.En résumé
- Eloquent permet de faire beaucoup de manipulations sur les tables et est à l'aise avec les relations.
- Le Query Builder est le compagnon parfait pour Eloquent.
- Il faut se méfier des requêtes imbriquées et utiliser le chargement lié (eager loading) pour limiter le nombre de requêtes générées.
- Parfois on doit utiliser des expressions brutes dans les requêtes mais il faut alors penser à se protéger des injections SQL.
Par bestmomo
Nombre de commentaires : 2