IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Tutoriel pour apprendre à utiliser le framework Laravel 5.3

Les données


précédentsommairesuivant

X. Manipuler les données

Dans les précédents chapitres, on a rencontré de nombreux exemples d'utilisation d'Eloquent pour manipuler des données. On a vu que sa capacité d'abstraction permet de réaliser facilement de nombreuses tâches sur les tables.

Avec Eloquent chaque table est représentée par un modèle qui sert à interagir avec elle. On peut ainsi aller chercher des données, en insérer, les modifier, les supprimer…

Il y a aussi dans Laravel un Query Builder qui est une puissante interface pour effectuer des requêtes sur les bases de données. Comme Eloquent et le Query Builder sont intimement liés, on a parfois du mal à les distinguer. En gros Eloquent utilise le Query Builder pour constituer et exécuter les requêtes.

Dans ce chapitre on va faire un peu le point de ce qu'on a vu et on va évoquer d'autres possibilités.

X-A. 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 quatre 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 :

Image non disponible

Au niveau des relations :

  • une relation 1:n entre les éditeurs et les livres ;
  • une relation n:n entre les auteurs et les livres.

La structure est en place, mais on va avoir également besoin de données. On va encore utiliser la bibliothèque Faker qui est chargée par défaut dans Laravel.

Modifiez ainsi le code du fichier des fabriques (models factories) :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
<?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 :

 
Sélectionnez
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.
<?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 :

 
Sélectionnez
php artisan db:seed

On 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 trois modèles dans l'application.

X-B. 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 :

Image non disponible

Et maintenant il n'y a plus qu'à entrer des expressions, elles seront évaluées dans le contexte de Laravel. Voici un exemple avec notre application :

Image non disponible

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.

X-C. 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.

X-C-1. Liste

Commençons par des choses simples, on veut tous les éditeurs. Avec Eloquent c'est facile :

Image non disponible

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.

X-C-2. Enregistrement particulier

On peut retrouver un éditeur avec son identifiant :

Image non disponible

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 :

Image non disponible

On peut aussi sélectionner les colonnes qu'on veut :

Image non disponible

X-C-3. Lignes distinctes

On peut aussi utiliser la méthode distinct pour avoir des lignes distinctes :

Image non disponible

X-C-4. Plusieurs conditions

On peut combiner des where :

Image non disponible

X-C-5. Encadrer des valeurs

On peut encadrer des valeurs avec whereBetween :

Image non disponible

X-C-6. Prendre des valeurs dans un tableau

On peut aussi prendre des valeurs dans un tableau avec whereIn :

Image non disponible

X-C-7. Agrégations

On peut aussi compter, calculer…

Image non disponible

X-C-8. Les erreurs

Que se passe-t-il si on ne trouve pas un enregistrement avec find ou first ? Regardez ces exemples :

Image non disponible

Donc, si vous voulez générer une erreur Illuminate\Database\Eloquent\ModelNotFoundException utilisez findOrFail ou firstOrFail.

X-D. 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.

X-D-1. 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 :

Image non disponible

Sans Eloquent le Query Builder devrait recourir à une jointure :

Image non disponible

Remarquez que le Query Builder retourne lui aussi une collection. C'est une nouveauté bienvenue de la version 5.3.

X-D-2. 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 :

Image non disponible

X-E. 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 :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
$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 :

Image non disponible

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

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
$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 deux requêtes maintenant :

 
Sélectionnez
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 :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
$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 :

 
Sélectionnez
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 :

Image non disponible

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 trois requêtes :

 
Sélectionnez
1.
2.
3.
4.
5.
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.

X-F. Insérer des enregistrements

X-F-1. Méthode « save »

Une façon simple d'ajouter un enregistrement est d'utiliser la méthode save :

Image non disponible

On crée une instance du modèle, on renseigne les attributs, 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 :

Image non disponible

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.

X-F-2. Méthode « create »

Une autre façon de procéder est d'utiliser la méthode create :

Image non disponible

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 :

Image non disponible

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 :

 
Sélectionnez
protected $fillable = ['titre', 'description'];

On a prévu seulement titre et description, du coup le reste ne passe pas et on a une erreur à la création.

Il y a des cas où 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é :

 
Sélectionnez
protected $fillable = ['titre', 'description', 'editeur_id'];

Cette fois ça fonctionne :

Image non disponible

Il existe la propriété $guarded qui est exactement l'inverse de $fillable.

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

Image non disponible

X-F-3. 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 :

Image non disponible

À partir de ce scénario, je vous laisse deviner la différence entre les deux méthodes.

X-G. Agir sur la table pivot

X-G-1. Attacher

Pour ajouter un enregistrement dans la table pivot on dispose de la méthode attach. Par exemple mon auteur 1 est relié à quatre livres :

Image non disponible

Je veux le relier à un autre livre, voilà comment faire :

Image non disponible

On trouve bien l'enregistrement dans la table pivot :

Image non disponible

X-G-2. Détacher

À l'inverse on peut détacher avec la méthode detach :

Image non disponible

X-G-3. Synchroniser

Parfois on veut mettre à jour globalement les attachements, on le fait avec la méthode sync :

Image non disponible

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.

X-H. Mettre à jour des enregistrements

X-H-1. Méthode « save »

On peut utiliser la méthode save pour aussi mettre à jour des enregistrements :

Image non disponible

X-H-2. Méthode « update »

On peut aussi utiliser la méthode update :

Image non disponible

L'avantage de cette méthode est qu'on peut mettre à jour plusieurs enregistrements :

Image non disponible

On voit ici qu'on a modifié cinq enregistrements en changeant l'éditeur de ces livres.

X-I. Supprimer des enregistrements

X-I-1. Méthode « delete »

On peut utiliser la méthode delete pour supprimer un enregistrement. Étant 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 :

Image non disponible

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.

X-J. 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.

précédentsommairesuivant

Copyright © 2017 Laravel. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.