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 :
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) :
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 :
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 :
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 :
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 :
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 :
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 :
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 :
X-C-3. Lignes distinctes▲
On peut aussi utiliser la méthode distinct pour avoir des lignes distinctes :
X-C-4. Plusieurs conditions▲
On peut combiner des where :
X-C-5. Encadrer des valeurs▲
On peut encadrer des valeurs avec whereBetween :
X-C-6. Prendre des valeurs dans un tableau▲
On peut aussi prendre des valeurs dans un tableau avec whereIn :
X-C-7. Agrégations▲
On peut aussi compter, calculer…
X-C-8. 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.
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 :
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.
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 :
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 :
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 :
Le changement peut paraître minime, mais on n'a plus que deux 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 :
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 :
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 trois requêtes :
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 :
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 :
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 :
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 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é :
protected $fillable
=
[
'
titre
'
,
'
description
'
,
'
editeur_id
'
];
Cette fois ça fonctionne :
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 :
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 :
À 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 :
Je veux le relier à un autre livre, voilà comment faire :
On trouve bien l'enregistrement dans la table pivot :
X-G-2. Détacher▲
À l'inverse on peut détacher avec la méthode detach :
X-G-3. 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.
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 :
X-H-2. 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é 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 :
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.