Vous êtes sur la page 1sur 346

Cours Laravel 9 – les bases – présentation générale

laravel.sillo.org/cours-laravel-9-les-bases-presentation-generale/

8 janvier 2022

Dans ce premier chapitre je vais évoquer PHP, son historique rapide et sa situation
actuelle. Je vais aussi expliquer l’intérêt d’utiliser un framework pour ce langage et
surtout pourquoi j’ai choisi Laravel. J’évoquerai enfin le patron MVC et la Programmation
Orientée Objet.

Un framework ?

Approche personnelle
PHP est un langage populaire et accessible. Il est facile à installer et présent chez tous
les hébergeurs. C’est un langage riche et plutôt facile à aborder, surtout pour quelqu’un
qui a déjà des bases en programmation. On peut réaliser rapidement une application web
fonctionnelle grâce à lui. Mais le revers de cette simplicité est que bien souvent le code
créé est confus, complexe, sans aucune cohérence. Il faut reconnaître que PHP
n’encourage pas à organiser son code et rien n’oblige à le faire.

Lorsqu’on crée des applications PHP on finit par avoir des routines personnelles toutes
prêtes pour les fonctionnalités récurrentes, par exemple pour gérer des pages de façon
dynamique. Une fois qu’on a créé une fonction ou une classe pour réaliser une tâche il
est naturel d’aller la chercher lorsque la même situation se présente. Puisque c’est une
bibliothèque personnelle et qu’on est seul maître à bord il faut évidemment la mettre à
jour lorsque c’est nécessaire, et c’est parfois fastidieux.

1/6
En général on a aussi une hiérarchie de dossiers à laquelle on est habitué et on la
reproduit quand on commence le développement d’une nouvelle application. On se rend
compte des fois que cette habitude a des effets pervers parce que la hiérarchie qu’on met
ainsi en place de façon systématique n’est pas forcément la plus adaptée.

En résumé l’approche personnelle est plutôt du bricolage à la hauteur de ses


compétences et de sa disponibilité.

(Re)découvrir PHP
Lorsque j’ai découvert PHP à la fin du dernier millénaire (ça fait plus impressionnant dit
comme ça ) il en était à la version 3. C’était essentiellement un langage de script en
général mélangé au HTML qui permettait de faire du templating, des accès aux données
et du traitement. La version 4 en 2000 a apporté plus de stabilité et une ébauche de
l’approche objet. Mais il a fallu attendre la version 5 en 2004 pour disposer d’un langage
de programmation à la hauteur du standard existant pour les autres langages. Ensuite la
version 7, puis la 8, n’ont fait que renforcer le sérieux de ce langage.

Cette évolution incite à perdre les mauvaises habitudes si on en avait. Un site comme
http://www.phptherightway.com offre des pistes pour mettre en place de bonnes
pratiques. Donc si vous êtes un bidouilleur de code PHP je vous conseille cette saine
lecture qui devrait vous offrir un nouvel éclairage sur ce langage et surtout vous permettre
de vous lancer de façon correcte dans le code de Laravel.

Un framework
D’après Wikipedia un framework informatique est un « ensemble cohérent de
composants logiciels structurels, qui sert à créer les fondations ainsi que les grandes
lignes de tout ou d’une partie d’un logiciel ». Autrement dit une base homogène avec des
briques toutes prêtes à disposition. Il existe des frameworks pour tous les langages de
programmation et en particulier pour PHP. En faire la liste serait laborieux tant il en existe
!

L’utilité d’un framework est d’éviter de passer du temps à développer ce qui a déjà été fait
par d’autres souvent plus compétents et qui a en plus été utilisé et validé par de
nombreux utilisateurs. On peut imaginer un framework comme un ensemble d’outils à
disposition. Par exemple je dois faire du routage pour mon site, je prends un composant
déjà tout prêt et qui a fait ses preuves et je l’utilise : gain de temps, fiabilité, mise à jour si
nécessaire…

Il serait vraiment dommage de se passer d’un framework alors que le fait d’en utiliser un
présente pratiquement uniquement des avantages.

Pourquoi Laravel ?

Constitution de Laravel

2/6
Laravel, créé par Taylor Otwel, initie une nouvelle façon de concevoir un framework en
utilisant ce qui existe de mieux pour chaque fonctionnalité. Par exemple toute application
web a besoin d’un système qui gère les requêtes HTTP. Plutôt que de réinventer quelque
chose, le concepteur de Laravel a tout simplement utilisé celui de Symfony en l’étendant
pour créer un système de routage efficace. En quelque sorte Otwel a fait son marché
parmi toutes les bibliothèques disponibles. Nous verrons dans ce cours comment cela est
réalisé. Mais Laravel ce n’est pas seulement le regroupement de bibliothèques existantes
(d’ailleurs quelques mauvaises langues insistent là dessus), c’est aussi de nombreux
composants originaux et surtout une orchestration de tout ça.

Vous allez trouver dans Laravel :

un système de routage (RESTFul et ressources),


un créateur de requêtes SQL et un ORM,
un moteur de template,
un système d’authentification pour les connexions,
un système de validation,
un système de pagination,
un système de migration pour les bases de données,
un système d’envoi d’emails,
un système de cache,
un système de gestion des événements,
un système d’autorisations,
une gestion des sessions,
un système de localisation,
un système de notifications…

Et d’autres choses encore que nous allons découvrir ensemble. Il est probable que
certains éléments de cette liste ne vous évoquent pas grand-chose, mais ce n’est pas
important pour le moment, tout cela deviendra plus clair au fil des chapitres.

Le meilleur de PHP

Plonger dans le code de Laravel, c’est recevoir un cours de programmation tant le style
est clair et élégant et le code bien organisé. Pour aborder de façon efficace ce
framework, il serait souhaitable que vous soyez familiarisé avec quelques notions :

les espaces de noms : c’est une façon de bien ranger le code pour éviter des
conflits de nommage. Laravel utilise cette possibilité de façon intensive. Tous les
composants sont rangés dans des espaces de noms distincts, de même que
l’application créée.
les fonctions anonymes : ce sont des fonctions sans nom (souvent appelées
closures) qui permettent d’améliorer le code. Les utilisateurs de Javascript y sont
habitués. Les utilisateurs de PHP un peu moins parce qu’elle y sont plus récentes.
Laravel les utilise aussi de façon systématique.

3/6
les méthodes magiques : ce sont des méthodes qui n’ont pas été explicitement
décrites dans une classe mais qui peuvent être appelées et résolues.
les interfaces : une interface est un contrat de constitution des classes. En
programmation objet c’est le sommet de la hiérarchie. Tous les composants de
Laravel sont fondés sur des interfaces.
les traits : c’est une façon d’ajouter des propriétés et méthodes à une classe sans
passer par l’héritage, ce qui permet de passer outre certaines limitations de
l’héritage simple proposé par défaut par PHP.

La documentation
Quand on s’intéresse à un framework il ne suffit pas qu’il soit riche et performant, il faut
aussi que la documentation soit à la hauteur. C’est le cas pour Laravel. Vous trouverez la
documentation sur le site officiel. Mais il existe de plus en plus de sources d’informations
dont voici les principales :

https://laravel.fr : site d’entraide francophone avec un forum actif.


http://laravel.io : le forum officiel.
http://www.laravel-tricks.com : un site d’astuces.
http://packalyst.com : le rassemblement de tous les packages pour ajouter des
fonctionnalités à Laravel.
https://laracasts.com : de nombreux tutoriels vidéo en anglais dont un certain
nombre en accès gratuit.
https://github.com/chiraggude/awesome-laravel : une page comportant une
multitude de liens intéressants.

Il existe aussi de bon livres mais pratiquement tous en anglais.

MVC ? POO ?

MVC

On peut difficilement parler d’un framework sans évoquer le patron Modèle-Vue-


Contrôleur. Pour certains il s’agit de la clé de voûte de toute application rigoureuse, pour
d’autres c’est une contrainte qui empêche d’organiser judicieusement son code. De quoi
s’agit-il ? Voici un petit schéma pour y voir clair :

4/6
C’est un modèle d’organisation du code :

le modèle est chargé de gérer les données,


la vue est chargée de la mise en forme pour l’utilisateur,
le contrôleur est chargé de gérer l’ensemble.

En général on résume en disant que le modèle gère la base de données, la vue produit
les pages HTML et le contrôleur fait tout le reste. Dans Laravel :

le modèle correspond à une table d’une base de données. C’est une classe qui
étend la classe Model qui permet une gestion simple et efficace des manipulations
de données et l’établissement automatisé de relations entre tables,
le contrôleur se décline en deux catégories : contrôleur classique et contrôleur de
ressource (je détaillerai évidemment tout ça dans le cours),
la vue est soit un simple fichier avec du code HTML, soit un fichier utilisant le
système de template Blade de Laravel.

Laravel propose ce patron mais ne l’impose pas. Nous verrons d’ailleurs qu’il est parfois
judicieux de s’en éloigner parce qu’il y a des tas de chose qu’on n’arrive pas à caser dans
cette organisation. Par exemple si je dois envoyer des emails où vais-je placer mon code
? En général ce qui se produit est l’inflation des contrôleurs auxquels on demande des
choses pour lesquelles ils ne sont pas faits.

POO

5/6
Laravel est fondamentalement orienté objet. La POO est un design pattern qui s’éloigne
radicalement de la programmation procédurale. Avec la POO tout le code est placé dans
des classes qui découlent d’interfaces qui établissent des contrats de fonctionnement.
Avec la POO on manipule des objets.

Avec la POO, la responsabilité du fonctionnement est répartie dans des classes, alors
que dans l’approche procédurale tout est mélangé. Le fait de répartir la responsabilité
évite la duplication du code qui est le lot presque forcé de la programmation procédurale.
Laravel pousse au maximum cette répartition en utilisant l’injection de dépendance.

L’utilisation de classes bien identifiées, dont chacune a un rôle précis, pilotées par des
interfaces claires, dopées par l’injection de dépendances : tout cela crée un code élégant,
efficace, lisible, facile à maintenir et à tester. C’est ce que Laravel propose. Alors vous
pouvez évidemment greffer là dessus votre code approximatif, mais vous pouvez aussi
vous inspirer des sources du framework pour améliorer votre style de programmation.

Popularité
Laravel s’est établi en quelques années au sommet de l’usage des frameworks PHP.
Voici des statistiques assez récentes basées sur la popularité sur Github :

En résumé
Un framework fait gagner du temps et donne l’assurance de disposer de
composants bien codés et fiables
Laravel est un framework novateur, complet, qui utilise les possibilités les plus
récentes de PHP et qui est impeccablement codé et organisé
La documentation de Laravel est complète, précise et de nombreux tutoriels et
exemples sont disponibles sur la toile
Laravel adopte le patron MVC mais ne l’impose pas, il est totalement orienté objet

6/6
Cours Laravel 9 – les bases – un environnement de
développement
laravel.sillo.org/cours-laravel-9-les-bases-un-environnement-de-developpement/

8 janvier 2022

Pour fonctionner Laravel a besoin d’un certain environnement :

un serveur,
PHP,
MySql,
Node,
Composer…

Il y a de plus en plus d’environnements disponibles dans le cloud avec des options


gratuites comme chez c9. Mais rien ne vaut un bon système local : c’est rapide, sûr et on
peut tout gérer. Mais évidemment il faut le construire !

Heureusement, il existe des solutions toutes prêtes, par exemple pour PHP + MySql :
wampserver, xampp…

Ces solutions sont intéressantes, mais pour ce cours je vous conseille plutôt Laragon. Il
est simple, rapide, convivial, non intrusif, complet, et en plus pensé pour Laravel ! Mais il
ne fonctionne que sur Windows.

Pour les utilisateurs de Linux il faut se tourner vers l’une des solutions évoquées ci-
dessus ou alors Homestead qui est l’environnement officiel de Laravel. Pour son
installation, il suffit de suivre la procédure décrite dans la documentation. Par contre, je
déconseille aux utilisateurs de Windows d’installer Homestead, sauf s’ils ont envie de
passer de longues heures à configurer leur système.

Pour les inconditionnels de Mac il faut se tourner vers Valet.

Donc en résumé :

pour Windows : Laragon,


pour Linux ou Mac : Homestead,
pour Mac : Valet.

Laragon

Installation
Pour récupérer Laragon il faut se rendre sur le site :

1/8
Ne cherchez pas trop de documentation (mais ne vous en faites pas, il n’y en a pas
vraiment besoin), il y a surtout un bouton pour aller sur le forum de discussion ainsi qu’un
autre pour le téléchargement. Vous verrez que vous avez plusieurs possibilités pour le
téléchargement, choisissez celle qui vous convient, a priori Laragon full qui comporte
Apache 2.4, Nginx, MySQL 5.7, PHP 7.4, Redis, Memcached, Node.js 14, npm, yarn,
git…

Une fois l’installation effectuée vous ouvrez Laragon et cliquer sur « Tout démarrer » pour
créer et lancer le serveur :

2/8
Vous avez maintenant à disposition :

un serveur Apache
PHP 7.4
MySQL 5.7
le terminal Cmder (une console améliorée par rapport à l’horrible de Windows !)
Notepad++
composer
nodejs
putty
memcached
git
redis…

Il va juste falloir changer la version de PHP parce que la 7.4 ne va pas nous suffire pour
la version 9 de Laravel qui nécessite la version 8. Il est facile d’ajouter une version de
PHP avec Laragon, la procédure est expliquée ici. Mais il vous faudra aussi sans doute
mettre à jour aussi la version d’Apache, c’est expliqué ici.

Avec Homestead on obtient pratiquement les mêmes possibilités.

Ne vous inquiétez pas si vous ne connaissez pas la moitié de ces outils ! D’une part nous
ne les utiliserons pas tous, d’autre part je détaillerai l’utilisation de ceux qui vous seront
nécessaires.

3/8
Hôte virtuel
Cerise sur le gâteau : Laragon crée automatiquement des hôtes virtuels pour les dossiers
qui se trouvent sur le serveur (www). Pour ceux qui ne savent pas de quoi il s’agit voici
un exemple avec justement le cas de Laravel. Le fichier de démarrage de Laravel est
placé en www/monsite/public/index.html. Donc à partir de localhost il faut entrer :
localhost/monsite/public. Ce n’est ni pratique ni élégant. Un hôte virtuel permet de
définir une adresse simplifiée. Par exemple ici Laragon va définir automatiquement
monsite.test. Avouez que c’est quand même mieux !

Mais ce n’est pas seulement une histoire d’esthétique ou d’économie de clavier. Le fait de
disposer d’un hôte virtuel permet d’avoir en local exactement le même comportement que
sur le serveur de production. Par exemple si vous avez sur une page HTML une image
avec ce genre de référence : /images/bouton.png, vous serez tout à fait heureux d’avoir
un hôte virtuel pour que l’image s’affiche !

Créer manuellement un hôte virtuel avec Windows n’est pas difficile, mais un peu
laborieux. Il faut modifier le fichier hosts de Windows et httpd-vhosts.conf de Apache.
Autant laisser Laragon s’en charger !

Composer

Présentation

Je vous ai dit dans le précédent chapitre que Laravel utilise des composants d’autres
sources. Plutôt que de les incorporer directement, il utilise un gestionnaire de
dépendances : composer. D’ailleurs pour le coup les composants de Laravel sont aussi
traités comme des dépendances. Mais qu’est-ce qu’un gestionnaire de dépendances ?

Imaginez que vous créez une application PHP et que vous utilisez des composants issus
de différentes sources : Carbon pour les dates, Redis pour les données… Vous pouvez
utiliser la méthode laborieuse en allant chercher tout ça de façon manuelle, et vous allez
être confronté à des difficultés :

télécharger tous les composants dont vous avez besoin et les placer dans votre
structure de dossiers,
traquer les éventuels conflits de nommage entre les librairies,
mettre à jour manuellement les librairies quand c’est nécessaire,
prévoir le code pour charger les classes à utiliser…

Tout ça est évidemment faisable, mais avouez que s’il était possible d’automatiser les
procédures, ce serait vraiment génial. C’est justement ce que fait un gestionnaire de
dépendances !

JSON

4/8
Pour comprendre le fonctionnement de Composer, il faut connaître le format JSON qui
est l’acronyme de JavaScript Object Notation. Un fichier JSON a pour but de contenir
des informations de type étiquette-valeur. Regardez cet exemple élémentaire :

{
"nom": "Durand",
"prénom": "Jean"
}

Les étiquettes sont « nom » et « prénom » et les valeurs correspondantes « Durand » et


« Jean ». Les valeurs peuvent être aussi des tableaux ou des objets. Regardez ce
second exemple :

{
"identité1" : {
"nom": "Durand",
"prénom": "Jean"
},
"identité2" : {
"nom": "Dupont",
"prénom": "Albert"
}
}

Composer a besoin d’un fichier composer.json associé. Ce fichier contient les


instructions : les dépendances, les classes à charger automatiquement… Par exemple :

"require": {
"php": "^8.0",
"laravel/framework": "^9.0",
},

Ici on dit qu’on veut (require) que PHP soit au moins en version 8.0 et on veut également
charger le composant « laravel/framework ».

On verra comment se présente le fichier composer.json de Laravel dans le prochain


chapitre.

Packagist

Tous les composants disponibles se trouvent sur le site Packagist :

5/8
Par exemple il me faut un composant pour l’envoi d’email, j’entre ceci dans la zone de
recherche :

J’obtiens une liste assez longue et je n’ai plus qu’à fouiller un peu pour trouver ce que je
cherche.

Packalyst

6/8
Le site Packalyst est spécialisé dans les composant conçus pour Laravel :

Là vous êtes sûr que le composant va fonctionner directement dans Laravel ! Il faut
toutefois vérifier qu’il correspond au numéro de version que vous utilisez.

Les éditeurs de code


Choisir un éditeur de code n’est pas évident, les critères sont nombreux. J’aime bien
Visual Studio Code qui jouit d’une grande popularité :

Son grand avantage est de proposer de très nombreux plugins pour étendre ses
possibilités, et il y en a pas mal pour Laravel. Vous pouvez installer tous les plugins que
vous voulez. Pour trouver ceux qui concernent Laravel vous avez une zone de recherche
:

7/8
Dans la communauté Laravel l’IDE qui a un grand succès est PhpStorm‌. Il est vraiment
puissant et complet, mais il n’est pas gratuit (sauf pour les étudiants qui ont une carte
internationale à jour).

En résumé
Laravel a besoin d’un environnement de développement complet : PHP, MySql,
composer…
Il existe des solutions toutes prêtes : Homestead pour Linux, Valet pour Mac et
Laragon pour Windows.
Composer est le gestionnaire de dépendances utilisé par Laravel.
Visual Studio Code est l’éditeur de code le plus utilisé et il est performant et gratuit.

8/8
Cours Laravel 9 – les bases – installation et
organisation
laravel.sillo.org/cours-laravel-9-les-bases-installation-et-organisation/

10 février 2022

Dans ce chapitre nous allons voir comment créer une application Laravel et comment le
code est organisé dans une application.

Pour utiliser Laravel et suivre ce chapitre et l’ensemble du cours vous aurez besoin d’un
serveur équipé de PHP avec au minimum la version 8 et aussi de MySQL. Nous avons
vu dans le précédent chapitre les différentes possibilités. D’autre part plusieurs
extensions de PHP doivent être activées.

Créer une application Laravel

Le serveur

Pour fonctionner correctement, Laravel a besoin de PHP :

Version >= 8,
Extension PDO,
Extension Mbstring,
Extension OpenSSL,
Extension Tokenizer,
Extension XML.‌,
Extension BCMath,
Extension Ctype,
Extension JSON
Extension Fileinfo
Extension DOM
Extentsion PCRE

Laravel est équipé d’un serveur sommaire pour le développement qui se lance avec cette
commande :

php artisan serve

On y accède à cette adresse : http://127.0.0.1:8000. Mais évidemment pour que ça


fonctionne il faut que vous ayez PHP installé.

Prérequis

Composer fonctionne en ligne de commande. Vous avez donc besoin de la console


(nommée Terminal ou Konsole sur OS X et Linux). Les utilisateurs de Linux sont très
certainement habitués à l’utilisation de la console mais il en est généralement pas de

1/11
même pour les adeptes de Windows. Pour trouver la console sur ce système il faut
chercher l’invite de commande :

Si vous utilisez Laragon, comme je vous le


conseille, vous avez une console améliorée
(Cmder) accessible avec ce bouton.

Si vous utilisez Visual Studio Code


vous pouvez ouvrir directement un
terminal (et même plusieurs)
directement dans une fenêtre de
l’éditeur :

Installation avec composer


Il y a plusieurs façons de créer une application Laravel. La plus classique consiste à
utiliser la commande create-project de composer. Par exemple je veux créer une
application dans un dossier laravel9 à la racine de mon serveur, voici la syntaxe à utiliser
:

composer create-project laravel/laravel laravel9

L’installation démarre et je n’ai plus qu’à attendre quelques minutes pour que Composer
fasse son travail jusqu’au bout. Vous verrez s’afficher une liste de téléchargements. Au
final on se retrouve avec cette architecture :

On peux vérifier que tout fonctionne bien avec l’URL http://localhost/laravel9/public.


Normalement on doit obtenir cette page :

2/11
Pour les mises à jour ultérieures il suffit encore d’utiliser Composer avec la commande
update :

3/11
composer update

Installation avec Laravel Installer


Une autre solution pour installer Laravel consiste à utiliser l’installeur. Il faut commencer
par installer globalement l’installeur avec Composer :

composer global require laravel/installer

Pour Windows il faut ensuite informer la variable d’environnement path de l’emplacement


du dossier %USERPROFILE%\AppData\Roaming\Composer\vendor\bin.

Pour créer une application il suffit de taper :

laravel new monappli

Laravel sera alors installé dans le dossier monappli.

Installation avec Laragon


Une façon encore plus simple pour installer Laravel est d’utiliser Laragon avec le menu :

On vous demande alors le nom du projet (donc du dossier) :

Et ensuite vous n’avez plus qu’à attendre ! La


console s’ouvre et vous pouvez suivre le
déroulement des opérations. On vous rappelle la
commande de composer et en plus une base de
données et automatiquement créée avec le même
nom !

Vous avez alors accès directement à l’application


avec laravel9.oo.

4/11
Si vous installez Laravel en téléchargeant directement les fichiers sur Github et en
utilisant la commande composer install il vous faut effectuer deux actions
complémentaires. En effet dans ce cas il ne sera pas automatiquement créé de clé de
sécurité et vous allez tomber sur une erreur au lancement. Il faut donc la créer avec la
commande php artisan key:generate. D’autre part vous aurez à la racine le fichier
.env.example que vous devrez renommer en .env (ou en faire une copie) pour que la
configuration fonctionne.

Autorisations
Au niveau des dossiers de Laravel, les seuls qui ont besoin de droits d’écriture par le
serveur sont storage (et ses sous-dossiers), et bootstrap/cache.

Des URL propres


Pour un serveur Apache il est prévu dans le dossier public un fichier .htaccess avec ce
code :

<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews -Indexes
</IfModule>

RewriteEngine On

# Handle Authorization Header


RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

# Redirect Trailing Slashes If Not A Folder...


RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]

# Send Requests To Front Controller...


RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>

Le but est essentiellement d’éviter d’avoir index.php dans l’url. Mais pour que ça
fonctionne il faut activer le module mod_rewrite.

Avec NGinx il faut ajouter ça dans la configuration du site :

location / {
try_files $uri $uri/ /index.php?$query_string;
}

Organisation de Laravel

5/11
Maintenant qu’on a un Laravel tout neuf et qui fonctionne voyons un peu ce qu’il
contient…

Dossier app

Ce dossier contient les éléments essentiels de l’application :

Console : toutes les commandes en


mode console,
Exceptions : pour gérer les erreurs
d’exécution,
Http : tout ce qui concerne la
communication : contrôleurs,
middlewares (il y a 7 middlewares de
base qui servent à filtrer les requêtes
HTTP) et le kernel,
Providers : tous les fournisseurs de
services (providers), il y en a déjà 5 au
départ. Les providers servent à
initialiser les composants.
Models : le dossier des modèles avec
déjà un présent qui concerne les
utilisateurs.

Évidemment tout cela doit vous paraître


assez nébuleux pour le moment mais nous
verrons en détail ces éléments au fil du
cours. Et on verra d’ailleurs que seront créés
bien d’autres dossiers selon nos besoins.

Autres dossiers

Voici une description du contenu des autres dossiers :

6/11
bootstrap : scripts d’initialisation de Laravel pour le chargement automatique des
classes, la fixation de l’environnement et des chemins, et pour le démarrage de
l’application,
public : tout ce qui doit apparaître dans le dossier public du site : images, CSS,
scripts…
config : toutes les configurations : application, authentification, cache, base de
données, espaces de noms, emails, systèmes de fichier, session…
database : migrations et populations,
resources : vues, fichiers de langage et assets (par exemple les fichiers Sass),
routes : la gestion des urls d’entrée de l’application,
storage : données temporaires de l’application : vues compilées, caches, clés de
session…
tests : fichiers de tests unitaires,
vendor : tous les composants de Laravel et de ses dépendances (créé par
composer).

Fichiers de la racine

Il y a un certain nombre de fichiers dans la racine dont voici les


principaux :

artisan : outil en ligne de Laravel pour des tâches de gestion,


composer.json : fichier de référence de composer,
package.json : fichier de référence de npm pour les assets,
phpunit.xml : fichier de configuration de phpunit (pour les tests
unitaires),
.env : fichier pour spécifier l’environnement d’exécution.

Nous verrons tout cela progressivement dans le cours, ne vous


inquiétez pas !

Accessibilité

Pour des raisons de sécurité sur le serveur seul le dossier public doit être accessible :

7/11
Cette configuration n’est pas toujours possible sur un serveur mutualisé, il faut alors
modifier un peu Laravel pour que ça fonctionne, j’en parlerai dans le chapitre sur le
déploiement.

Environnement et messages d’erreur


Par défaut lorsque vous installez Laravel, celui-ci est en mode « debug » et vous aurez
une description précise de toutes les erreurs. Par exemple ouvrez le fichier
routes/web.php et changez ainsi le code :

Route::post('/', function () {
return view('welcome');
});

Ouvrez l’url de base, vous obtenez une page d’erreur avec en particulier cette information
:

Pendant la phase de développement on a besoin d’obtenir des messages explicites pour


traquer les erreurs inévitables que nous allons faire. En mode « production » il faudra
changer ce mode, pour cela ouvrez le fichier config/app.php et trouvez cette ligne :

'debug' => (bool) env('APP_DEBUG', false),

8/11
Autrement dit on va chercher la valeur dans l’environnement, mais où peut-on le trouver ?
Regardez à la racine des dossiers, vous y trouvez le fichier .env avec ce contenu :

APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:crHCzQ5T3RTmdRg+5PuvskDcoP95iP5ZUdBrU9PIaXo=
APP_DEBUG=true
APP_URL=http://localhost

LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=

BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120

MEMCACHED_HOST=127.0.0.1

REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

MAIL_MAILER=smtp
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME="${APP_NAME}"

AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false

PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1

MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

9/11
Vous remarquez que dans ce fichier la variable APP_DEBUG a la valeur true. On va la
conserver ainsi puisqu’on veut être en mode « debug ». Vous êtes ainsi en mode
débogage avec affichage de messages d’erreur détaillés. Si vous la mettez à false (ou si
vous la supprimez), avec une URL non prévue vous obtenez maintenant juste :

Il ne faudra évidemment pas laisser la valeur true lors d’une mise en production ! On en
reparlera lorsqu’on verra la gestion de l’environnement. Vous ne risquerez ainsi plus
d’oublier de changer cette valeur parce que Laravel saura si vous êtes sur votre serveur
de développement ou sur celui de production.

D’autre part il y a un fichier qui collecte les erreurs (Log), il n’existe pas à l’installation
mais se crée à la première erreur signalée. Alors on va en créer une. Toujours dans le
fichier routes/web.php modifiez ainsi le code :

Route::get('/', function () {
return view('welcofme');
});

On a changé le nom de la vue, du coup elle n’existe pas (on parlera plus tard des vues
évidemment). Maintenant un fichier de Log a été créé ici :

On peut y lire :

[2022-01-14 16:08:05] local.ERROR: View [welcofme] not found.


{"exception":"[object] (InvalidArgumentException(code: 0):
View [welcofme] not found. at ...

Par défaut il n’y a qu’un fichier mais si vous préférez avoir un


fichier par jour par exemple il suffit d’ajouter cette ligne dans le
fichier .env :

LOG_CHANNEL=daily

De la même manière par défaut Laravel stocke toutes les erreurs. C’est pratique dans la
phase de développement mais en production vous pouvez limiter le niveau de sévérité
des erreurs retenues (mode debug), par exemple si vous vous contentez des warning :

LOG_LEVEL=warning

La valeur de APP_KEY qui sécurise les informations est automatiquement générée lors
de l’installation avec create-project.

10/11
En résumé
Pour son installation et sa mise à jour Laravel utilise le gestionnaire de
dépendances composer.
La création d’une application Laravel se fait à partir de la console avec une simple
ligne de commande.
Laravel est organisé en plusieurs dossiers.
Le dossier public est le seul qui doit être accessible pour le client.
L’environnement est fixé à l’aide du fichier .env.
Par défaut Laravel est en mode « debug » avec affichage de toutes les erreurs.

11/11
Cours Laravel 9 – les bases – le routage
laravel.sillo.org/cours-laravel-9-les-bases-le-routage/

10 février 2022

Dans ce chapitre nous allons nous intéresser au devenir d’une requête HTTP qui arrive
dans notre application Laravel. Nous allons voir l’intérêt d’utiliser un fichier .htaccess
pour simplifier les url. Nous verrons aussi le système de routage pour trier les requêtes.

Les requêtes HTTP

Petit rappels

On va commencer par un petit rappel sur ce qu’est une requête HTTP. Voici un schéma
illustratif :

Le HTTP (Hypertext Transfer Protocol) est un protocole de communication entre un client


et un serveur. Le client demande une ressource au serveur en envoyant une requête et le
serveur réagit en envoyant une réponse, en général une page Html.

Quand on surfe sur Internet chacun de nos clics provoque en général cet échange, et
plus généralement une rafale d’échanges.

La requête du client comporte un certain nombre d’informations (headers, status code,


body…).

Prenons l’exemple de ce blog. Lorsque je clique sur le lien voilà un aperçu de toutes les
requêtes HTTP qui se produisent :

1/8
Regardons d’un peu plus près l’une d’elles :

On trouve :

l’url : https://laravel.sillo.org/
la méthode : GET
le code : 200 (donc tout s’est bien
passé)
la version du HTTP : 1.1

Il y a évidemment bien d’autres choses dans les headers (content-type, cookies,


encodage…) mais pour le moment on va se contenter de ces informations. Notre
navigateur digère tout ça de façon transparente, heureusement pour nous !

Notre application Laravel doit savoir interpréter les informations qui arrivent et les utiliser
de façon pertinente pour renvoyer ce que demande le client. Nous allons voir comment
cela est réalisé.

Les méthodes

Il est indispensable de connaître les principales méthodes du HTTP :

GET : c’est la plus courante, on demande une ressource qui ne change jamais, on
peut mémoriser la requête, on est sûr d’obtenir toujours la même ressource,
POST : elle est aussi très courante, là la requête modifie ou ajoute une ressource,
le cas le plus classique est la soumission d’un formulaire (souvent utilisé à tort à la
place de PUT),
PUT : on ajoute ou remplace complètement une ressource,
PATCH : on modifie partiellement une ressource (donc à ne pas confondre avec
PUT),
DELETE : on supprime une ressource.

La différence entre PUT et POST est loin d’être évidente, vous pouvez lire sur le sujet cet
excellent article.

.htaccess et index.php

2/8
Pour Laravel on veut que toutes les requêtes aboutissent obligatoirement sur le fichier
index.php situé dans le dossier public. Pour y arriver on peut utiliser une URL de ce
genre :

http://monsite.fr/index.php/mapage

Mais ce n’est pas très esthétique avec ce index.php au milieu. Si vous avez un serveur
Apache lorsque la requête du client arrive sur le serveur où se trouve notre application
Laravel elle passe en premier par le fichier .htaccess, s’il existe, qui fixe des règles pour
le serveur. Il y a justement un fichier .htaccess dans le dossier public de Laravel avec
une règle de réécriture de telle sorte qu’on peut avoir une url simplifiée :

http://monsite.fr/mapage

Un petit schéma pour visualiser cette action :

Pour que ça fonctionne il faut que le serveur Apache ait le module mod_rewrite activé.

Le cycle de la requête

Lorsque la requête atteint le fichier public/index.php l’application Laravel est créée et


configurée et l’environnement est détecté. Nous reviendrons plus tard plus en détail sur
ces étapes. Ensuite le fichier routes/web.php est chargé. Voici l’emplacement de ce
fichier :

Les autres fichiers concernent des routes plus spécifiques comme


pour les API avec le fichier api.php ou les routes pour les actions en
ligne de commande avec le fichier console.php.

C’est avec ce fichier que la requête va être analysée et dirigée.


Regardons ce qu’on y trouve au départ :

Route::get('/', function () {
return view('welcome');
});

Comme Laravel est explicite vous pouvez déjà deviner à quoi sert ce code :

Route : on utilise le routeur,


get : on regarde si la requête a la méthode « get »,
‘/’ : on regarde si l’url comporte uniquement le nom de domaine,

3/8
dans la fonction anonyme on retourne (return) une vue (view) à partir du fichier
« welcome ».

Ce fichier « welcome » se trouve bien rangé dans le dossier des vues :

C’est ce fichier comportant du code Html qui génère le texte


d’accueil que vous obtenez au démarrage initial de Laravel :

Laravel propose plusieurs helpers qui simplifient la syntaxe. Il y a par exemple view pour
la classe View comme on l’a vu dans le code ci-dessus.

Visualisons le cycle de la requête :

4/8
Sur votre serveur local vous n’avez pas de nom de domaine et vous allez utiliser une url
de la forme http://localhost/tuto/public en admettant que vous ayez créé Laravel dans
un dossier www/tuto. Mais vous pouvez aussi créer un hôte virtuel pour avoir une
situation plus réaliste comme déjà évoqué au précédent chapitre.

Laravel accepte les verbes suivants : get, post, put, patch, delete, options, match
(pour prévoir plusieurs verbes) et any (on accepte tous les verbes).

Plusieurs routes et paramètre de route


A l’installation, Laravel a une seule route qui correspond à l’url de base composée
uniquement du nom de domaine. Voyons maintenant comment créer d’autres routes.
Imaginons que nous ayons 3 pages qui doivent être affichées avec ces urls :

1. http://monsite.fr/1
2. http://monsite.fr/2
3. http://monsite.fr/3

J’ai fait apparaître en gras la partie spécifique de l’url pour chaque page. Il est facile de
réaliser cela avec ce code :

Route::get('1', function() { return 'Je suis la page 1 !'; });


Route::get('2', function() { return 'Je suis la page 2 !'; });
Route::get('3', function() { return 'Je suis la page 3 !'; });

Cette fois je n’ai pas créé de vue parce que ce qui nous intéresse est uniquement une
mise en évidence du routage, je retourne donc directement la réponse au client.
Visualisons cela pour la page 1 :

5/8
On a besoin du caractère « / » uniquement dans la route de base.

On peut maintenant se poser une question : est-il vraiment indispensable de créer 3


routes alors que la seule différence tient à peu de chose : une valeur qui change ?

On peut utiliser un paramètre pour une route qui accepte des éléments variables en
utilisant des accolades. Regardez ce code :

Route::get('{n}', function($n) {
return 'Je suis la page ' . $n . ' !';
});

Et une visualisation du fonctionnement :

On dit que la route est paramétrée parce qu’elle possède un paramètre qui peut prendre
n’importe quelle valeur.

On peut rendre un paramètre optionnel en lui ajoutant un point d’interrogation mais il ne


doit pas être suivi par un paramètre obligatoire. Dans ce cas pour éviter une erreur
d’exécution il faut prévoir une valeur par défaut pour le paramètre, par exemple :

Route::get('{n?}', function($n = 1) {

Le paramètre n est devenu optionnel et par défaut sa valeur est 1.

Erreur d’exécution et contrainte de route

6/8
Dans mon double exemple précédent lorsque je dis que le résultat est le même je mens
un peu. Que se passe-t-il dans les deux cas pour cette url ?

http://monsite.fr/4

Dans le cas des trois routes vous tombez sur une erreur
:Par contre dans la version avec le paramètre vous
obtenez une réponse valide ! Ce qui est logique parce
qu’une route est trouvée. Le paramètre accepte n’importe
quelle valeur et pas seulement des nombres. Par exemple
avec cette url :

http://monsite.fr/nimportequoi

Vous obtenez :

Je suis la page nimportequoi !

Ce qui vous l’avouerez n’est pas très heureux !

Pour éviter ce genre de désagrément il faut contraindre le paramètre à n’accepter que


certaines valeurs. On réalise cela à l’aide d’une expression régulière :

Route::get('{n}', function($n) {
return 'Je suis la page ' . $n . ' !';
})->where('n', '[1-3]');

Maintenant je peux affirmer que les comportements sont identiques ! Mais il nous faudra
régler le problème des routes non prévues.

Route nommée
Il est parfois utile de nommer une route, par exemple pour générer une url ou pour
effectuer une redirection. La syntaxe pour nommer une route est celle-ci :

Route::get('/', function() {
return 'Je suis la page d\'accueil !';
})->name('home');

Par exemple pour générer l’url qui correspond à cette route on peut utiliser l’helper route :

route('home')

Ce qui va générer l’url de base du site dans ce cas : http://monsite.test.

Un avantage à utiliser des routes nommées est qu’on peut réorganiser les urls d’un site
sans avoir à modifier beaucoup de code.

Nous verrons des cas d’utilisation de routes nommées dans les prochains chapitres.

Ordre des routes

7/8
Une chose importante à connaître est l’ordre des routes !

Lisez bien ceci pour vous éviter des heure de recherches et de prises de tête . La règle
est :

Les routes sont analysée dans leur ordre dans le fichier des routes.

Regardez ces deux routes :

Route::get('{n}', function($n) {
return 'Je suis la page ' . $n . ' !';
});

Route::get('contact', function() {
return "C'est moi le contact.";
});

Que pensez-vous qu’il va se passer avec http://monsite.test/contact ?

Je vous laisse deviner et tester et surtout bien retenir ce fonctionnement !

On peut aussi grouper des routes pour simplifier la syntaxe mais nous verrons ça plus
tard…

En résumé
Laravel possède un fichier .htaccess pour simplifier l’écriture des url.
Le système de routage est simple et explicite.
On peut prévoir des paramètres dans les routes.
On peut contraindre un paramètre à correspondre à une expression régulière.
On peut nommer une route pour faciliter la génération des url ‌et les redirections.

La documentation officielle de cette partie est ici.

8/8
Cours Laravel 9 – les bases – les réponses
laravel.sillo.org/cours-laravel-9-les-bases-les-reponses/

10 février 2022

Nous avons vu précédemment comment la requête qui arrive est traitée par les routes.
Voyons maintenant les réponses que nous pouvons renvoyer au client. Nous allons voir
le système des vues de Laravel avec la possibilité de transmettre des paramètres. Nous
verrons aussi comment créer des templates avec l’outil Blade.

Les réponses automatiques


Nous avons déjà construit des réponses lorsque nous avons vu le routage au chapitre
précédent mais nous n’avons rien fait de spécial pour cela, juste renvoyé une chaîne de
caractères comme réponse. Par exemple si nous utilisons cette route :

Route::get('test', function () {
return 'un test';
});

Nous interceptons l’url http://monsite/test et nous renvoyons la chaîne de caractères


« un test ». Mais évidemment Laravel en coulisse construit une véritable réponse HTTP.
Voyons cela :

On se rend compte qu’on a une requête complète avec ses headers mais nous ne
pouvons pas intervenir sur ces valeurs. Remarquez au passage qu’on a des cookies, on
en reparlera lorsque nous verrons les sessions.

1/9
Le content-type indique le type MIME du document retourné, pour que le navigateur
sache quoi faire du document en fonction de la nature de son contenu. Par exemple :

text/html : page Html classique


text/plain : simple texte sans mise en forme
application/pdf : fichier pdf
application/json : données au format JSON
application/octet-stream : téléchargement de fichier

Pour une liste exhaustive c’est ici.

Maintenant voyons ce que ça donne si on renvoie un tableau :

Route::get('test', function () {
return ['un', 'deux', 'trois'];
});

Cette fois on reçoit une réponse JSON :

Donc si vous voulez renvoyer du JSON il suffit de retourner un tableau


et Laravel s’occupe de tout !

Construire une réponse


Le fonctionnement automatique c’est bien mais des fois on veut imposer des valeurs.
Dans ce cas il faut utiliser une classe de Laravel pour construire une réponse. Comme la
plupart du temps on a un helper qui nous évite de déclarer la classe en question (en
l’occurrence c’est la classe Illuminate\Http\Response qui hérite de celle de Symfony
: Symfony\Component\HttpFoundation\Response).

Route::get('test', function () {
return response('un test', 206)->header('Content-Type', 'text/plain');
});

Cette fois j’impose un code (206 : envoi partiel) et un type MIME (text/plain) :

Dans le protocole HTTP il existe des codes pour spécifier les réponses. Ces codes sont
classés par grandes catégories. Voici les principaux :

200 : requête exécutée avec succès,

2/9
403 : ressource interdite,
404 : la ressource demandée n’a pas été
trouvée,
503 : serveur indisponible.

Pour une liste complète c’est ici.

En fait vous aurez rarement la nécessité de


préciser les headers parce que Laravel s’en
charge très bien, mais vous voyez que c’est
facile à faire.

On peut aussi ajouter un cookie avec la méthode cookie.

Les vues
Dans une application réelle vous retournerez rarement la réponse directement à partir
d’une route, vous passerez au moins par une vue. Dans sa version la plus simple une
vue est un simple fichier avec du code Html :

<!doctype html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Ma première vue</title>
</head>
<body>
Je suis une vue !
</body>
</html>

Il faut enregistrer cette vue (j’ai choisi le nom « vue1 ») dans le dossier resources/views
avec l’extension php :

Même si vous ne mettez que du code Html dans une vue vous
devez l’enregistrer avec l’extension php.

On peut appeler cette vue à partir d’une route avec ce code :

Route::get('/', function() {
return view('vue1');
});

Pour que ça fonctionne commentez ou supprimez la route de


base de l’installation.

Je vous rappelle la belle sémantique de Laravel qui se lit comme de la prose : je retourne
(return) une vue (view) à partir du fichier de vue « vue1 ».

Voici une illustration du processus :

3/9
Vue paramétrée
En général on a des informations à transmettre à une vue, voyons à présent comment
mettre cela en place. Supposons que nous voulions répondre à ce type de requête :

http://monsite.fr/article/n

Le paramètre n pouvant prendre une valeur numérique . Voyons comment cette url est
constituée :

la base de l’url est constante pour le site, quelle que soit la requête,
la partie fixe ici correspond aux articles,
la partie variable correspond au numéro de l’article désiré (le paramètre).

Route

4/9
Il nous faut une route pour intercepter ces urls :

Route::get('article/{n}', function($n) {
return view('article')->with('numero', $n);
})->where('n', '[0-9]+');

On transmet la variable à la vue avec la méthode with.

Vue
Il ne nous reste plus qu’à créer la vue article.php dans le dossier resources/views :

<!doctype html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Les articles</title>
</head>
<body>
<p>C'est l'article n° <?php echo $numero ?></p>
</body>
</html>

Pour récupérer le numéro de l’article on utilise la variable $numero.

Voici une schématisation du fonctionnement :

Il existe une méthode « magique » pour transmettre un paramètre, par exemple pour
transmettre la variable $numero comme je l’ai fait ci-dessus on peut écrire le code ainsi :

5/9
return view('article')->withNumero($n);

Il suffit de concaténer le nom de la variable au mot clé with.

On peut aussi transmettre un tableau comme paramètre :

return view('article', ['numero' => $n]);

Blade
Laravel possède un moteur de template élégant nommé Blade qui nous permet de faire
pas mal de choses. La première est de nous simplifier la syntaxe. Par exemple au lieu de
la ligne suivante que nous avons prévue dans la vue précédente :

<p>C'est l'article n° <?php echo $numero ?></p>

On peut utiliser cette syntaxe avec Blade :

<p>C'est l'article n° {{ $numero }}</p>

Tout ce qui se trouve entre les doubles accolades est interprété comme du code PHP.
Mais pour que ça fonctionne il faut indiquer à Laravel qu’on veut utiliser Blade pour cette
vue. Ça se fait simplement en modifiant le nom du fichier :

Il suffit d’ajouter « blade » avant l’extension « php ». Vous


pouvez tester l’exemple précédent avec ces modifications et
vous verrez que tout fonctionne parfaitement avec une syntaxe
épurée.

Il y a aussi la version avec la syntaxe {!! … !!}. La différence


entre les deux versions est que le texte entre les doubles
accolades est échappé ou purifié (on utilise en interne
htmlspecialchars pour éviter les attaques XSS). Donc soyez
prudent si vous utilisez la syntaxe {!! … !!} !

Un template
Une fonction fondamentale de Blade est de permettre de faire du templating, c’est à dire
de factoriser du code de présentation. Poursuivons notre exemple en complétant notre
application avec une autre route chargée d’intercepter des urls pour des factures. Voici la
route :

Route::get('facture/{n}', function($n) {
return view('facture')->withNumero($n);
})->where('n', '[0-9]+');

Et voici la vue :

6/9
<!doctype html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Les factures</title>
</head>
<body>
<p>C'est la facture n° {{ $numero }}</p>
</body>
</html>

On se rend compte que cette vue est pratiquement la même que celle pour les articles. Il
serait intéressant de placer le code commun dans un fichier.

C’est justement le but d’un template d’effectuer cette opération.

Voici le template :

<!doctype html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>@yield('titre')</title>
</head>
<body>
@yield('contenu')
</body>
</html>

J’ai repris le code commun et prévu deux emplacements repérés par le mot clé @yield et
nommés « titre » et « contenu ». Il suffit maintenant de modifier les deux vues. Voilà pour
les articles :

@extends('template')

@section('titre')
Les articles
@endsection

@section('contenu')
<p>C'est l'article n° {{ $numero }}</p>
@endsection

Et voilà pour les factures :

@extends('template')

@section('titre')
Les factures
@endsection

@section('contenu')
<p>C'est la facture n° {{ $numero }}</p>
@endsection

7/9
Dans un premier temps on dit qu’on veut utiliser le template avec @extends et le nom du
template « template ». Ensuite on remplit les zones prévues dans le template grâce à la
syntaxe @section en précisant le nom de l’emplacement et en fermant avec
@endsection. Voici un schéma pour bien visualiser tout ça avec les articles :

Au niveau du dossier des vues on a donc les trois fichiers :

Blade permet de faire bien d’autres choses, nous verrons cela


dans les prochains chapitres.

Lorsqu’elles deviendront nombreuses on organisera nos vues


dans des dossiers.

Les redirections
Souvent il ne faut pas envoyer directement la réponse mais rediriger sur une autre url.
Pour réaliser cela on a l’helper redirect :

8/9
return redirect('facture');

Ici on redirige sur l’url http://monsite/facture.

On peut aussi rediriger sur une route nommée. Par exemple vous avez cette route :

Route::get('users/action', function() {
return view('users.action');
})->name('action');

Cette route s’appelle action et elle correspond à l’url http://monsite/users/action. On


peut simplement rediriger sur cette route avec cette syntaxe :

return redirect()->route('action');

Si la route comporte un paramètre (ou plusieurs) on peut aussi lui assigner une valeur.
Par exemple avec cette route :

Route::get('users/action/{type}', function($type) {
return view('users.action');
})->name('action');

On peut rediriger ainsi en renseignant le paramètre :

return redirect()->route('action', ['type' => 'play']);

Des fois on veut tout simplement recharger la même page, par exemple lors de la
soumission d’un formulaire avec des erreurs dans la validation des données, il suffit alors
de faire :

return back();

On verra de nombreux exemples de redirections dans les prochains chapitres.

En résumé
Laravel construit automatiquement des réponses HTTP lorsqu’on retourne une
chaîne de caractère ou un tableau.
Laravel offre la possibilité de créer des vues.
Il est possible de transmettre simplement des paramètres aux vues.
L’outil Blade permet de créer des templates et d’optimiser ainsi le code des vues.
On peut facilement effectuer des redirections avec transmission éventuelle de
paramètres.‌

La documentation officielle de cette partie est ici.

9/9
Cours Laravel 9 – les bases – artisan et les contrôleurs
laravel.sillo.org/cours-laravel-9-les-bases-artisan-et-les-controleurs/

10 février 2022

Nous avons vu le cycle d’une requête depuis son arrivée, son traitement par les routes et
sa réponse avec des vues qui peuvent être boostées par Blade. Avec tous ces éléments
vous pourriez très bien réaliser un site web complet, mais Laravel offre encore bien des
outils performants que je vais vous présenter.

Pour correctement organiser son code dans une application Laravel il faut bien répartir
les tâches. Dans les exemples vus jusqu’à présent j’ai renvoyé une vue à partir d’une
route, vous ne ferez pratiquement jamais cela dans une application réelle (même si
personne ne vous empêchera de le faire ! ). Les routes sont juste un système d’aiguillage
pour trier les requêtes qui arrivent.

Mais alors qui s’occupe de la suite ?

Eh bien ce sont les contrôleurs, le sujet de ce chapitre.

Nous allons aussi découvrir l’outil Artisan qui est la boîte à outil du développeur pour
Laravel.

Artisan
Lorsqu’on construit une application avec Laravel on a de nombreuses tâches à accomplir,
comme créer des classes, vérifier les routes…

C’est là qu’intervient Artisan, le compagnon indispensable. Il fonctionne en ligne de


commande, donc à partir de la console. Il suffit de se positionner dans le dossier racine et
d’utiliser la commande :

php artisan

pour obtenir la liste de ses possibilités, en voici un extrait :

1/7
Nous verrons peu à peu les principales commandes disponibles. Il y en a une pour
connaître les routes prévues dans le code. Voici ce que ça donne avec une nouvelle
installation :

On sait que la seule route au départ est celle-ci :

Route::get('/', function () {
return view('welcome');
});

Elle correspond à la première ligne de la liste avec une closure.

Mais à quoi correspondent les autres routes ?

On a vu qu’il y a 4 fichiers de routes, en particulier on a celui pour les API :

Lui aussi comporte une route par défaut :

Route::middleware('auth:sanctum')->get('/user', function
(Request $request) {
return $request->user();
});

Une API est un système sans état qui se contente de fournir des
ressources. Je ne vais pas évoquer cette possibilité dans ce cours.
Vous pouvez supprimer cette route pour éviter de polluer votre liste de routes.

Je parlerai des middlewares dans un prochain chapitre.

Les contrôleurs

2/7
Rôle

La tâche d’un contrôleur est de réceptionner une requête (qui a déjà été sélectionnée par
une route) et de définir la réponse appropriée, rien de moins et rien de plus. Voici une
illustration du processus :

Constitution

Pour créer un contrôleur, nous allons utiliser Artisan. Dans la console entrez cette
commande :

php artisan make:controller WelcomeController

Si tout se passe bien vous allez trouver le contrôleur ici :

Avec ce code :

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class WelcomeController extends Controller


{
//
}

Ajoutez la méthode index :

3/7
<?php
...
class WelcomeController extends Controller
{
public function index()
{
return view('welcome');
}
}

Analysons un peu le code :

on trouve en premier l’espace de nom (App\Http\Controllers),


le contrôleur hérite de la classe Controller qui se trouve dans le même dossier et
qui permet de factoriser des actions communes à tous les contrôleurs,
on trouve enfin la méthode index qui renvoie quelque chose que maintenant vous
connaissez : une vue, en l’occurrence « welcome » dont nous avons déjà parlé.
Donc si j’appelle cette méthode je retourne la vue « welcome » au client.

Liaison avec les routes

Maintenant la question qu’on peut se poser est : comment s’effectue la liaison entre les
routes et les contrôleurs ?

Ouvrez le fichier des routes et entrez ce code (et commentez ou supprimez la route de
base) :

use App\Http\Controllers\WelcomeController;

Route::get('/', [WelcomeController::class, 'index']);

Si vous avez utilisé des précédentes versions de Laravel (avant la version 8), la syntaxe
peut vous surprendre, en effet le préfixage automatique pour l’espace de nom des
contrôleurs a été supprimé. Toutes les informations correspondantes sont ici.

Maintenant avec l’url de base vous devez retrouver la page d’accueil de Laravel :

4/7
Voici une visualisation de la liaison entre la route et le contrôleur :

On voit qu’au niveau de la route, il suffit de désigner le contrôleur et la méthode dans un


tableau.

Route nommée
De la même manière que nous pouvons nommer une route classique, on peut aussi
donner un nom à une route qui pointe une méthode de contrôleur :

5/7
Route::get('/', [WelcomeController::class, 'index'])->name('home');

Si on utilise Artisan pour lister les routes (php artisan route:list) :

On voit bien que l’action est faite par le contrôleur avec précision de la méthode à utiliser.
On trouve aussi le nom de la route (home).

Utilisation d’un contrôleur


Voyons à présent un exemple pratique de mise en œuvre d’un contrôleur. On va
conserver notre exemple avec les articles, mais désormais traité avec un contrôleur. On
conserve le même template et les mêmes vues :

On va créer un contrôleur (entraînez-vous à utiliser Artisan)


pour les articles :

<?php

namespace App\Http\Controllers;

class ArticleController extends Controller


{
public function show($n)
{
return view('article')->with('numero', $n);
}
}

Dans ce contrôleur, on a une méthode show chargée de générer la vue. Il ne nous reste
plus qu’à créer la route :

use App\Http\Controllers\ArticleController;

Route::get('article/{n}', [ArticleController::class, 'show'])->where('n', '[0-


9]+');

Voici une illustration du fonctionnement avec ce contrôleur :

6/7
Notez qu’on pourrait utiliser la méthode « magique » pour la transmission du paramètre à
la vue :

return view('article')->withNumero($n);

En résumé
Les contrôleurs servent à réceptionner les requêtes triées par les routes et à fournir
une réponse au client.
Artisan permet de créer facilement un contrôleur.
Il est facile d’appeler une méthode de contrôleur à partir d’une route.
On peut nommer une route qui pointe vers une méthode de contrôleur.‌

7/7
Cours Laravel 9 – les bases – formulaires et
middlewares
laravel.sillo.org/cours-laravel-9-les-bases-formulaires-et-middlewares/

10 février 2022

Dans bien des circonstances, le client envoie des informations au serveur. La situation la
plus générale est celle d’un formulaire. Nous allons voir dans ce chapitre comment créer
facilement un formulaire avec Laravel, comment réceptionner les entrées et nous
améliorerons notre compréhension du routage.

Nous verrons aussi l’importante notion de middleware.

Scénario et routes
Nous allons envisager un petit scénario avec une demande de formulaire de la part du
client, sa soumission et son traitement :

On va donc avoir besoin de deux routes :

une pour la demande du formulaire avec une méthode get,


une pour la soumission du formulaire avec une méthode post.

On va donc créer ces deux routes dans le fichier routes/web.php :

use App\Http\Controllers\UsersController;

Route::get('users', [UsersController::class, 'create']);


Route::post('users', [UsersController::class, 'store']);

Jusque-là on avait vu seulement des routes avec le verbe get, on a maintenant aussi une
route avec le verbe post.

Les urls correspondantes sont donc :

http://monsite.fr/users avec la méthode get,


http://monsite.fr/users avec la méthode post.

1/8
Donc on a la même url, seul le verbe diffère. Voici le scénario schématisé avec les urls :

Les middlewares
Les middlewares sont chargés de filtrer les requêtes HTTP qui arrivent dans l’application,
ainsi que celles qui en partent (beaucoup moins utilisé). Le cas le plus classique est celui
qui concerne la vérification de l’authentification d’un utilisateur pour qu’il puisse accéder à
certaines ressources. On peut aussi utiliser un middleware par exemple pour démarrer la
gestion des sessions.

Voici un schéma pour illustrer cela :

On peut avoir en fait plusieurs middlewares en pelures d’oignon, chacun effectue son
traitement et transmet la requête ou la réponse au suivant.

Ainsi dès qu’il y a un traitement à faire à l’arrivée des requêtes (ou à leur départ) un
middleware est tout indiqué.

Laravel peut servir comme application « web » ou comme « api ». Dans le premier cas,
on a besoin :

de gérer les cookies,

2/8
de gérer une session,
de gérer la protection CSRF (dont je parle plus loin dans ce chapitre)…

Si vous regardez dans le fichier app/Http/Kernel.php :

protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],

'api' => [
//
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];

On trouve les deux middlewares de groupes (ils rassemblent plusieurs middlewares) «


web » et « api ». On voit que dans le premier cas, on active bien les cookies, les sessions
et la vérification CSRF.

Par défaut toutes les routes que vous entrez dans le fichier routes/web.php sont
incluses dans le groupe « web ». Si vous regardez dans le provider
app/Providers/RouteServiceProvider.php vous trouvez cette inclusion :

public function boot()


{
...

$this->routes(function () {
...

Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
});
}

Le formulaire
Pour faire les choses correctement nous allons prévoir un template
resources/views/template.blade.php :

3/8
<!doctype html>
<html lang="fr">
<head>
<meta charset="UTF-8">
</head>
<body>
@yield('contenu')
</body>
</html>

Et une vue resources/views/infos.blade.php qui utilise ce template :

@extends('template')

@section('contenu')
<form action="{{ url('users') }}" method="POST">
@csrf
<label for="nom">Entrez votre nom : </label>
<input type="text" name="nom" id="nom">
<input type="submit" value="Envoyer !">
</form>
@endsection

Je vous parle plus loin de la signification de la syntaxe @csrf.

L’helper url est utilisé pour générer l’url complète pour l’action du formulaire. Pour une
route nommée on pourrait utiliser l’helper route.

Le résultat sera un formulaire sans fioriture :

Le contrôleur
Il ne nous manque plus que le contrôleur pour faire fonctionner tout ça. Utilisez Artisan
pour générer un contrôleur :

php artisan make:controller UsersController

Vous devez le retrouver ici :

Modifiez ensuite son code ainsi :

4/8
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class UsersController extends Controller


{
public function create()
{
return view('infos');
}

public function store(Request $request)


{
return 'Le nom est ' . $request->input('nom');
}
}

Le contrôleur possède deux méthodes :

la méthode create qui reçoit l’url http://monsite.fr/users avec le verbe get et qui
retourne le formulaire,
la méthode store qui reçoit l’url http://monsite.fr/users avec le verbe post et qui
traite les entrées.

Pour la première méthode, il n’y a rien de nouveau et je vous renvoie aux chapitres
précédents si quelque chose ne vous paraît pas clair. Par contre, nous allons nous
intéresser à la seconde méthode.

Dans cette seconde méthode, on veut récupérer l’entrée du client. Encore une fois la
syntaxe est limpide : on souhaite dans la requête (request) les entrées (input) récupérer
celle qui s’appelle nom.

Si vous faites fonctionner tout ça vous devez finalement obtenir l’affichage du nom saisi.
Voici une schématisation du fonctionnement qui exclut les routes pour simplifier :

5/8
(1) le client envoie la requête de demande du formulaire qui est transmise au contrôleur
par la route (non représentée sur le schéma),
(2) le contrôleur crée la vue « infos »,
(3) la vue « infos » crée le formulaire,
(4) le formulaire est envoyé au client,
(5) le client soumet le formulaire, le contrôleur reçoit la requête de soumission par
l’intermédiaire de la route (non représentée sur le schéma),
(6) le contrôleur génère la réponse,
(7) la réponse est envoyée au client.

La protection CSRF
On a vu que le formulaire généré par Laravel comporte une ligne un peu particulière :

@csrf

Si on regarde le code généré on trouve quelque chose dans ce genre :

<input type="hidden" name="_token"


value="iIjW9PMNsV6VKT2sIc16ShoTf6SdYVZolVUGsxDI">

À quoi cela sert-il ?

6/8
Tout d’abord CSRF signifie Cross-Site Request Forgery. C’est une attaque qui consiste
à faire envoyer par un client une requête à son insu. Cette attaque est relativement
simple à mettre en place et consiste à envoyer à un client authentifié sur un site un script
dissimulé (dans une page web ou un email) pour lui faire accomplir une action à son insu.

Pour se prémunir contre ce genre d’attaque Laravel génère une valeur aléatoire (token)
associée au formulaire de telle sorte qu’à la soumission cette valeur est vérifiée pour être
sûr de l’origine.

Vous vous demandez peut-être où se trouve ce middleware CSRF ?

Il est bien rangé dans le dossier app/Http/Middleware :

Pour tester l’efficacité de cette vérification


essayez un envoi de formulaire sans le token
en modifiant ainsi la vue :

@extends('template')

@section('contenu')
<form action="{{ url('users') }}"
method="POST">
<label for="nom">Entrez votre nom
: </label>
<input type="text" name="nom"
id="nom">
<input type="submit"
value="Envoyer !">
</form>
@endsection

Vous tombez sur cette page à la soumission :

Comme le token n’est pas bon, Laravel en conclut qu’il a


expiré parce qu’évidemment il a une durée de validité
limitée liée à la session.

Analysons un peu mieux cette réponse :

On voit une réponse 419 qui ne fait pas


encore partie du standard HTTP mais qui
est de plus en plus utilisée comme
alternative à 401.

Page d’erreur personnalisée

7/8
La page d’erreur sur laquelle on est tombée n’est pas très explicite et en anglais. On
pourrait la vouloir en français, ou tout simplement en changer le style. Mais où se trouve
le code de cette page ? Regardez dans le dossier vendor :

Vous trouvez un dossier des vues par défaut de Laravel pour


les erreurs les plus classiques. Vous n’allez évidemment pas
modifier directement ces vues dans le dossier vendor ! Mais
vous pouvez surcharger ces vues en créant un dossier
resources/views/errors et en copiant le fichier concerné :

On a alors de base ce code :

@extends('errors::minimal')

@section('title', __('Page Expired'))


@section('code', '419')
@section('message', __('Page Expired'))

Adaptez le code selon vos goûts. Je n’ai pas encore parlé des
possibilités de Laravel au niveau des langues. La syntaxe
__(…) permet de faire en sorte que le même code serve pour
plusieurs langues. Mais si vous n’avez que le français alors
vous pouvez directement changer :

@extends('errors::minimal')

@section('title', 'Page expirée')


@section('code', '419')
@section('message', 'La page a expiré. Veuillez
recommencer.')

Vous pouvez intervenir ainsi pour tous les codes d’erreur.

En résumé
Laravel permet de créer des routes avec différents verbes : get, post…
Un middleware permet de filtrer les requêtes.
Les entrées du client sont récupérées dans la requête.
On peut se prémunir contre les attaques CSRF.
On peut personnaliser les pages d’erreur par défaut de Laravel et même en créer
de nouvelles.

8/8
Cours Laravel 9 – les bases – la validation
laravel.sillo.org/cours-laravel-9-les-bases-la-validation/

11 février 2022

Nous avons vu dans le chapitre précédent un scénario mettant en œuvre un formulaire.


Nous n’avons imposé aucune contrainte sur les valeurs transmises. Dans une application
réelle, il est toujours nécessaire de vérifier que ces valeurs correspondent à ce qu’on
attend. Par exemple un nom doit comporter uniquement des caractères alphabétiques et
avoir une longueur maximale ou minimale, une adresse email doit correspondre à un
certain format…

Il faut donc mettre en place des règles de validation. En général on procède à une
première validation côté client pour éviter de faire des allers-retours avec le serveur. Mais
quelle que soit la pertinence de cette validation côté client elle n’exonère pas d’une
validation côté serveur.

On ne doit jamais faire confiance à des données qui arrivent sur le serveur !

Dans l’exemple de ce chapitre je ne prévoirai pas de validation côté client, d’une part ce
n’est pas mon propos, d’autre part elle masquerait la validation côté serveur pour les
tests.

Scénario et routes
Voici le scénario que je vous propose pour ce chapitre :

1/12
1. le client demande le formulaire de contact,
2. le contrôleur génère le formulaire,
3. le contrôleur envoie le formulaire,
4. le client remplit le formulaire et le soumet,
5. le contrôleur teste la validité des informations et là on a deux possibilités :

en cas d’échec on renvoie le formulaire au client en l’informant des erreurs et en


conservant ses entrées correctes,
en cas de réussite on envoie un message de confirmation au client .

Le contrôleur
On va encore utiliser Artisan pour générer le contrôleur :

php artisan make:controller ContactController

Modifiez le code par défaut pour en arriver à celui-ci :

2/12
<?php

namespace App\Http\Controllers;

class ContactController extends Controller


{
public function create()
{
return 'vue contact';
}

public function store()


{
return 'vue confirm';
}
}

La méthode create ne présente aucune nouveauté par rapport à ce qu’on a vu au


chapitre précédent. On se contentera de renvoyer la vue contact qui comporte le
formulaire.

C’est dans la méthode store qu’on traitera le formulaire.

Routes

On va donc avoir besoin de 2 routes :

use App\Http\Controllers\ContactController;

Route::get('contact', [ContactController::class, 'create']);


Route::post('contact', [ContactController::class, 'store']);

On aura une seule url (avec verbe « get » pour demander le formulaire et verbe « post »
pour le soumettre) :

http://monsite.fr/contact

Vérifiez avec la commande php artisan route:list que tout est correct :

On a bien nos deux routes avec l’url correct, le bon contrôleur avec les méthodes
prévues et le middleware web appliqué aux deux routes.

La requête de formulaire
Il y a plusieurs façons d’effectuer la validation avec Laravel mais la plus simple et
élégante consiste à utiliser une requête de formulaire (Form request).

Nous avons déjà utilisé Artisan qui permet d’effectuer de nombreuses opérations et nous
allons encore avoir besoin de lui pour créer une requête de formulaire :

3/12
php artisan make:request ContactRequest

Comme par défaut le dossier n’existe pas il est créé en même temps que la classe :

Voyons le code généré :

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class ContactRequest extends FormRequest


{
/**
* Determine if the user is authorized to make this
request.
*
* @return bool
*/
public function authorize()
{
return false;
}

/**
* Get the validation rules that apply to the
request.
*
* @return array
*/
public function rules()
{
return [
//
];
}
}

La classe générée comporte 2 méthodes :

authorize : pour effectuer un contrôle de sécurité éventuel sur l’identité ou les droits
de l’émetteur,
rules : pour les règles de validation.

On va arranger le code pour notre cas :

4/12
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class ContactRequest extends FormRequest


{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}

/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'nom' => 'bail|required|between:5,20|alpha',
'email' => 'bail|required|email',
'message' => 'bail|required|max:250'
];
}
}

Au niveau de la méthode rules on retourne un tableau qui contient des clés qui
correspondent aux champs du formulaire. Vous retrouvez le nom, l’email et le message.
Les valeurs contiennent les règles de validation. Comme il y en a chaque fois plusieurs
elles sont séparées par le signe « | ». Voyons les différentes règles prévues :

bail : on arrête de vérifier dès qu’une règle n’est pas respectée,


required : une valeur est requise, donc le champ ne doit pas être vide,
between : nombre de caractères entre une valeur minimale et une valeur
maximale,
alpha : on n’accepte que les caractères alphabétiques,
email : la valeur doit être une adresse email valide.

Au niveau de la méthode authorize je me suis contenté de renvoyer true parce que nous
ne ferons pas de contrôle supplémentaire à ce niveau.

Vous pouvez trouver toutes les règles disponibles dans la documentation. Vous verrez
que la liste est longue !

On va utiliser cette requête de formulaire dans notre contrôleur :

5/12
<?php

namespace App\Http\Controllers;

use App\Http\Requests\ContactRequest;

class ContactController extends Controller


{
...

public function store(ContactRequest $request)


{
...
}
}

Les vues

Le template

Pour ce chapitre je vais créer un template réaliste avec l’utilisation de Bootstrap pour
alléger le code. Voici le code de ce template (resources/views/template.blade.php) :

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Mon joli site</title>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css"
rel="stylesheet" integrity="sha384-
Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi"
crossorigin="anonymous">
<style>
textarea { resize: none; }
.card { width: 25em; }
</style>
</head>
<body>
@yield('contenu')
</body>
</html>

J’ai prévu l’emplacement @yield nommé « contenu » pour recevoir les pages du site,
pour notre exemple on aura seulement la page de contact et celle de la confirmation.

La vue de contact
La vue de contact va contenir essentiellement un formulaire
(resources/views/contact.blade.php) :

6/12
@extends('template')

@section('contenu')
<br>
<div class="container">
<div class="row card text-white bg-dark">
<h4 class="card-header">Contactez-moi</h4>
<div class="card-body">
<form action="{{ url('contact') }}" method="POST">
@csrf
<div class="mb-3">
<input type="text" class="form-control @error('nom') is-
invalid @enderror" name="nom" id="nom" placeholder="Votre nom" value="{{
old('nom') }}">
@error('nom')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<input type="email" class="form-control @error('email')
is-invalid @enderror" name="email" id="email" placeholder="Votre email" value="{{
old('email') }}">
@error('email')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<textarea class="form-control @error('message') is-
invalid @enderror" name="message" id="message" placeholder="Votre message">{{
old('message') }}</textarea>
@error('message')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<button type="submit" class="btn btn-secondary">Envoyer !
</button>
</form>
</div>
</div>
</div>
@endsection

Cette vue étend le template vu ci-dessus et renseigne la section « contenu ». Je ne


commente pas la mise en forme spécifique à Bootstrap.

En cas de réception du formulaire suite à des erreurs on reçoit une variable $errors qui
contient un tableau avec comme clés les noms des contrôles et comme valeurs les textes
identifiant les erreurs.

La variable $errors est générée systématiquement pour toutes les vues.

On pourrait accéder à cette variable et faire un traitement spécifique pour afficher des
erreurs mais Blade nous propose une possibilité plus élégante avec la directive @error.

7/12
Enfin en cas d’erreur de validation les anciennes valeurs saisies sont retournée au
formulaire et récupérées avec l’helper old :

value="{{ old('nom') }}"

Dans le contrôleur on va changer le code pour retourner cette vue :

class ContactController extends Controller


{
public function create()
{
return view('contact');
}

Au départ le formulaire se présentera ainsi :

Après une soumission et renvoi avec des erreurs il pourra se présenter ainsi :

8/12
Les messages en français

Par défaut les messages sont en anglais. Pour avoir ces textes en français vous devez
utiliser le package ici.

Pour faire les choses simplement commencez par cette installation :

composer require laravel-lang/publisher laravel-lang/lang laravel-lang/attributes


--dev

Puis intallez le français :

php artisan lang:add fr

Vous devriez obtenir ceci :

Ensuite changez cette ligne dans le fichier config/app.php :

'locale' => 'fr',

Vous devriez avoir vos messages en français :

9/12
La vue de confirmation
Pour la vue de confirmation (resources/views/confirm.blade.php) le code est plus
simple et on utilise évidemment le même template :

@extends('template')

@section('contenu')
<br>
<div class="container">
<div class="row card text-white bg-dark">
<h4 class="card-header">Contactez-moi</h4>
<div class="card-body">
<p class="card-text">Merci. Votre message a été transmis à
l'administrateur du site. Vous recevrez une réponse rapidement.</p>
</div>
</div>
</div>
@endsection

On modifie le code du contrôleur pour renvoyer cette vue :

class ContactController extends Controller


{
...

public function store(ContactRequest $request)


{
return view('confirm');
}
}

10/12
Ce qui donne cette apparence :

D’autre façons d’effectuer la validation


Si vous n’appréciez pas les requêtes de formulaire et leur côté « magique » vous pouvez
effectuer la validation directement dans le contrôleur avec la méthode validate. Voici le
contrôleur modifié en conséquence :

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ContactController extends Controller


{
public function create()
{
return view('contact');
}

public function store(Request $request)


{
$this->validate($request, [
'nom' => 'bail|required|between:5,20|alpha',
'email' => 'bail|required|email',
'message' => 'bail|required|max:250'
]);

return view('confirm');
}
}

Cette fois on injecte dans la méthode store directement la requête


(Illuminate\Http\Request). Le fonctionnement est exactement le même.

Si cette méthode validate est encore trop abstraite à votre goût vous pouvez détailler les
opérations :

11/12
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;

class ContactController extends Controller


{
public function create()
{
return view('contact');
}

public function store(Request $request)


{
$validator = Validator::make($request->all(), [
'nom' => 'bail|required|between:5,20|alpha',
'email' => 'bail|required|email',
'message' => 'bail|required|max:250'
]);

if ($validator->fails()) {
return back()->withErrors($validator)->withInput();
}

return view('confirm');
}
}

On utilise la façade Validator en précisant toutes les entrée ($request->all()) et les


règles de validation. Ensuite si la validation échoue (fails) on renvoie (back) le formulaire
avec les erreurs (withErrors) et les valeurs entrées (withInput) pour pouvoir les afficher
dans le formulaire.

Mais pourquoi se compliquer la vie quand on dispose de fonctionnalités plus simples et


élégantes ?

En résumé
La validation est une étape essentielle de vérification des entrées du client.
On dispose de nombreuses règles de validation.
Le validateur génère des erreurs explicites à afficher au client.
Pour avoir les textes des erreurs en français il faut aller chercher les traductions et
les placer dans le bon dossier.
Les requêtes de formulaires (Form request) permettent d’effectuer la validation de
façon simple et élégante.
Il y a plusieurs façons d’effectuer la validation à adapter selon les goûts‌et les
circonstances.

12/12
Cours Laravel 9 – les bases – envoyer un email
laravel.sillo.org/cours-laravel-9-les-bases-envoyer-un-email/

11 février 2022

Laravel utilisait jusqu’à sa version 8 le célèbre composant SwiftMailer pour l’envoi des
emails. Cela change dans la version 9 qui utilise désormais le composant Symfony
Mailer. Dans ce chapitre, nous allons prolonger l’exemple précédent de la prise de
contact en ajoutant l’envoi d’un email à l’administrateur du site lorsque quelqu’un
utilisateur soumet une demande de contact.

On va donc prendre le code tel qu’on l’a laissé lors du précédent chapitre et le compléter
en conséquence.

On verra plus tard que Laravel propose aussi un système complet de notification qui
permet entre autres l’envoi d’emails.

Le scénario
Le scénario est donc le même que pour le précédent chapitre avec l’ajout d’une action :

1/10
On va avoir les mêmes routes et vues, c’est uniquement au niveau du contrôleur que le
code va évoluer pour intégrer cette action complémentaire.

Configuration
Si vous regardez dans le fichier .env vous trouvez une section qui concerne les emails :

MAIL_MAILER=smtp
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME="${APP_NAME}"

Les premières valeurs correspondent au prestataire utilisé.

Au niveau du driver, le plus classique est certainement le SMTP mais vous pouvez aussi
utiliser sendmail, mailgun (gratuit jusqu’à 10 000 envois par mois), Postmark, ses …

2/10
Vous devez correctement renseigner les paramètres pour que ça fonctionne selon votre
contexte (en local avec le SMTP de votre prestataire, en production avec la fonction mail
de PHP ou d’un autre système…).

La classe Mailable
Avec Laravel, pour envoyer un email, il faut passer par la création d’une classe « mailable
». Encore une fois, c’est Artisan qui va nous permettre de créer notre classe :

php artisan make:mail Contact

Comme le dossier n’existe pas il est créé en même temps que le fichier de la classe :

Par défaut, on a ce code :

<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class Contact extends Mailable


{
use Queueable, SerializesModels;

/**
* Create a new message instance.
*
* @return void
*/
public function __construct()
{
//
}

/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this->view('view.name');
}
}

Tout se passe dans la méthode build. On voit qu’on retourne une vue, celle-ci doit
comporter le code pour le contenu de l’email.

On commence par changer le nom de la vue :

3/10
return $this->view('emails.contact');

On va créer le dossier emails et cette vue (resources/views/emails/contact.blade.php)


:

Avec ce code :

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
</head>
<body>
<h2>Prise de contact sur mon beau site</h2>
<p>Réception d'une prise de contact avec les éléments
suivants :</p>
<ul>
<li><strong>Nom</strong> : {{ $contact['nom'] }}
</li>
<li><strong>Email</strong> : {{ $contact['email'] }}
</li>
<li><strong>Message</strong> : {{
$contact['message'] }}</li>
</ul>
</body>
</html>

Pour que ça fonctionne on doit transmettre à cette vue les entrées de l’utilisateur. Il faut
donc passer les informations à la classe Contact à partir du contrôleur.

On peut aussi créer des email en Markdown.

Transmission des informations à la vue


Il y a deux façons de procéder pour transmettre les informations, nous allons utiliser la
plus simple. Il suffit de créer une propriété obligatoirement publique dans la classe
Mailable et celle-ci sera automatiquement transmise à la vue. Voici le nouveau code de
notre classe :

4/10
<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class Contact extends Mailable


{
use Queueable, SerializesModels;

/**
* Elements de contact
* @var array
*/
public $contact;

/**
* Create a new message instance.
*
* @return void
*/
public function __construct(Array $contact)
{
$this->contact = $contact;
}

/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this->from('monsite@chezmoi.com')
->view('emails.contact');
}
}

J’en ai aussi profité pour préciser l’adresse de l’expéditeur avec la méthode from (on
peut aussi renseigner la variable MAIL_FROM_ADDRESS dans le fichier .env). On
pourrait attacher un document avec attach, faire une copie avec cc…

J’ai créé la propriété publique $contact qui sera renseignée par l’intermédiaire du
constructeur.

Il ne reste plus qu’à modifier le contrôleur pour envoyer cet email avec les données
nécessaires :

5/10
<?php

namespace App\Http\Controllers;

use App\Http\Requests\ContactRequest;
use Illuminate\Support\Facades\Mail;
use App\Mail\Contact;

class ContactController extends Controller


{
public function create()
{
return view('contact');
}

public function store(ContactRequest $request)


{
Mail::to('administrateur@chezmoi.com')
->send(new Contact($request->except('_token')));

return view('confirm');
}
}

Tout se passe sur cette ligne :

Mail::to('administrateur@chezmoi.com')
->send(new Contact($request->except('_token')));

On a :

l’adresse de l’administrateur (to),


l’envoi avec la méthode send,
la création d’une instance de la classe Contact avec la transmission des données
saisies (mis à part le token pour la protection CSRF qui ne sert pas).

Et si tout se passe bien le message doit arriver jusqu’à l’administrateur :

Comme l’envoi d’emails peut prendre beaucoup de temps, on utilise en général une file
d’attente (queue). Il faut changer ainsi le code dans le contrôleur :

Mail::to('administrateur@chezmoi.com')
->queue(new Contact($request->except('_token')));

6/10
Mais évidemment pour que ça fonctionne il faut avoir paramétré et lancé un système de
file d’attente. J’en parlerai sans doute dans un chapitre ultérieur.

Test de l’email

Quand on crée la vue pour l’email, il est intéressant de voir l’aspect final avant de faire un
envoi réel. Pour le faire il suffit de créer une simple route :

Route::get('/test-contact', function () {
return new App\Mail\Contact([
'nom' => 'Durand',
'email' => 'durand@chezlui.com',
'message' => 'Je voulais vous dire que votre site est magnifique !'
]);
});

Maintenant en utilisant l’url monsite/test-contact, on obtient l’aperçu directement dans le


navigateur !

Envoyer des emails en phase développement

Le mode Log
Lorsqu’on est en phase de développement, il n’est pas forcément pratique ou judicieux
d’envoyer réellement des emails. Une solution simple consiste à passer en mode Log en
renseignant le driver dans le fichier .env :

MAIL_MAILER=log

Les emails ne seront pas envoyés, mais le code en sera mémorisé dans le fichier des
logs :

Vous allez y trouver par exemple ce contenu :

7/10
[2020-08-29 15:16:48] local.DEBUG: Message-ID:
<f2d44aa0cbb6730bf16d9800235a21cb@laravel6.oo>
Date: Thu, 29 Aug 2020 15:16:48 +0000
Subject: Contact
From: monsite@chezmoi.com
To: administrateur@chezmoi.com
MIME-Version: 1.0
Content-Type: text/html; charset=utf-8
Content-Transfer-Encoding: quoted-printable

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
</head>
<body>
<h2>Prise de contact sur mon beau site</h2>
<p>Réception d'une prise de contact avec les éléments suivants :</p>
<ul>
<li><strong>Nom</strong> : Dupont</li>
<li><strong>Email</strong> : dupont@lui.fr</li>
<li><strong>Message</strong> : Un petit message de contact</li>
</ul>
</body>
</html>

Ce qui vous permet de vérifier que tout se passe correctement (hormis l’envoi).

MailTrap
Une autre possibilité très utilisée est MailTrap qui a une option gratuite (une boîte, avec
50 messages au maximum et 2 messages par seconde). Vous avez un tableau de bord
et une boîte de messages :

8/10
J’ai fait apparaître les configurations pour Laravel. Il est ainsi facile de renseigner le
fichier .env :

MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=login
MAIL_PASSWORD=passe
MAIL_ENCRYPTION=null

Mettez l’encryption à null si vous avez des soucis à ce niveau.

Avec cette configuration lorsque j’envoie‌un email je le vois arriver :

9/10
Vous pouvez analyser l’email avec précision, je vous laisse découvrir toutes les options.

En résumé
Laravel permet l’envoi simple d’email.
Il faut configurer correctement les paramètres pour l’envoi des emails.
Pour chaque email, il faut créer une classe « mailable ».
On peut passer des informations à l’email en créant une propriété publique dans la
classe « mailable ».
On peut passer en mode Log en phase de développement ou alors utiliser
MailTrap.

10/10
Cours Laravel 9 – les bases – configuration, session et
gestion de fichiers
laravel.sillo.org/cours-laravel-9-les-bases-configuration-session-et-gestion-de-fichiers/

12 février 2022

Dans ce chapitre nous verrons la configuration, la gestion des sessions et des fichiers
avec un exemple simple d’envoi et d’enregistrement de fichiers images dans un dossier à
partir d’un formulaire.

La configuration
Tout ce qui concerne la configuration de Laravel se trouve dans le dossier config :

De nombreuses valeurs de configuration sont définies dans le


fichier .env.

Les fichiers de configuration contiennent en fait juste un tableau


avec des clés et des valeurs. Par exemple pour les vues
(view.php) :

<?php

return [

'paths' => [
resource_path('views'),
],

...

];

On a la clé paths et la valeur : un tableau avec des valeurs. Pour


récupérer une valeur il suffit d’utiliser sa clé avec l’helper config :

config('view.paths');

On utilise le nom du fichier (view) et le nom de la clé (paths) séparés par un point.

On peut aussi changer une valeur :

config(['view.paths' => resource_path().'/mes_vues']);

Si je fais effectivement cela mes vues, au lieu d’être cherchées dans le dossier
resources/views seront cherchées dans le dossier resources/mes_vues.

Lorsqu’on change ainsi une valeur de configuration ce n’est valable que pour la requête
en cours.

1/10
Vous pouvez évidemment créer vos propres fichiers de configuration. Pour l’exemple de
ce chapitre on va avoir besoin justement d’utiliser une configuration. Comme notre
application doit enregistrer des fichiers d’images dans un dossier il faut définir
l’emplacement et le nom de ce dossier de destination. On va donc créer un fichier
images.php :

Dans ce fichier on va définir le nom du dossier :

<?php

return ['path' => 'uploads'];

En production pour gagner en performances il est conseillé de


mettre toute la configuration en cache dans un seul fichier avec
la commande php artisan config:cache.

Les sessions
Étant donné que les requêtes HTTP sont fugitives et ne laissent aucune trace il est
important de disposer d’un système qui permet de mémoriser des informations entre
deux requêtes. C’est justement l’objet des sessions.

La configuration des sessions se trouve dans le fichier de configuration session.php. On


trouve dans le fichier .env la définition du driver ainsi que la durée de vie des
informations :

SESSION_DRIVER=file
SESSION_LIFETIME=120

Par défaut c’est un fichier (dans storage/framework/sessions) qui mémorise les


informations des sessions mais on peut aussi utiliser : les cookies, la base de données,
tableau (utilisé pour les tests), memcached, redis…

Quel que soit le driver utilisé l’helper session de Laravel permet une gestion simplifiée
des sessions. Vous pouvez ainsi créer une variable de session à partir de la rquête :

$request->session()->put('clé', 'valeur');

Ou à l’aide de l’helper :

session(['clé' => 'valeur']);

Vous pouvez aussi récupérer une valeur à partir de sa clé :

$valeur = $request->session()->get('clé');

Et on peut aussi utiliser l’helper :

2/10
$valeur = session('clef');

Vous pouvez prévoir une valeur par défaut :

$valeur = session('clef', 'valeur par défaut');

Il est souvent utile (ça sera le cas pour notre exemple) de savoir si une certaine clé est
présente en session :

if (session()->has('error')) ...

Ces informations demeurent pour le même client à travers ses requêtes. Laravel
s’occupe de ces informations, on se contente de lui indiquer un couple clé-valeur et il
s’occupe de tout.

Ce ne sont là que les méthodes de base pour les sessions, vous trouverez tous les
renseignements complémentaires dans la documentation.

La gestion des fichiers


Laravel utilise Flysystem comme composant de gestion de fichiers. Il en propose une API
bien pensée et facile à utiliser. On peut ainsi manipuler fichiers et dossiers de la même
manière en local ou à distance, que ce soit en FTP ou sur le cloud.

La configuration du système se trouve dans le fichier config/filesystem.php. Par défaut


on est en local :

'default' => env('FILESYSTEM_DISK', 'local'),

Mais on peut aussi choisir FTP, SFTP ou s3.

On peut définir des « disques » (disks) qui sont des cibles combinant un driver, un
dossier racine et différents éléments de configuration :

3/10
'disks' => [

'local' => [
'driver' => 'local',
'root' => storage_path('app'),
'throw' => false,
],

'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
'throw' => false,
],

's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
'throw' => false,
],

],

Donc par défaut c’est le disque local qui est actif et le dossier racine est storage/app.

Autrement dit si j’utilise ce code :

Storage::disk('local')->put('recettes.txt', 'Contenu du fichier');

Je vais envoyer le fichier recettes.txt dans le dossier storage/app.

Et si j’utilise ce code :

Storage::disk('public')->put('recettes.txt', 'Contenu du fichier');

Cette fois j’envoie le fichier dans le dossier storage/app/public. Par convention ce


dossier doit être accessible depuis le web.

Mais je vous ai dit précédemment que seul le dossier public à la racine doit être
accessible. Alors ?

Alors pour que ce soit possible il faut créer un lien symbolique public/storage qui pointe
sur storage/app/public. Il y a d’ailleurs une commande d’Artisan pour ça :

php artisan storage:link

4/10
Mais franchement je préfère créer directement un dossier dans public. Les motivations
avancées (ne pas perdre de fichiers au déploiement) me paraissent trop minces.

Donc rien n’empêche de changer la configuration :

'public' => [
'driver' => 'local',
'root' => public_path(),
'url' => env('APP_URL'),
'visibility' => 'public',
'throw' => false,
],

Ainsi le disque « public » pointe sur le dossier public. C’est d’ailleurs ce qu’on va faire
pour l’exemple de ce chapitre.

Il y a de nombreuses méthodes pour manipuler dossiers et fichiers, je ne vais pas


développer tout ça pour le moment, vous avez le détail dans la documentation. On va
surtout utiliser les possibilités de téléchargement des fichiers dans ce chapitre.

La requête de formulaire
Nous allons encore avoir besoin d’une requête de formulaire pour la validation. Comme
nous l’avons déjà vu nous utilisons la commande d’Artisan pour la créer :

php artisan make:request ImagesRequest

Vous devez trouver le fichier ici :

Changez le code pour celui-ci :

5/10
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class ImagesRequest extends FormRequest


{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}

/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return ['image' =>
'required|image|dimensions:min_width=100,min_height=100'];
}
}

On a seulement 3 règles pour le champ image :

le champ est obligatoire (required),


ce doit être une image (image),
l’image doit faire au minimum 100 * 100 pixels (dimensions).

Maintenant notre validation est prête.

Les routes et le contrôleur


On va avoir besoin de deux routes :

use App\Http\Controllers\PhotoController;

Route::get('photo', [PhotoController::class, 'create']);


Route::post('photo', [PhotoController::class, 'store']);

On utilise Artisan pour créer le contrôleur :

php artisan make:controller PhotoController

Vous devez trouver le fichier ici :

Changez le code pour celui-ci :

6/10
<?php

namespace App\Http\Controllers;

use App\Http\Requests\ImagesRequest;

class PhotoController extends Controller


{

public function create()


{
return view('photo');
}

public function store(ImagesRequest $request)


{
$request->image->store(config('images.path'),
'public');

return view('photo_ok');
}
}

Donc au niveau des urls :

http://monsite.fr/photo avec le verbe get pour la demande du formulaire,


http://monsite.fr/photo avec le verbe post pour la soumission du formulaire et
l’envoi du fichier image associé.

En ce qui concerne le traitement de la soumission, vous remarquez qu’on récupère le


chemin du dossier d’enregistrement qu’on a prévu dans la configuration :

config('images.path')

La partie intéressante se situe avec ce code :

$request->image->store(config('images.path'), 'public')

On récupère l’image avec $request->image. Ce qu’on obtient là est une instance de la


classe Illuminate/Http/UploadedFile.

Laravel dispose d’une documentation complète de ses classes, par exemple vous
trouvez cette classe ici avec la méthode store documentée.

La méthode store génère automatiquement un nom de fichier basé sur son contenu
(hashage MD5) et elle retourne le chemin complet.

Les vues
On va utiliser le template des chapitres précédents
(resources/views/template.blade.php) :

7/10
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Mon joli site</title>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
rel="stylesheet" integrity="sha384-
EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"
crossorigin="anonymous">
<style>
textarea { resize: none; }
.card { width: 25em; }
</style>
</head>
<body>
@yield('contenu')
</body>
</html>

Voici la vue pour le formulaire (resources/views/photo.blade.php) :

@extends('template')
@section('contenu')
<br>
<div class="container">
<div class="row card text-white bg-dark">
<h4 class="card-header">Envoi d'une photo</h4>
<div class="card-body">
<form action="{{ url('photo') }}" method="POST"
enctype="multipart/form-data">
@csrf
<div class="mb-3">
<input class="form-control @error('image') is-invalid
@enderror" type="file" id="image" name="image" >
@error('image')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<button type="submit" class="btn btn-secondary">Envoyer !
</button>
</form>
</div>
</div>
</div>
@endsection

Avec cet aspect :

8/10
Remarquez comment est créé le formulaire :

<form action="{{ url('photo') }}" method="POST" enctype="multipart/form-data">

On a prévu le type mime nécessaire pour associer un fichier lors de la soumission :

enctype="multipart/form-data"

En cas d’erreur de validation le message est affiché et la bordure du champ devient


rouge :

Et voici la vue pour la confirmation en retour (app/views/photo_ok.blade.php) :

@extends('template')

@section('contenu')
<br>
<div class="container">
<div class="row card text-white bg-dark">
<h4 class="card-header">Envoi d'une photo</h4>
<div class="card-body">
<p class="card-text">Merci. Votre photo à bien été reçue et
enregistrée.</p>
</div>
</div>
</div>
@endsection

Avec cet aspect :

9/10
On retrouve normalement le fichier bien rangé dans le dossier prévu :

Voici le schéma de fonctionnement :

En résumé
Les fichiers de configuration permettent de mémoriser facilement des ensembles
clé-valeur et sont gérés par l’helper config.
Les sessions permettent de mémoriser des informations concernant un client et
sont facilement manipulables avec l’helper session.
Laravel comporte un système complet de gestion de fichiers en local, à distance ou
sur le cloud avec une API commune.
Il est facile de créer un système de téléchargement de fichier.

10/10
Cours Laravel 9 – les bases – injection de dépendance,
conteneur et façades
laravel.sillo.org/cours-laravel-9-les-bases-injection-de-dependance-conteneur-et-facades/

12 février 2022

Dans ce chapitre nous allons reprendre l’exemple précédent de l’envoi de photos en nous
posant des questions d’organisation du code. Laravel ce n’est pas seulement un
framework pratique, c’est aussi un style de programmation. Il vaut mieux évoquer ce style
le plus tôt possible dans l’apprentissage pour prendre rapidement les bonnes habitudes.

Vous pouvez très bien créer un site complet dans le fichier des routes, vous pouvez aussi
vous contenter de contrôleurs pour effectuer tous les traitements nécessaires. Je vous
propose une autre approche, plus en accord avec ce que nous offre Laravel.

Le problème et sa solution

Le problème
Je vous ai déjà dit qu’un contrôleur a pour mission de réceptionner les requêtes et
d’envoyer les réponses. Entre les deux il y a évidemment du traitement à effectuer, la
réponse doit se construire, parfois c’est très simple, parfois plus long et délicat. Mais
globalement nous avons pour un contrôleur ce fonctionnement :

Reprenons la méthode store de notre contrôleur PhotoController du précédent chapitre


:

1/8
public function store(ImagesRequest $request)
{
$request->image->store(config('images.path'), 'public');

return view('photo_ok');
}

Qu’avons-nous comme traitement ? On récupère l’image transmise et on enregistre cette


image.

La question est : est-ce qu’un contrôleur doit savoir comment s’effectue ce traitement ? Si
vous avez plusieurs contrôleurs dans votre application qui doivent effectuer le même
traitement vous allez multiplier cette mise en place. Imaginez que vous ayez ensuite
envie de modifier l’enregistrement des images, par exemple en les mettant sur le cloud,
vous allez devoir retoucher le code de tous vos contrôleurs ! La répétition de code n’est
jamais une bonne chose, une saine règle de programmation veut qu’on commence à se
poser des questions sur l’organisation du code dès qu’on fait des copies.

Cette préconisation est connue sous l’acronyme DRY (« Don’t Repeat Yourself »).

Un autre élément à prendre en compte aussi est la testabilité des classes. Nous verrons
cet aspect important du développement trop souvent négligé. Pour qu’une classe soit
testable il faut que sa mission soit simple et parfaitement identifiée et il ne faut pas qu’elle
soit étroitement liée avec une autre classe. En effet cette dépendance rend les tests plus
difficiles.

La solution

Alors quelle est la solution ? L’injection de dépendance ! Voyons de quoi il s’agit.


Regardez ce schéma :

Une nouvelle classe entre en jeu pour la gestion, c’est elle qui est effectivement chargée
du traitement, le contrôleur fait juste appel à ses méthodes. Mais comment cette classe
est-elle injectée dans le contrôleur ? Voici le code du contrôleur modifié :

2/8
<?php

namespace App\Http\Controllers;

use App\Http\Requests\ImagesRequest;
use App\Repositories\PhotosRepository;

class PhotoController extends Controller


{
public function create()
{
return view('photo');
}

public function store(ImagesRequest $request, PhotosRepository


$photosRepository)
{
$photosRepository->save($request->image);

return view('photo_ok');
}
}

Vous remarquez qu’au niveau de la méthode store il y a un nouveau paramètre de type


App\Repositories\PhotosRepository. On utilise la méthode save de la classe ainsi
injectée pour faire le traitement.

De cette façon le contrôleur ignore totalement comment se fait la gestion, il sait juste que
la classe PhotosRepository sait la faire. Il se contente d’utiliser la méthode de cette
classe qui est « injectée ».

Maintenant vous vous demandez sans doute comment cette classe est injectée là, en
d’autres termes comment et où est créée cette instance. Eh bien Laravel est assez malin
pour le faire lui-même.

PHP est très tolérant sur les types des variables (mais ça s’est quand même bien
amélioré à partir de la version 7 !). Lorsque vous en déclarez une vous n’êtes pas obligé
de préciser que c’est un string ou un array. PHP devine le type selon la valeur affectée. Il
en est de même pour les paramètres des fonctions. Mais personne ne vous empêche de
déclarer un type comme je l’ai fait ici pour le paramètre de la méthode. C’est même
indispensable pour que Laravel sache quelle classe est concernée. Étant donné que je
déclare le type, Laravel est capable de créer une instance de ce type et de l’injecter dans
le contrôleur.

Pour trouver la classe Laravel utilise l’introspection (reflexion en anglais) de PHP qui
permet d’inspecter le code en cours d’exécution. Elle permet aussi de manipuler du code
et donc de créer par exemple un objet d’une certaine classe. Vous pouvez trouver tous
les renseignements dans le manuel PHP‌.

La gestion

3/8
Maintenant qu’on a dit au contrôleur qu’une classe s’occupe de la gestion il nous faut la
créer. Pour bien organiser notre application on crée un nouveau dossier et on place notre
classe dedans :

Le codage ne pose aucun problème parce qu’il est identique


à ce qu’on avait dans le contrôleur :

<?php

namespace App\Repositories;

use Illuminate\Http\UploadedFile;

class PhotosRepository
{
public function save(UploadedFile $image)
{
$image->store(config('images.path'), 'public');
}
}

Attention à ne pas oublier les espaces de noms !

Maintenant notre code est parfaitement organisé et facile à maintenir et à tester.

Mais allons un peu plus loin, créons une interface pour notre classe :

Avec ce code :

<?php

namespace App\Repositories;

use Illuminate\Http\UploadedFile;

interface PhotosRepositoryInterface
{
public function save(UploadedFile $image);
}

Il suffit ensuite d’en informer la classe PhotosRepository :

class PhotosRepository implements PhotosRepositoryInterface

Ce qui serait bien maintenant serait dans notre contrôleur de référencer l’interface :

4/8
<?php

namespace App\Http\Controllers;

use App\Http\Requests\ImagesRequest;
use App\Repositories\PhotosRepositoryInterface;

class PhotoController extends Controller


{
public function create()
{
return view('photo');
}

public function store(ImagesRequest $request, PhotosRepositoryInterface


$photosRepository)
{
$photosRepository->save($request->image);

return view('photo_ok');
}
}

Le souci c’est que Laravel n’arrive pas à deviner la classe à instancier à partir de cette
interface :

Comment s’en sortir ?

Lorsque j’ai présenté la structure de Laravel j’ai mentionné la présence de providers :

A quoi sert un provider ? Tout simplement à procéder à


des initialisations : événements, middlewares, et
surtout des liaisons de dépendance. Laravel possède
un conteneur de dépendances qui constitue le cœur de
son fonctionnement. C’est grâce à ce conteneur qu’on
va pouvoir établir une liaison entre une interface et une
classe.

Ouvrez le fichier app\Providers\AppServiceProvider.php et ajoutez cette ligne de code


:

5/8
public function register()
{
$this->app->bind(
'App\Repositories\PhotosRepositoryInterface',
'App\Repositories\PhotosRepository'
);
}

La méthode register est activée au démarrage de l’application, c’est l’endroit idéal pour
notre liaison. Ici on dit à l’application (app) d’établir une liaison (bind) entre l’interface
App\Repositories\PhotosRepositoryInterface et la classe
App\Repositories\PhotosRepository. Ainsi chaque fois qu’on se référera à cette
interface dans une injection Laravel saura quelle classe instancier. Si on veut changer la
classe de gestion il suffit de modifier le code du provider.

Maintenant notre application fonctionne.

Si vous obtenez encore un message d’erreur vous disant que l’interface ne peut pas être
instanciée lancez la commande :

composer dumpautoload

Les façades
Laravel propose de nombreuses façades pour simplifier la syntaxe. On l’a vu de façon
récurrente pour les routes :

use Illuminate\Support\Facades\Route;

Route::get('/', function () {
return view('welcome');
});

Un façade est une interface statique pour une classe disponible dans le conteneur de
services. Laravel offre des façades pour pratiquement la totalité des classes disponibles.
Les façades simplifient la syntaxe et les tests. On n’a pas besoin d’injecter la classe
correspondante.

Mais comment fonctionnent ces façades ?

Laravel dispose d’une classe Illuminate\Support\Facades\Facade qui contient le code


pour faire fonctionner tout ça. Donc toutes les façades créées doivent hériter de cette
classe de base.

Par exemple pour les routes on a la façade Route qui correspond à la classe
Illuminate\Support\Facades\Route. Regardons cette classe :

6/8
<?php

namespace Illuminate\Support\Facades;

...
class Route extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'router';
}
}

On se contente de retourner router. Il faut aller voir dans le fichier


Illuminate\Routing\RoutingServiceProvider pour trouver l’enregistrement du router :

protected function registerRouter()


{
$this->app->singleton('router', function ($app) {
return new Router($app['events'], $app);
});
}

Les providers permettent d’enregistrer des composants dans le conteneur de Laravel. Ici
on déclare router et on voit qu’on crée une instance de la classe Router (new
Router…). Le nom complet est Illuminate\Routing\Router. Si vous allez voir cette
classe vous trouverez les méthodes qu’on a utilisées dans ce chapitre, par exemple get :

public function get($uri, $action = null)


{
return $this->addRoute(['GET', 'HEAD'], $uri, $action);
}

Autrement dit si j’écris en utilisant la façade :

Route::get('/', function() { return 'Coucou'; });

J’obtiens le même résultat que si j’écris en allant chercher le routeur dans le conteneur :

$this->app['router']->get('/', function() { return 'Coucou'; });

Ou encore en utilisant un helper :

app('router')->get('/', function() { return 'Coucou'; });

La différence est que la première syntaxe est plus simple et intuitive mais certains
n’aiment pas trop ce genre d’appel statique.

En résumé

7/8
Un contrôleur doit déléguer toute tâche qui ne relève pas de sa compétence.
L’injection de dépendance permet de bien séparer les tâches, de simplifier la
maintenance du code et les tests unitaires.
Les providers permettent de faire des initialisations, en particulier des liaisons de
dépendance entre interfaces et classes.
Laravel est équipé de nombreuses façades qui simplifient la syntaxe.
Il existe aussi des helpers pour simplifier la syntaxe. ‌

8/8
Cours Laravel 9 – les données – migrations et modèles
laravel.sillo.org/cours-laravel-9-les-donnees-migrations-et-modeles/

14 février 2022

Dans ce chapitre nous allons commencer à aborder les bases de données. C’est un
vaste sujet auquel Laravel apporte des réponses efficaces. Nous allons commencer par
voir les migrations et les modèles.

Les migrations
Une migration permet de créer et de mettre à jour un schéma de base de données.
Autrement dit, vous pouvez créer des tables, des colonnes dans ces tables, en
supprimer, créer des index… Tout ce qui concerne la maintenance de vos tables peut
être pris en charge par cet outil. Vous avez ainsi un suivi de vos modifications.

La configuration de la base

Vous devez dans un premier temps avoir une base de données. Laravel permet de gérer
les bases de type MySQL, Postgres, SQLite et SQL Server. Je ferai tous les exemples
avec MySQL mais le code sera aussi valable pour les autres types de bases.

Il faut indiquer où se trouve votre base, son nom, le nom de l’utilisateur, le mot de passe
dans le fichier de configuration .env :

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=

Ici nous avons les valeurs par défaut à l’installation de Laravel. Il faudra évidemment
modifier ces valeurs selon votre contexte de développement et définir surtout le nom de
la base, le nom de l’utilisateur et le mot de passe. Pour une installation de MySql en local
en général l’utilisateur est root et on n’a pas de mot de passe.

Artisan

Nous avons déjà utilisé Artisan qui permet de faire beaucoup de choses, vous avez un
aperçu des commandes en entrant :

php artisan

Vous avez une longue liste. Pour ce chapitre nous allons nous intéresser uniquement à
celles qui concernent les migrations :

1/15
On dispose de 6 commandes :

fresh : supprime toutes les tables et relance la migration (commande apparue avec
la version 5.5)
install : crée et informe la table de référence des migrations
refresh : réinitialise et relance les migrations
rollback : annule la dernière migration
status : donne des informations sur les migrations

Installation
Si vous regardez dans le dossier database/migrations il y a déjà 4 migrations présentes
:

table users : c’est une migration de base pour créer une table des utilisateurs,
table password_resets : c’est une migration liée à la précédente qui permet de
gérer le renouvellement des mots de passe en toute sécurité,
table failed_jobs : une migration qui concerne les queues,
table personal_access_tokens concerne les api.

Puisque ces migrations sont présentes autant les utiliser pour nous entrainer.

Commencez par créer une base MySQL et informez .env, par exemple :

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel9
DB_USERNAME=root
DB_PASSWORD=

Lancez alors la commande install :

On se retrouve alors avec une table


migrations dans la base avec cette
structure :

2/15
Pour le moment cette table est vide, elle va se remplir au fil des
migrations pour les garder en mémoire.

Vous n’aurez jamais à intervenir directement sur cette table qui


est là juste pour la gestion effectuée par Laravel.

Constitution d’une migration

Si vous ouvrez le fichier


database/migrations/2014_10_12_000000_create_users_table.php vous trouvez ce
code :

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration


{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('users');
}
};

On dispose dans cette classe de deux fonctions :

up : ici on a le code de création de la table et de ses colonnes

3/15
down : ici on a le code de suppression de la table

Lancer les migrations


Pour lancer les migrations on utilise la commande migrate :

On voit que les 4 migrations présentes ont été exécutées et on trouve les 4 tables dans la
base (en plus de celle de gestion des migrations) :

Pour comprendre le lien entre migration et création de la table


associée voici une illustration pour la table users :

La méthode timestamps permet la création des deux colonnes created_at et


updated_at.

Annuler ou rafraichir une migration


Pour annuler une migration on utilise la commande rollback :

4/15
Les méthodes down des migrations sont exécutées et les tables sont supprimées.

Pour annuler et relancer en une seule opération on utilise la commande refresh :

Pour éviter d’avoir à coder la méthode down on a la commande fresh qui supprime
automatiquement les tables concernées :

Créer une migration


Il existe une commande d’artisan pour créer un squelette de migration :

La migration est créée dans le dossier :

5/15
Avec ce code de base :

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration


{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
//
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
};

Il faut ensuite compléter ce code selon vos besoins !

Le nom du fichier de migration commence par sa date de création, ce qui conditionne son
positionnement dans la liste. L’élément important à prendre en compte est que l’ordre des
migrations prend une grande importance lorsqu’on a des clés étrangères !

La population (seeding)
En plus de proposer des migrations, Laravel permet aussi la population (seeding), c’est à
dire un moyen simple de remplir les tables d’enregistrements. Les classes de la
population se trouvent dans le dossier databases/seeds:

On est libre d’organiser les classes comme on veut dans ce


dossier : en garder une seule ou en faire plusieurs pour être
mieux organisé.

Lorsqu’on installe Laravel on dispose de la classe


DatabaseSeeder avec ce code :

6/15
<?php

namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder


{
/**
* Seed the application's database.
*
* @return void
*/
public function run()
{
// \App\Models\User::factory(10)->create();
}
}

On a un appel commenté pour remplir la table users. Décommentez cette ligne et lancez
la population avec cette commande :

php artisan db:seed

On vérifie dans la table qu’on a


bein eu la création de 10
utilisateurs :

Je parlerai plus tard des factories.

Eloquent
Laravel propose un ORM (acronyme de object-relational mapping ou en bon Français un
mappage objet-relationnel) très performant.

De quoi s’agit-il ?

Tout simplement que tous les éléments de la base de données ont une représentation
sous forme d’objets manipulables.

7/15
Quel intérêt ?

Tout simplement de simplifier grandement les opérations sur la base comme nous allons
le voir dans toute cette partie du cours.

Avec Eloquent une table est représentée par une classe qui étend la classe Model. On
peut créer un modèle avec Artisan :

php artisan make:model Test

On trouve le fichier ici :

Pour ceux qui ont travaillé avec les versions anciennes de Laravel on a
désormais un dossier Models par défaut.

Avec cette trame de base :

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Test extends Model


{
use HasFactory;
}

Je parlerai plus tard des factories et donc de ce trait présent par défaut dans les modèles.

On peut créer un modèle en même temps que la migration pour la table avec cette
syntaxe :

php artisan make:model Test -m

Un exemple
Nous allons encore prendre un exemple simple de gestion de contacts mais cette fois en
allant plus loin avec Eloquent.

Le contrôleur

On va créer un contrôleur avec cette commande :

php artisan make:controller ContactsController

8/15
On va créer ces deux méthodes :

create : pour appeler la vue avec le formulaire de


création
store : pour valider la saisie et enregistrer le contact
dans la base

class ContactsController extends Controller


{
public function create()
{
//
}

public function store(Request $request)


{
//
}
}

Il va falloir coder ces méthodes…

Les routes
Il nous faut deux routes :

une route GET appeler la méthode create du contrôleur


une route POST appeler la méthode store du contrôleur

use App\Http\Controllers\ContactsController;

Route::get('contact', [ContactsController::class, 'create'])-


>name('contact.create');
Route::post('contact', [ContactsController::class, 'store'])-
>name('contact.store');

On nomme les routes pour pouvoir les utiliser plus facilement.

Le modèle et la migration

On va créer le modèle et la migration tant qu’on y est pour la table contacts :

php artisan make:model Contact -m

9/15
Dans le code généré pour la migration on va ajouter deux colonnes (message et email) :

public function up()


{
Schema::create('contacts', function (Blueprint $table) {
$table->id();
$table->text('message');
$table->string('email');
$table->timestamps();
});
}

Et on lance la migration pour la table contacts :

Par convention le modèle Contact sait qu’il est relié à la table contacts. Si on sortait de
la convention de nommage il faudrait ajouter une propriété au modèle pour spécifier le
nom de la table.

Le formulaire

On va continuer à utiliser le template déjà vu dans les précédents articles


(resources/views/template.blade.php) :

10/15
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Mon joli site</title>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
rel="stylesheet" integrity="sha384-
EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"
crossorigin="anonymous">
<style>
textarea { resize: none; }
.card { width: 25em; }
</style>
</head>
<body>
@yield('contenu')
</body>
</html>

On crée la vue du formulaire (contact.blade.php) :

@extends('template')

@section('contenu')
<br>
<div class="container">
<div class="row card text-white bg-dark">
<h4 class="card-header">Contactez-moi</h4>
<div class="card-body">
<form action="{{ route('contact.create') }}" method="POST">
@csrf
<div class="mb-3">
<input type="email" class="form-control @error('email')
is-invalid @enderror" name="email" id="email" placeholder="Votre email" value="{{
old('email') }}">
@error('email')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<textarea class="form-control @error('message') is-
invalid @enderror" name="message" id="message" placeholder="Votre message">{{
old('message') }}</textarea>
@error('message')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<button type="submit" class="btn btn-secondary">Envoyer !
</button>
</form>
</div>
</div>
</div>
@endsection

11/15
Rien de nouveau ici puisque c’est pratiquement le même code qu’on avait déjà vu.
Remarquez l’attribut action du formulaire dans lequel on utilise le nom de la route ainsi
que l’helper route :

<form action="{{ route('contact.create') }}" method="POST">

Il ne reste plus qu’à coder le contrôleur pour envoyer la vue :

public function create()


{
return view('contact');
}

Et on devrait l’obtenir à l’adresse …/contact :

L’enregistrement

Maintenant on doit coder le traitement de la soumission du formulaire dans le contrôleur :

public function store(Request $request)


{
$this->validate($request, [
'email' => 'bail|required|email',
'message' => 'bail|required|max:500'
]);

$contact = new \App\Models\Contact;


$contact->email = $request->email;
$contact->message = $request->message;
$contact->save();

return "C'est bien enregistré !";


}

Je ne reviens pas sur la validation qu’on a vu dans un précédent article.

12/15
On crée une nouvelle instance du modèle, on renseigne ses propriétés et on finit avec un
save pour l’enregistrer dans la base. Et si tout se passe bien on doit trouver
l’enregistrement dans la table :

Méthode create et assignement de masse

On peut aussi utiliser la méthode create du modèle pour enregistrer dans la base :

\App\Models\Contact::create([
'email' => $request->email,
'message' => $request->message,
]);

Ce code remplace dans la méthode store celui-ci :

$contact = new \App\Models\Contact;


$contact->email = $request->email;
$contact->message = $request->message;
$contact->save();

La méthode create d’Eloquent attend un tableau de données.

Si vous utilisez ce code vous allez tomber sur cette erreur :

Par sécurité ce type d’assignement de masse (on transmet directement un tableau de


valeurs issues du client avec la méthode create) est limité par une propriété au niveau
du modèle qui désigne précisément les noms des colonnes susceptibles d’être modifiées.
Dans le modèle Contact on va ajouter ce code :

class Contact extends Model


{
...
protected $fillable = ['email', 'message'];
}

Ce sont les seules colonnes qui seront impactées par la méthode create (et
équivalentes). Attention à cela lorsque vous avez un bug mystérieux avec des colonnes
qui ne se mettent pas à jour !

Mais quel est le risque ?

13/15
Imaginez qu’il y ait une autre colonne avec des données sensibles et non prévue dans le
formulaire mais qu’un petit malin l’ajoute à la requête, cette colonne serait mise à jour en
même temps que les autres !

Le modèle en détail

Modifiez ainsi le code dans le contrôleur ContactController :

dd(\App\Models\Contact::create ($request->all ()));

L’helper dd est bien pratique (il existe aussi l’helper ddd) il regroupe un var_dump et un
die. Ici si on soumet le formulaire on va observer de plus près le modèle créé parce que
la méthode create renvoie ce modèle :

App\Models\Contact {#1132 ▼
#connection: "mysql"
#table: "contacts"
#primaryKey: "id"
#keyType: "int"
+incrementing: true
#with: []
#withCount: []
+preventsLazyLoading: false
#perPage: 15
+exists: true
+wasRecentlyCreated: true
#escapeWhenCastingToString: false
#attributes: array:5 [▼
"email" => "toto@ici.fr"
"message" => "Mon petit message"
"updated_at" => "2022-01-22 17:52:47"
"created_at" => "2022-01-22 17:52:47"
"id" => 1
]
#original: array:5 [▶]
#changes: []
#casts: []
#classCastCache: []
#attributeCastCache: []
#dates: []
#dateFormat: null
#appends: []
#dispatchesEvents: []
#observables: []
#relations: []
#touches: []
+timestamps: true
#hidden: []
#visible: []
#fillable: array:2 [▶]
#guarded: array:1 [▶]
}

14/15
On a pas mal de propriétés mais en particulier des attributs (attributes) et on se rend
compte que chacun de ces attributs correspond à une colonne de la table contact.

En résumé
La base de données doit être configurée pour fonctionner avec Laravel.
Les migrations permettent d’intervenir sur le schéma des tables de la base.
Eloquent permet une représentation des tables sous forme d’objets pour simplifier
les manipulations des enregistrements.
L’assignement de masse est limité par la propriété $fillable pour des raisons de
sécurité.

15/15
Cours Laravel 9 – les données – jouer avec Eloquent
laravel.sillo.org/cours-laravel-9-les-donnees-jouer-avec-eloquent/

14 février 2022

On a commencé à voir Eloquent et les modèles dans le premier chapitre de cette partie
du cours, mais on s’est limité pour le moment à créer un enregistrement. Dans ce
chapitre, on va aller plus loin en explorant les possibilités d’Eloquent, couplé à un
puissant générateur de requêtes, pour manipuler les données.

Pour effectuer les manipulations de ce chapitre, vous aurez besoin d’une installation
fraîche de Laravel. Les migrations devront également être effectuées pour qu’on puisse
utiliser la base de données.

Tinker
Artisan est plein de possibilités, l’une d’elle est un outil très pratique, un REPL (Read–
Eval–Print Loop). C’est un outil qui permet d’entrer des expressions, de les faire évaluer
et d’avoir le résultat à l’affichage et son nom est Tinker. Ce REPL est boosté par le
package psys. Il y a un bon article de présentation en anglais ici.

Pour lancer cet outil il y a une commande d’artisan :

php artisan tinker

On va se servir de cet outil pour interagir avec la base de données dans ce chapitre.

Créer un enregistrement

Méthode create
On a déjà vu comment créer un enregistrement avec Eloquent. Comme a priori votre
base de données est vide avec une installation fraiche de Laravel on va commencer par
ajouter un utilisateur dans la table users à l’aide du modèle User. Pour mémoire ce
modèle est le seul présent au départ :

Dans Tinker entrez cette expression :

User::create(['name' => 'Durand', 'email' => 'durand@chezlui.fr',


'password' => 'pass'])

1/8
Remarquez qu’on n’a pas besoin d’entrer l’espace de nom complet App\Models\User,
Tinker crée automatiquement un alias.

La méthode create renvoie le modèle avec tous les attributs créés comme les dates et
l’id. Tinker nous les affiche gentiment. On retrouve évidemment notre enregistrement
dans la table users :

Pour mémoire cette méthode create est un assignement de masse et implique les
précautions que nous avons déjà vues.

Méthode save

Une autre façon de créer un enregistrement avec Eloquent et de commencer par créer un
modèle vide, renseigner les attributs, puis utiliser la méthode save pour enregistrer dans
la table.

Entrez cette suite d’expressions :

$user = new User


$user->name = 'Dupont'
$user->email = 'dupont@chezlui.fr'
$user->password = 'pass'
$user->save()

On commence par créer le modèle, puis


on renseigne ses attributs, enfin on
enregistre dans la base avec save.

Modifier un enregistrement
Voyons maintenant comment modifier un enregistrement existant. Il y a aussi deux
manières de procéder.

2/8
Méthode update

Entrez cette expression dans Tinker :

User::find(1)->update(['email' => 'durand@cheznous.fr'])

On commence par récupérer le premier enregistrement de la table avec find(1), ç’a pour
effet de créer un modèle avec les attributs renseignés pour Durand. Ensuite on utilise la
méthode update sur ce modèle en précisant le nom et la valeur de chaque attribut qu’on
veut modifier dans un tableau. ici on a changé l’adresse email pour Durand.

Avec cette méthode update on a encore le problème de l’assignement de masse.

Méthode save

On peut aussi utiliser la méthode save pour faire une modification. Entrez ces
expressions dans Tinker :

$user = User::find(2)
$user->email = 'dupont@cheznous.fr'
$user->save()

Avec find(2) on crée un modèle avec les


renseignements de Dupont (le deuxième
enregistrement de la table), ensuite on
change l’attribut email, enfin on
enregistre dans la base.

On peut constater les modifications dans


la table :

Remarquez que les colonnes created_at et updated_at n’ont maintenant plus les
mêmes valeurs puisqu’on a fait des modifications.

Supprimer un enregistrement
Après la création et la modification vient tout naturellement la suppression avec la
méthode delete. Si vous entrez cette expression dans Tinker :

3/8
User::find(2)->delete()

Vous supprimez l’utilisateur qui a l’index 2.

La suppression est définitive. Il existe aussi une possibilité de suppression provisoire


(soft delete) mais il faut modifier le modèle en ajoutant un trait et une propriété. Cette
partie de la documentation traite de ce sujet que je ne pense pas développer dans ce
cours.

Trouver des enregistrements


Rafraichissez la table (pas dans Tinker cette fois) :

php artisan migrate:refresh

Et recréez avec Tinker les deux utilisateurs :

User::create(['name' => 'Durand', 'email' => 'durand@chezlui.fr', 'password' =>


'pass'])
User::create(['name' => 'Dupont', 'email' => 'dupont@chezlui.fr', 'password' =>
'pass'])

Tous les enregistrements


On a vu déjà la méthode find pour trouver un enregistrement à partir de son identifiant
(on peut aussi passer plusieurs identifiants dans un tableau). On peut aussi tous les
récupérer avec la méthode all :

4/8
Si vous êtes observateur vous avez remarqué que cette fois on n’obtient pas un modèle,
comme c’était le cas avec find mais une collection
(Illuminate\Database\Eloquent\Collection). Cette classe comporte un nombre
considérable de méthodes, on peut donc effectuer de nombreux traitements à ce niveau
et même les chaîner.

Par exemple ici j’extrais un modèle au hasard de la collection :

J’aurai l’occasion dans ce cours de


montrer l’utilisation de plusieurs des
méthodes disponibles.

Sélectionner des enregistrements


En général on ne veut pas tous les enregistrements, mais juste certains qui
correspondent à certains critères. Eloquent est couplé à un puissant générateur de
requêtes SQL (Query Builder). On peut par exemple utiliser la méthode where. Entrez
cette expression dans Tinker :

User::where('name', 'Dupont')->get()

On récupère une collection qui ne contient que le modèle qui correspond à Dupont.

Ajoutez cet utilisateur :

User::create(['name' => 'Martin', 'email' => 'martin@chezlui.fr', 'password' =>


'pass'])

On a maintenant 3 utilisateurs :

5/8
Maintenant entrez cette expression :

User::where('name', '<', 'e')->get()

Cette fois, on a sélectionné à partir des noms dont la première lettre doit être avant « e »
dans l’alphabet et on obtient une collection avec deux modèles.

On dispose de la méthode first si on veut récupérer juste le premier :

On peut aussi limiter les colonnes


retournées avec la méthode select :

6/8
Ici on ne retourne que les noms.

Souvent on a envie de connaître la requête générée, pour la voir il suffit d’utiliser la


méthode toSql :

On dispose avec le générateur de requête de


riches possibilités que nous verrons
progressivement, par exemple, on peut
facilement trier les enregistrements avec la méthode orderBy :

Souvent il existe une syntaxe plus simple, par exemple, on peut obtenir le même résultat
avec latest :

User::select('name')->latest('name')->get()

Trouver ou créer
Que se passe-t-il si l’enregistrement qu’on veut ne se trouve pas dans la table ? Faisons
un essai :

On a null en retour et on peut gérer cette valeur. Mais des fois,


on se trouve dans une situation où on désire récupérer un
enregistrement ou le créer s’il n’existe pas. On peut le faire en
deux étapes, mais on peut aussi le faire en une seule action avec la méthode
firstOrCreate :

7/8
Ici comme l’enregistrement existe il est retourné normalement, mais dans le cas suivant il
n’existe pas et donc il est créé :

On se retrouve donc avec 4 enregistrements dans la table :

Il existe aussi la méthode firstOrNew, si l’enregistrement existe il est retourné comme


avec firstOrCreate, par contre s’il n’existe pas il est juste créé une instance du modèle,
mais aucune action dans la base n’est réalisée.

De la même manière, il existe la méthode updateOrCreate basée sur le même principe.

En résumé
On peut créer des enregistrements avec Eloquent avec la méthode create.
On peut modifier des enregistrements avec Eloquent avec la méthode update.
On peut créer ou modifier des enregistrements avec Eloquent avec la méthode
save.
Le résultat d’une requête générée avec Eloquent est soit un modèle soit une
collection.
Eloquent est associé à un puissant générateur de requêtes (Query Builder).
Des méthodes spéciales permettent de faire plusieurs actions comme aller chercher
un enregistrement ou le créer s’il n’existe pas.

8/8
Cours Laravel 9 – les données – les ressources (1/2)
laravel.sillo.org/cours-laravel-9-les-donnees-les-ressources-1/

15 février 2022

Dans ce chapitre nous allons commencer à étudier les ressources qui permettent de
créer des routes « CRUD » (Create, Read, Update, Delete) adaptées à la persistance de
données. Comme exemple pratique nous allons prendre le cas d’une table de films.

Les données
On repart d’un Laravel vierge et on crée une base comme on l’a vu précédemment.
Appelons la par exemple laravel9 pour faire original. On renseigne le fichier .env en
conséquence :

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel9
DB_USERNAME=root
DB_PASSWORD=

La migration

On va créer avec Artisan le modèle Film en même temps que la migration :

php artisan make:model Film --migration

1/18
Pour faire simple on va se contenter de 3 colonnes pour le titre du film, son année de
sortie et sa description :

public function up()


{
Schema::create('films', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->year('year');
$table->text('description');
$table->timestamps();
});
}

On a les champs :

id : entier auto-incrémenté qui sera la clé primaire de la table,


title : texte pour le nom du film,
year : année de sortie du film,
description : description du film,
created_at et updated_at créés par la méthode timestamps,

Ensuite on lance la migration :

On a la création des tables de base de Laravel qu’on a déjà vues et qui ne nous
intéressent pas pour cet article. Mais on voit aussi la création de la table films :

Le modèle
Le modèle Film qu’on a créé est vide au départ (il n’y a que le trait pour le factory). On va
se contenter de prévoir l’assignement de masse avec la propriété $fillable :

2/18
protected $fillable = ['title', 'year', 'description'];

La population

Pour nos essais on va remplir un peu la table avec quelques films. On va créer un factory
:

php artisan make:factory FilmFactory

On a ce code de base :

<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

/**
* @extends
\Illuminate\Database\Eloquent\Factories\Factory<\App\Models\F

*/
class FilmFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
//
];
}
}

On va compléter ainsi :

public function definition()


{
return [
'title' => $this->faker->sentence(2, true),
'year' => $this->faker->year,
'description' => $this->faker->paragraph(),
];
}

On change ensuite le code du seeder (database/seeds/DatabaseSeeder.php) :

3/18
use App\Models\Film;

...

public function run()


{
Film::factory(10)->create();
}

Il ne reste plus qu’à lancer la population :

php artisan db:seed

Si tout va bien on se retrouve avec 10 films dans la table :

Une ressource

Le contrôleur
On va maintenant créer un contrôleur de ressource avec Artisan :

php artisan make:controller FilmController --resource

C’est la commande qu’on a déjà vue pour créer un contrôleur avec en plus l’option –
resource.

Vous trouvez comme résultat le contrôleur app/Http/Controllers/FilmController :

Avec ce code :

4/18
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class FilmController extends Controller


{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
//
}

/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
//
}

/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//
}

/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
//
}

/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/

5/18
public function edit($id)
{
//
}

/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
//
}

/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
}

Les 7 méthodes créées couvrent la gestion complète des films:

index : pour afficher la liste des films,

create : pour envoyer le formulaire pour la création d’un nouveau film,

store : pour créer un nouveau film,

show : pour afficher les données d’un film,

edit : pour envoyer le formulaire pour la modification d’un film,

update : pour modifier les données d’un film,

destroy : pour supprimer un film.

Les routes

Pour créer toutes les routes il suffit de cette unique ligne de code :

use App\Http\Controllers\FilmController;

Route::resource('films', FilmController::class);

On va vérifier ces routes avec Artisan :

6/18
Vous trouvez 7 routes, avec chacune une méthode et une url, qui pointent sur les 7
méthodes du contrôleur. Notez également que chaque route a aussi un nom qui peut être
utilisé par exemple pour une redirection.

On peut obtenir plus d’information avec la commande « verbose » :

php artisan route:list -v

On retrouve aussi pour chaque route le middleware web dont je vous ai déjà parlé.

Nous allons à présent considérer chacune de ces routes et créer la gestion des données,
les vues, et le code nécessaire au niveau du contrôleur.

La validation

On va créer une requête de formulaire pour la création ou la modification d’un film :

php artisan make:request Film

On a deux champs à vérifier : le titre et l’année. A priori il n’y a aucune


différence de validation pour la création et la modification. Voilà le
code modifié pour cette classe :

7/18
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class Film extends FormRequest


{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}

/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'title' => ['required', 'string', 'max:100'],
'year' => ['required', 'numeric', 'min:1950', 'max:' . date('Y')],
'description' => ['required', 'string', 'max:500'],
];
}
}

On prévoie comme règles :

title : le champ est obligatoire (required), ça doit être du texte (string), le nombre
maximum de caractères (max) doit être de 100
year : le champ est obligatoire (required), ça doit être un nombre (number), la
valeur minimale (min) doit être 1950, la valeur maximale (max) doit être l’année
actuelle (date(‘Y’))
description : le champ est obligatoire (required), ça doit être du texte (string), le
nombre maximum de caractères (max) doit être de 500

On pourra injecter cette classe dans les méthodes du contrôleur.

Le template
Laravel s’occupe essentiellement du côté serveur et n’impose rien côté client, même s’il
propose des choses. Autrement dit on peut utiliser Laravel avec n’importe quel système
côté client. Pour notre exemple je vous propose d’utiliser Bulma pour la mise en forme.
Voici un template qui va nous servir pour toutes nos vues :

8/18
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,
initial-scale=1">
<title>Films</title>
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.m

@yield('css')
</head>
<body>
<main class="section">
<div class="container">
@yield('content')
</div>
</main>
</body>
</html>

La liste des films

La route

La liste des films correspond à cette route :

Le contrôleur
Dans le contrôleur c’est la méthode index qui est concernée. On va donc la coder :

...
use App\Models\Film;

class FilmController extends Controller


{
public function index()
{
$films = Film::all();
return view('index', compact('films'));
}
...

On va chercher tous les films avec la méthode all du modèle, on appelle la vue index en
lui transmettant les films.

La vue index

On crée la vue index :

Avec ce code :

9/18
@extends('template')

@section('content')
<div class="card">
<header class="card-header">
<p class="card-header-title">Films</p>
</header>
<div class="card-content">
<div class="content">
<table class="table is-hoverable">
<thead>
<tr>
<th>#</th>
<th>Titre</th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
@foreach($films as $film)
<tr>
<td>{{ $film->id }}</td>
<td><strong>{{ $film->title
}}</strong></td>
<td><a class="button is-
primary" href="{{ route('films.show', $film->id)
}}">Voir</a></td>
<td><a class="button is-
warning" href="{{ route('films.edit', $film->id)
}}">Modifier</a></td>
<td>
<form action="{{
route('films.destroy', $film->id) }}" method="post">
@csrf
@method('DELETE')
<button
class="button is-danger" type="submit">Supprimer</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
@endsection

10/18
Quelques remarques concernant le code :

on utilise la directive @foreach pour faire une boucle sur tous les films
la méthode route qui génère une url selon la route peut être accompagnée d’un
paramètre, par exemple route(‘films.show’, $film->id) permet de générer l’url de
la forme …/films/id
les formulaire HTML ne supportent pas les verbes PUT, PATCH et DELETE, du
coup on doit utiliser le verbe POST et prévoir dans le formulaire un input caché qui
indique le verbe à utiliser en réalité, la directive @method permet de facilement
mettre ça en place, on s’en sert pour le bouton de suppression

La pagination
Ici on n’a que 10 films mais imaginez qu’on en ait des centaines ou des milliers ! Dans ce
cas une pagination serait la bienvenue. Laravel est bien équipé pour ça. Au niveau du
contrôleur le changement est facile :

$films = Film::paginate(5);

On a remplacé la méthode all par paginate en indiquant en paramètre le nombre


d’enregistrement par page. Ensuite dans la vue il suffit de prévoir ce code :

11/18
</div>
<footer class="card-footer">
{{ $films->links() }}
</footer>
</div>
@endsection

Mais le souci c’est que par défaut le marquage généré est celui qui convient à Tailwind,
du coup avec Bulma on obtient ça :

Ce qui n’est pas du meilleur effet !

Il nous faut donc modifier la vue qui génère ce


code. Comme nous ne sommes pas les premiers
à avoir ce souci il suffit de chercher sur la toile et
on trouve facilement ce repo.

On commence par publier les vues :

php artisan vendor:publish --tag=laravel-pagination

On se rend compte d’ailleurs qu’il y en a une de prévue


pour Semantic qui est aussi un superbe framework, de
même pour Bootstrap qui n’est plus à présenter.

Pour terminer on remplace le code du fichier


tailwind.blade.php par celui-ci :

12/18
@if ($paginator->hasPages())
<nav class="pagination is-centered" role="navigation" aria-label="pagination">
{{-- Previous Page Link --}}
@if ($paginator->onFirstPage())
<a class="pagination-previous" disabled>@lang('pagination.previous')
</a>
@else
<a class="pagination-previous" href="{{ $paginator->previousPageUrl()
}}">@lang('pagination.previous')</a>
@endif

{{-- Next Page Link --}}


@if ($paginator->hasMorePages())
<a class="pagination-next" href="{{ $paginator->nextPageUrl()
}}">@lang('pagination.next')</a>
@else
<a class="pagination-next" disabled>@lang('pagination.next')</a>
@endif

{{-- Pagination Elements --}}


<ul class="pagination-list">
@foreach ($elements as $element)
{{-- "Three Dots" Separator --}}
@if (is_string($element))
<li><span class="pagination-ellipsis">&hellip;</span></li>
@endif

{{-- Array Of Links --}}


@if (is_array($element))
@foreach ($element as $page => $url)
@if ($page == $paginator->currentPage())
<li><a class="pagination-link is-current" aria-
label="Goto page {{ $page }}">{{ $page }}</a></li>
@else
<li><a href="{{ $url }}" class="pagination-link"
aria-label="Goto page {{ $page }}">{{ $page }}</a></li>
@endif
@endforeach
@endif
@endforeach
</ul>

</nav>
@endif

Maintenant c’est un peu mieux :

13/18
Ça mérite juste d’être un peu aéré et centré. Comme Bulma utilise Flex on va ajouter
quelques règles dans la vue index :

@section('css')
<style>
.card-footer {
justify-content: center;
align-items: center;
padding: 0.4em;
}
</style>
@endsection

Maintenant c’est plus joli :

Mais ça serait encore mieux en français ! On a déjà vu qu’on peut récupérer les fichiers
de langue ici. On copie le dossier ici :

Et dans config/app.php on change cette ligne :

'locale' => 'fr',

Maintenant c’est parfait !

L’affichage d’un film


On va voir maintenant l’affichage les données d’un film. On y accède à partir du bouton
Voir.

La route

La liste des films correspond à cette route :

14/18
Le contrôleur
Dans le contrôleur c’est la méthode show qui est concernée. On va donc la coder.

Il me faut toutefois préciser déjà un point important. Dans la version du contrôleur


générée par défaut on voit que le film au niveau des arguments des fonctions est
référencé par son identifiant, par exemple :

public function show($id)

La variable id contient la valeur passée dans l’url. Par exemple …/films/8 indique qu’on
veut voir les informations du film d’identifiant 8. Il suffit donc ensuite d’aller chercher dans
la base le film correspondant.

On va utiliser une autre stratégie :

public function show(Film $film)

L’argument cette fois est une instance du modèle App\Models\Film. Etant donné qu’il
rencontre ce type, Laravel va automatiquement livrer une instance du modèle pour le film
concerné ! C’est ce qu’on appelle liaison implicite (Implicit Bindind). Vous voyez encore là
à quel point Laravel nous simplifie la vie.

Du coup la méthode devient très simple à coder :

public function show(Film $film)


{
return view('show', compact('film'));
}

La vue show

On crée cette vue :

Avec ce code :

15/18
@extends('template')

@section('content')
<div class="card">
<header class="card-header">
<p class="card-header-title">Titre : {{ $film->title }}</p>
</header>
<div class="card-content">
<div class="content">
<p>Année de sortie : {{ $film->year }}</p>
<hr>
<p>{{ $film->description }}</p>
</div>
</div>
</div>
@endsection

Sobre et efficace. J’aurais pu prévoir un bouton de retour mais les navigateurs font déjà
ça très bien.

Supprimer un film

La route
La suppression d’un film correspond à cette route :

Le contrôleur
Dans le contrôleur c’est la méthode destroy qui est concernée. On va donc la coder :

16/18
public function destroy(Film $film)
{
$film->delete();

return back()->with('info', 'Le film a bien été supprimé dans la base de


données.');
}

Comme pour la méthode show on utilise une liaison implicite et on obtient du coup
immédiatement une instance du modèle. Comme c’est un peu brutal comme suppression
il peut être judicieux de fournir un message de confirmation pour l’utilisateur. Je ne vais
pas le faire ici pour ne pas trop alourdir le projet et il s’agit uniquement de traitement côté
client.

Par contre après la suppression il faut afficher quelque chose pour dire que l’opération
s’est réalisée correctement. On voit qu’il y a une redirection avec la méthode back qui
renvoie la même page. D’autre part la méthode with permet de flasher une information
dans la session. Cette information ne sera valide que pour la requête suivante. Dans
notre vue index on va prévoir quelque chose pour afficher cette information :

@section('content')
@if(session()->has('info'))
<div class="notification is-success">
{{ session('info') }}
</div>
@endif
<div class="card">

La directif @if permet de déterminer si une information est présente en session, et si


c’est le cas de l’afficher :

Classiquement on prévoit un bouton pour fermer la barre de notification. Là encore je vais


simplifier parce c’est aussi un traitement purement client. Il suffit d’ajouter un peu de
Javascript pour le faire (vous pouvez consulter la documentation de Bulma sur le sujet).

Dans le prochain article on verra comment créer et modifier un film.

En résumé
Une ressource dans Laravel est constituée d’un contrôleur comportant les 7
méthodes permettant une gestion complète.
Les routes vers une ressource sont créées avec une simple ligne de code.

17/18
Laravel permet de mettre en place facilement une pagination, il faut adapter
l’apparence en fonction du framework CSS qu’on utilise.
On peut mettre en place dans le routage une liaison implicite pour générer
automatiquement une instance de la classe dont l’identifiant est passée dans l’url.

18/18
Cours Laravel 9 – les données – les ressources (2/2)
laravel.sillo.org/cours-laravel-9-les-donnees-les-ressources-2-2/

15 février 2022

Dans cet article nous allons terminer de coder la ressource que nous avons commencée
dans le précédent. Il nous reste à voir comment créer et modifier un film.

Pour vous simplifier la vie voilà le code de démarrage de cet article qui tient donc compte
de tout ce qu’on a vu dans le précédent.

Créer un film

Les routes

Pour la création d’un film on va avoir deux routes :

pour afficher le formulaire de création


pour soumettre le formulaire

Le contrôleur

Dans le contrôleur ce sont les méthodes create et store qui sont concernées. On va
donc les coder :

use App\Http\Requests\Film as FilmRequest;

...

public function create()


{
return view('create');
}

public function store(FilmRequest $filmRequest)


{
Film::create($filmRequest->all());

return redirect()->route('films.index')->with('info', 'Le film a bien été


créé');
}

La vue index
On a déjà codé la vue index mais on n’a pas prévu de bouton pour créer un film. Alors on
va arranger ça (je remets tout le code pour que ce soit plus simple) :

1/15
@extends('template')

@section('css')
<style>
.card-footer {
justify-content: center;
align-items: center;
padding: 0.4em;
}
.is-info {
margin: 0.3em;
}
</style>
@endsection

@section('content')
@if(session()->has('info'))
<div class="notification is-success">
{{ session('info') }}
</div>
@endif
<div class="card">
<header class="card-header">
<p class="card-header-title">Films</p>
<a class="button is-info" href="{{ route('films.create') }}">Créer un
film</a>
</header>
<div class="card-content">
<div class="content">
<table class="table is-hoverable">
<thead>
<tr>
<th>#</th>
<th>Titre</th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
@foreach($films as $film)
<tr>
<td>{{ $film->id }}</td>
<td><strong>{{ $film->title }}</strong></td>
<td><a class="button is-primary" href="{{
route('films.show', $film->id) }}">Voir</a></td>
<td><a class="button is-warning" href="{{
route('films.edit', $film->id) }}">Modifier</a></td>
<td>
<form action="{{ route('films.destroy', $film-
>id) }}" method="post">
@csrf
@method('DELETE')
<button class="button is-danger"
type="submit">Supprimer</button>
</form>

2/15
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
<footer class="card-footer is-centered">
{{ $films->links() }}
</footer>
</div>
@endsection

Maintenant on a un bouton :

La vue create

On crée la vue create :

3/15
@extends('template')

@section('content')
<div class="card">
<header class="card-header">
<p class="card-header-title">Création d'un film</p>
</header>
<div class="card-content">
<div class="content">
<form action="{{ route('films.store') }}" method="POST">
@csrf
<div class="field">
<label class="label">Titre</label>
<div class="control">
<input class="input @error('title') is-danger @enderror"
type="text" name="title" value="{{ old('title') }}" placeholder="Titre du film">
</div>
@error('title')
<p class="help is-danger">{{ $message }}</p>
@enderror
</div>
<div class="field">
<label class="label">Année de diffusion</label>
<div class="control">
<input class="input" type="number" name="year" value="{{
old('year') }}" min="1950" max="{{ date('Y') }}">
</div>
@error('year')
<p class="help is-danger">{{ $message }}</p>
@enderror
</div>
<div class="field">
<label class="label">Description</label>
<div class="control">
<textarea class="textarea" name="description"
placeholder="Description du film">{{ old('description') }}</textarea>
</div>
@error('description')
<p class="help is-danger">{{ $message }}</p>
@enderror
</div>
<div class="field">
<div class="control">
<button class="button is-link">Envoyer</button>
</div>
</div>
</form>
</div>
</div>
</div>
@endsection

4/15
Comme on a prévu la validation on va vérifier que ça marche :

5/15
Par contre le nom des champs n’est pas très judicieux, on va corriger ça dans le fichier
des langues (lang/fr/validation.php) :

],
'attributes' => [
'title' => 'titre',
'year' => 'année',
],
];

Maintenant c’est quand même mieux :

Lorsqu’un film a été créé on retourne à la vue index avec un message :

6/15
Modifier un film

Les routes

Pour la modification d’un film on va avoir deux routes :

pour afficher le formulaire de modification


pour soumettre le formulaire

Le contrôleur
Dans le contrôleur ce sont les méthodes edit et update qui sont concernées. On va donc
les coder :

7/15
public function edit(Film $film)
{
return view('edit', compact('film'));
}

public function update(FilmRequest $filmRequest, Film $film)


{
$film->update($filmRequest->all());

return redirect()->route('films.index')->with('info', 'Le film a bien été


modifié');
}

Le code ressemble beaucoup à celui vu ci-dessus pour la création et c’est normal.

La vue edit

Là aussi on va avoir pratiquement le même code que la vue create. La différence c’est
qu’il faut renseigner les contrôles du formulaire au départ. On crée la vue edit :

8/15
@extends('template')

@section('content')
<div class="card">
<header class="card-header">
<p class="card-header-title">Modification d'un film</p>
</header>
<div class="card-content">
<div class="content">
<form action="{{ route('films.update', $film->id) }}"
method="POST">
@csrf
@method('put')
<div class="field">
<label class="label">Titre</label>
<div class="control">
<input class="input @error('title') is-danger @enderror"
type="text" name="title" value="{{ old('title', $film->title) }}"
placeholder="Titre du film">
</div>
@error('title')
<p class="help is-danger">{{ $message }}</p>
@enderror
</div>
<div class="field">
<label class="label">Année de diffusion</label>
<div class="control">
<input class="input" type="number" name="year" value="{{
old('year', $film->year) }}" min="1950" max="{{ date('Y') }}">
</div>
@error('year')
<p class="help is-danger">{{ $message }}</p>
@enderror
</div>
<div class="field">
<label class="label">Description</label>
<div class="control">
<textarea class="textarea" name="description"
placeholder="Description du film">{{ old('description', $film->description) }}
</textarea>
</div>
@error('description')
<p class="help is-danger">{{ $message }}</p>
@enderror
</div>
<div class="field">
<div class="control">
<button class="button is-link">Envoyer</button>
</div>
</div>
</form>
</div>
</div>
</div>
@endsection

9/15
Là encore on a la validation qui fonctionne et le message qui informe de la modification
avec la redirection :

Ordonner les films


Pour le moment les films sont listés dans l’ordre de leur identifiant, ce qui n’est pas très
heureux. Il serait plus judicieux d’avoir la liste dans l’ordre alphabétique des noms de
films. Pour le faire il suffit d’intervenir dans le contrôleur :

public function index()


{
$films = Film::oldest('title')->paginate(5);

Maintenant on a bien les films dans l’ordre désiré :

Finalement aussi quel intérêt de faire apparaître l’identifiant ? Surtout maintenant qu’ils
sont dans le désordre, on va donc modifier la vue index :

10/15
<thead>
<tr>
<th>Titre</th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
@foreach($films as $film)
<tr>
<td><strong>{{ $film->title
}}</strong></td>
<td><a class="button is-
primary" href="{{ route('films.show',
$film->id) }}">Voir</a></td>
<td><a class="button is-
warning" href="{{ route('films.edit',
$film->id) }}">Modifier</a></td>
<td>
<form action="{{
route('films.destroy', $film->id) }}"
method="post">
@csrf
@method('DELETE')
<button
class="button is-danger"
type="submit">Supprimer</button>
</form>
</td>
</tr>
@endforeach
</tbody>

C’est beaucoup mieux :

11/15
Soft delete
Telle qu’on a prévu la suppression des films ils sont définitivement retirés de la base.
Laravel propose une autre façon de procéder avec le soft delete. Dans un premier temps
on se contente de marquer le film comme supprimé en renseignant une colonne
deleted_at. Dans un deuxième temps on peut faire la suppression définitive. En gros on
crée une corbeille.

La migration

Dans la migration de la table films on ajoute la colonne avec la méthode softDeletes :

public function up()


{
Schema::create('films', function (Blueprint $table) {
...
$table->softDeletes();
});
}

On relance la migration avec la population :

php artisan migrate:fresh --seed

Le modèle

Au niveau du modèle Film on ajoute le trait SoftDeletes :

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Film extends Model


{
use HasFactory, SoftDeletes;

protected $fillable = ['title', 'year', 'description'];


}

Maintenant quand on supprime un film on le conserve dans la base et on renseigne la


colonne deleted_at :

Les routes

On ajoute deux routes pour les deux nouvelles actions :

12/15
Route::controller(FilmController::class)->group(function () {
Route::delete('films/force/{film}', 'forceDestroy')-
>name('films.force.destroy');
Route::put('films/restore/{film}', 'restore')->name('films.restore');
});

La possibilité de créer un groupe avec le contrôleur est une nouveauté de la version 9 de


Laravel.

Le contrôleur

Dans le contrôleur il faut déjà modifier la méthode index pour inclure les films de la
corbeille :

public function index()


{
$films = Film::withTrashed()->oldest('title')->paginate(5);

return view('index', compact('films'));


}

Dans la méthode destroy il faut changer le texte du message :

return back()->with('info', 'Le film a bien été mis dans la corbeille.');

Enfin il faut créer les deux nouvelles méthodes :

public function forceDestroy($id)


{
Film::withTrashed()->whereId($id)->firstOrFail()->forceDelete();

return back()->with('info', 'Le film a bien été supprimé définitivement dans


la base de données.');
}

public function restore($id)


{
Film::withTrashed()->whereId($id)->firstOrFail()->restore();

return back()->with('info', 'Le film a bien été restauré.');


}

La vue index

Il ne reste plus qu’à modifier la vue index :

13/15
@foreach($films as $film)
<tr @if($film->deleted_at) class="has-background-grey-lighter" @endif>
<td><strong>{{ $film->title }}</strong></td>
<td>
@if($film->deleted_at)
<form action="{{ route('films.restore', $film->id) }}"
method="post">
@csrf
@method('PUT')
<button class="button is-primary"
type="submit">Restaurer</button>
</form>
@else
<a class="button is-primary" href="{{ route('films.show',
$film->id) }}">Voir</a>
@endif
</td>
<td>
@if($film->deleted_at)
@else
<a class="button is-warning" href="{{ route('films.edit',
$film->id) }}">Modifier</a>
@endif
</td>
<td>
<form action="{{ route($film->deleted_at? 'films.force.destroy' :
'films.destroy', $film->id) }}" method="post">
@csrf
@method('DELETE')
<button class="button is-danger" type="submit">Supprimer</button>
</form>
</td>
</tr>
@endforeach

Fonctionnement
Maintenant quand on clique sur le bouton Supprimer d’un film ça le met dans la corbeille
et il prend cette apparence avec un fond grisé et des boutons différents :

On peut alors soit le supprimer définitivement avec le bouton Supprimer, soit le restaurer
avec le bouton Restaurer.

Voici en téléchargement le code final de cet article.

14/15
En résumé
Un contrôleur de ressource permet de normaliser les opérations CRUD. Sa création
et son routage sont simples.
Le soft delete permet de mettre en place une corbeille pour les enregistrements.

15/15
Cours Laravel 9 – les données – la relation 1:n
laravel.sillo.org/cours-laravel-9-les-donnees-la-relation-1n/

16 février 2022

Pour le moment nous n’avons manipulé qu’une table avec Eloquent. Dans le présent
chapitre nous allons utiliser deux tables et les mettre en relation.

La relation la plus répandue et la plus simple entre deux tables est celle qui fait
correspondre un enregistrement d’une table à plusieurs enregistrements de l’autre table,
on parle de relation de un à plusieurs ou encore de relation de type 1:n.

Nous allons poursuivre notre gestion de films. Comme nous en avons beaucoup nous
éprouvons la nécessité de les classer en catégories : comédie, fantastique, drame,
thriller…

On va partir du projet dans l’état où on l’a laissé dans le précédent article. Il y a un lien de
téléchargement à la fin.

Les migrations

La table categories

Nous allons créer une table pour les catégories. On crée sa migration en même temps
que le modèle :

php artisan make:model Category --migration

Voici le code complété pour la migration :

public function up()


{
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name')->unique();
$table->string('slug')->unique();
$table->timestamps();
});
}

On va donc définir les colonnes ;

1/13
name : nom de la catégorie
slug : adaptation du nom pour le rendre compatible avec
les urls

La table films

On a déjà une migration pour la table films mais il va falloir la compléter pour pouvoir la
mettre en relation avec la table categories :

public function up()


{
Schema::disableForeignKeyConstraints();
Schema::create('films', function (Blueprint $table) {
...
$table->foreignId('category_id')
->constrained()
->onUpdate('restrict')
->onDelete('restrict');
});
}

Dans la table films on déclare une clé étrangère nommée category_id qui référence la
colonne id dans la table categories. La méthode foreignId crée une colonne de type
UNSIGNED BIGINT. La méthode constrained utilise les conventions de Laravel pour
déterminer le nom de la table référencée, ici c’est category.

En cas de suppression (onDelete) ou de modification (onUpdate) on a une restriction


(restrict).

Que signifient ces deux dernières conditions ?

Imaginez que vous ayez une catégorie avec l’id 5 qui a deux films, donc dans la table
films on a deux enregistrements avec category_id qui a la valeur 5. Si on supprime la
catégorie que va-t-il se passer ? On risque de se retrouver avec nos deux
enregistrements dans la table films avec une clé étrangère qui ne correspond à aucun
enregistrement dans la table categories. En mettant restrict on empêche la suppression
d’une catégorie qui a des films. On doit donc commencer par supprimer ses films avant
de le supprimer lui-même. On dit que la base assure l’intégrité référentielle. Elle
n’acceptera pas non plus qu’on utilise pour category_id une valeur qui n’existe pas dans
la table categories.

2/13
Une autre possibilité est cascade à la place de restrict. Dans ce cas si vous supprimez
une catégorie ça supprimera en cascade les films de cette catégorie.

C’est une option qui est moins utilisée parce qu’elle peut s’avérer dangereuse, surtout
dans une base comportant de multiples tables en relation. Mais c’est aussi une stratégie
très efficace parce que c’est le moteur de la base de données qui se charge de gérer les
enregistrements en relation, vous n’avez ainsi pas à vous en soucier au niveau du code.

On pourrait aussi ne pas signaler à la base qu’il existe une relation et la gérer seulement
dans notre code. Mais c’est encore plus dangereux parce que la moindre erreur de
gestion des enregistrements dans votre code risque d’avoir des conséquences
importantes dans votre base avec de multiples incohérences.

On va lancer les migrations en rafraichissant la base :

php artisan migrate:fresh

Les migrations sont effectuées dans l’ordre alphabétique, ce qui peut générer un
problème avec les clés étrangères. Si la table référencée est créée après on va tomber
sur une erreur du genre :

Illuminate\Database\QueryException : SQLSTATE[HY000]: General error: 1215 Cannot


add foreign key constraint (SQL: alter table `films` add constraint
`films_category_id_foreign` foreign key (`category_id`) references `categories`
(`id`) on delete restrict on update restrict)

C’est pour cette raison que j’ai ajouté cette ligne dans la migration :

Schema::disableForeignKeyConstraints();

On désactive temporairement le contrôle référentiel le temps de créer les tables.

La population
On va remplir les tables avec des enregistrements pour nos essais.

Les catégories

On va définir un certain nombre de catégories. Alors on crée un factory :

php artisan make:factory CategoryFactory --model=Category

On complète le code :

3/13
use Illuminate\Support\Str;

...

public function definition()


{
$name = $this->faker->word();
return [
'name' => $name,
'slug' => Str::slug($name),
];
}

La relation
On a la situation suivante :

une catégorie peut avoir plusieurs films,


un film n’appartient qu’à une catégorie.

Il faut trouver un moyen pour référencer cette relation dans les tables. Le principe est
simple et on l’a vu dans les migrations : on prévoit dans la table films une colonne
destinée à recevoir l’identifiant de la catégorie. On appelle cette ligne une clé étrangère
parce qu’on enregistre ici la clé d’une autre table. Voici une représentation visuelle de
cette relation :

Vous voyez la relation films_category_id_foreign dessinée entre la clé id dans la table


categories et la clé étrangère category_id dans la table films.

Les modèles et la relation

Le modèle Category

Le modèle Category se trouve ici (on l’a créé en même temps que la migration) :

On va lui ajouter ce code :

4/13
public function films()
{
return $this->hasMany(Film::class);
}

On déclare ici avec la méthode films (au pluriel) qu’une


catégorie a plusieurs (hasMany) films (Film). On aura ainsi une
méthode pratique pour récupérer les films d’une catégorie.

Le modèle Film
Dans le modèle Film on va coder la réciproque :

public function category()


{
return $this->belongsTo(Category::class);
}

Ici on a la méthode category (au singulier) qui permet de trouver la catégorie à laquelle
appartient (belongsTo) le film.

La relation 1:n
Voici une schématisation de la relation avec les deux méthodes :

Si vous ne spécifiez pas de manière explicite le nom de la table dans un modèle, Laravel
le déduit à partir du nom du modèle en le mettant au pluriel (à la mode anglaise) et en
mettant la première lettre en minuscule. Donc avec le modèle Film il en conclut que la

5/13
table s’appelle films. Si ce n’était pas satisfaisant il faudrait créer une propriété $table.

Les deux méthodes mises en place permettent de récupérer facilement un


enregistrement lié. Par exemple pour avoir tous les films de la catégorie qui a l’id 1 :

$films = App\Models\Category::find(1)->films;

De la même manière on peut trouver la catégorie du film d’id 1 :

$category = App\Models\Film::find(1)->category;

Vous voyez que le codage devient limpide avec ces méthodes !

La population (seeding)
Pour la population on va justement utiliser la relation qu’on vient de mettre en place. On
modifie ainsi DatabaseSeeder :

use App\Models\{ Film, Category };

...

public function run()


{
Category::factory()
->has(Film::factory()->count(4))
->count(10)
->create();
}

On lance la population :

php artisan db:seed

Si vous tombez sur une erreur avec duplication de données c’est que faker n’est pas
assez aléatoire, alors relancez la mibration et la population.

On crée ainsi 10 catégories :

Avec cette population pour les catégories le nom et le slug sont indentiques mais dans
une situation plus réaliste il y aurait évidemment souvent des différences, avec les
majuscules, les espaces, les accents…

Et pour chacune 4 films associés.

On a maintenant tout en place pour commencer à nous amuser…

6/13
Route et contrôleur
On va définir une nouvelle route pour aller chercher les films par catégorie :

Route::controller(FilmController::class)->group(function () {
...
Route::get('category/{slug}/films', 'index')->name('films.category');
});

7/13
On voit qu’on pointe la méthode index du contrôleur. Cette méthode existe déjà mais ne
servait jusque là qu’à fournir la liste paginée des films sans tenir compte de catégories.
On met à jour le code du contrôleur :

use App\Models\{Film, Category};

...

public function index($slug = null)


{
$query = $slug ? Category::whereSlug($slug)->firstOrFail()->films() :
Film::query();
$films = $query->withTrashed()->oldest('title')->paginate(5);
$categories = Category::all();
return view('index', compact('films', 'categories', 'slug'));
}

On voit qu’on distingue le cas où on fournit un slug de catégorie et le cas où on n’en


fournit pas. On envoie toutes les informations nécessaires dans la vue index.

La vue index
On va donc un peu modifier la vue index pour ajouter cette fonctionnalité.
Essentiellement on ajoute une liste de choix :

<div class="card">
<header class="card-header">
<p class="card-header-title">Films</p>
<div class="select">
<select onchange="window.location.href = this.value">
<option value="{{ route('films.index') }}" @unless($slug) selected
@endunless>Toutes catégories</option>
@foreach($categories as $category)
<option value="{{ route('films.category', $category->slug) }}"
{{ $slug == $category->slug ? 'selected' : '' }}>{{ $category->name }}</option>
@endforeach
</select>
</div>
<a class="button is-info" href="{{ route('films.create') }}">Créer un
film</a>
</header>

On modifie aussi un peu le style pour tenir compte de la liste :

select, .is-info {
margin: 0.3em;
}

Maintenant quand on arrive avec l’url …/films on a :

8/13
Quand on clique sur une option de la liste, donc une des catégories, ça envoie la requête
qui va bien et ça affiche les films de cette catégorie :

Ici on a l’url …/category/voluptatum/films.

La vue show
On va aussi ajouter dans la vue show le nom de la catégorie du film. On complète le
contrôleur :

public function show(Film $film)


{
$category = $film->category->name;
return view('show', compact('film', 'category'));
}

Et la vue :

<div class="content">
<p>Année de sortie : {{ $film->year }}</p>
<p>Catégorie : {{ $category }}</p>
...
</div>

Et ça roule :

9/13
La création d’un film
Maintenant aussi quand on crée un film il faut l’attribuer à une catégorie. Il faut déjà
modifier la méthode create du contrôleur pour envoyer les catégories :

public function create()


{
$categories = Category::all();
return view('create', compact('categories'));
}

Et modifier la vue create en ajoutant une liste des catégories :

<form action="{{ route('films.store') }}" method="POST">


@csrf
<div class="field">
<label class="label">Catégorie</label>
<div class="select">
<select name="category_id">
@foreach($categories as $category)
<option value="{{ $category->id }}">{{ $category->name }}
</option>
@endforeach
</select>
</div>
</div>

10/13
On ajoute le champ category_id dans la propriété $fillable du modèle Film :

protected $fillable = ['title', 'year', 'description', 'category_id'];

Et on n’a même pas besoin de toucher à la méthode store du contrôleur ! D’autre part on
ne va pas se soucier de validation pour une liste imposée.

Les composeurs de vue


Vous avez peut-être remarqué une répétition de code dans ces deux méthodes du
contrôleur :

11/13
public function index($slug = null)
{
$query = $slug ? Category::whereSlug($slug)->firstOrFail()->films() :
Film::query();
$films = $query->withTrashed()->oldest('title')->paginate(5);
$categories = Category::all();
return view('index', compact('films', 'categories', 'slug'));
}

public function create()


{
$categories = Category::all();
return view('create', compact('categories'));
}

Dans les deux cas on va chercher toutes les catégories pour les envoyer à la vue.
Laravel propose le concept de composeur de vue (view composer) pour traiter cette
situation de façon plus élégante. Dans un premier temps on nettoie le contrôleur :

public function index($slug = null)


{
$query = $slug ? Category::whereSlug($slug)->firstOrFail()->films() :
Film::query();
$films = $query->withTrashed()->oldest('title')->paginate(5);
return view('index', compact('films', 'slug'));
}

public function create()


{
return view('create');
}

Donc là on n’envoie plus les catégories et ça ne fonctionne plus !

Changez ainsi le code du fichier App\Providers\AppServiceProvider :

12/13
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\View;
use App\Models\Category;

class AppServiceProvider extends ServiceProvider


{
public function register()
{
//
}

public function boot()


{
View::composer(['index', 'create'], function ($view) {
$view->with('categories', Category::all());
});
}
}

On utilise la façade View avec la méthode composer pour mettre en place le fait que
chaque fois qu’une des deux vues index ou create et appelée alors on associe la
variable categories qui contient toutes les catégories. Et ça fonctionne ! Classiquement
on créerait plutôt un provider dédié à cette tâche.

Pour simplifier l’exemple je ne prévois pas la possibilité de modifier la catégorie d’un film.

J’ai prévu un ZIP récupérable ici qui contient le code de cet article.

En résumé
Une relation de type 1:n nécessite la création d’une clé étrangère côté n.
Une relation dans la base nécessite la mise en place de méthodes spéciales dans
les modèles.
On dispose de plusieurs méthodes pour manipuler une relation.
Un composeur de vue permet de mutualiser du code pour envoyer des informations
dans les vues.

13/13
Cours Laravel 9 – les données – la relation n:n
laravel.sillo.org/cours-laravel-9-les-donnees-la-relation-nn/

16 février 2022

Dans le précédent chapitre nous avons vu la relation de type 1:n, la plus simple et la plus
répandue. Nous allons maintenant étudier la relation de type n:n, plus délicate à
comprendre et à mettre en œuvre. Nous allons voir qu’Eloquent permet de simplifier la
gestion de ce type de relation.

On va continuer l’application de gestion de films, toujours avec des catégories, mais


maintenant on va considérer qu’un film peut appartenir à plusieurs catégories, ce qui
change pas mal de choses…

Les données

La relation n:n

Imaginez une relation entre deux tables A et B qui permet de dire :

je peux avoir une ligne de la table A en relation avec plusieurs lignes de la table B,
je peux avoir une ligne de la table B en relation avec plusieurs lignes de la table A.

Cette relation ne se résout pas comme nous l’avons vu au chapitre précédent avec une
simple clé étrangère dans une des tables. En effet il nous faudrait des clés dans les deux
tables et plusieurs clés, ce qui n’est pas possible à réaliser.

La solution consiste à créer une table intermédiaire (nommée table pivot) qui sert à
mémoriser les clés étrangères. On va donc toujours avoir nos tables films et categories
mais en plus une table pivot entre les deux.

Les migrations

Au niveau des migration celle pour les catégories ne va pas changer. Pour celle des films
on va retirer la clé étrangère, donc il ne va rester que ça :

public function up()


{
Schema::create('films', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->year('year');
$table->text('description');
$table->timestamps();
$table->softDeletes();
});
}

1/11
Et il nous faut en plus la migration pour la table pivot qui elle aura 2 clés étrangères : une
pour les catégories et une autre pour les films :

php artisan make:migration create_category_film_table

Par convention on met les deux noms au singulier et dans l’ordre alphabétique donc
category_film.

Et on code la migration pour les deux clé étrangères :

public function up()


{
Schema::create('category_film', function (Blueprint $table) {
$table->id();
$table->timestamps();

$table->foreignId('category_id')
->constrained()
->onUpdate('cascade')
->onDelete('cascade');

$table->foreignId('film_id')
->constrained()
->onUpdate('cascade')
->onDelete('cascade');
});
}

Cette fois j’ai choisi cascade pour les clés étrangères. Donc si on supprime une
catégorie ou un film la table pivot sera automatiquement mise à jour.

Et on rafraichit la base :

php artisan migrate:fresh

Au niveau des tables on a ce schéma :

2/11
La relation entre les deux tables est assurée par la table pivot . Cette table pivot contient
les clés des deux tables :

category_id pour mémoriser la clé de la table categories,


film_id pour mémoriser la clé de la table films.

De cette façon on peut avoir plusieurs enregistrements liés entre les deux tables, il suffit à
chaque fois d’enregistrer les deux clés dans la table pivot. Évidemment au niveau du
code ça demande un peu d’intendance parce qu’il y a une table supplémentaire à gérer.

Les modèles

Dans le modèle Category on change la relation :

public function films()


{
return $this->belongsToMany(Film::class);
}

On déclare ici avec la méthode films (au pluriel) qu’une catégorie appartient à plusieurs
(belongsToMany) films (Film). On aura ainsi une méthode pratique pour récupérer les
films d’une catégorie.

C’est exactement pareil pour le modèle Film :

protected $fillable = ['title', 'year', 'description'];

public function categories()


{
return $this->belongsToMany(Category::class);
}

Au passage j’ai supprimé la colonne category_id dans la propriété $fillable.

C’est le même principe que pour les catégories puisque la relation est symétrique. On
déclare avec la méthode categories (au pluriel) qu’un film appartient à plusieurs
(belongsToMany) catégories (Category).

La relation n:n
Voici une schématisation de cette relation avec les deux méthodes symétriques :

3/11
Toutes les méthodes qu’on a vues pour la relation 1:n fonctionnent avec la relation n:n,
ce qui est logique. Le fait qu’il y ait une table pivot ne change rien au fait que la relation,
vue de l’une des deux tables, ressemble à s’y méprendre à une relation 1:n. Si je choisis
une catégorie par exemple je sais qu’elle peut avoir plusieurs films liés.

La population
Il nous faut encore créer des enregistrements pour nos essais. On ne peut pas se
contenter de garder ce qu’on avait fait parce que maintenant ça serait bien qu’un film
appartienne à plusieurs catégories. Alors voilà le nouveau code de DatabaseSeeder :

public function run()


{
Category::factory()->count(10)->create();

$ids = range(1, 10);

Film::factory()->count(40)->create()->each(function ($film) use($ids) {


shuffle($ids);
$film->categories()->attach(array_slice($ids, 0, rand(1, 4)));
});
}

On commence par créer 10 catégories. Ensuite on crée 40 films et pour chacun on


attache entre 1 et 4 catégories. On passe à la méthode attach l’identifiant (on peut en
mettre plusieurs dans un tableau comme je l’ai fait ici) de l’enregistrement en relation et
Eloquent se charge de renseigner la table pivot. Il existe aussi la méthode detach qui fait
exactement l’inverse.

4/11
Affichage d’un film et eager loading
Pour l’affichage d’un film on avait prévu de préciser la catégorie à laquelle appartenait ce
film. Il est évident qu’il va falloir prendre en compte maintenant le fait qu’on peut avoir
plusieurs catégories. On va mettre à jour la méthode show du contrôleur :

public function show(Film $film)


{
$film->with('categories')->get();
return view('show', compact('film'));
}

On sait qu’avec la liaison de la route on a déjà le modèle du film. On le complète en


ajoutant (with) ses catégories. A la sortie on a une collection avec la relation :

5/11
Collection {#265 ▼
#items: array:40 [▼
0 => Film {#276 ▼
#fillable: array:3 [▼
0 => "title"
1 => "year"
2 => "description"
]
...
#attributes: array:7 [▼
"id" => 1
"title" => "In qui cumque."
"year" => 2013
"description" => "Consequatur cumque odit delectus velit et. Sit non qui
harum vel quas autem numquam. Repellat ea praesentium voluptas fugit hic.
Voluptate ut tempore neque veni ▶"
"created_at" => "2019-09-03 11:58:23"
"updated_at" => "2019-09-03 11:58:23"
"deleted_at" => null
]
...
#relations: array:1 [▼
"categories" => Collection {#317 ▼
#items: array:2 [▼
0 => Category {#452 ▶}
1 => Category {#500 ▶}
]
}
]
...

On appelle cette manière de faire l’eager loading (par opposition au lazy loading). Ça
permet d’éviter de multiples accès à la base pour aller récupérer des valeurs.

Maintenant dans la vue show on n’a plus de problème pour afficher les catégories :

6/11
@extends('template')

@section('content')
<div class="card">
<header class="card-header">
<p class="card-header-title">Titre : {{ $film->title }}</p>
</header>
<div class="card-content">
<div class="content">
<p>Année de sortie : {{ $film->year }}</p>
<hr>
<p>Catégories :</p>
<ul>
@foreach($film->categories as $category)
<li>{{ $category->name }}</li>
@endforeach
</ul>
<hr>
<p>Description :</p>
<p>{{ $film->description }}</p>
</div>
</div>
</div>
@endsection

On utilise la directive @foreach pour boucler sur les catégories :

7/11
Il y a quand même une chose qui me dérange. On a deux accès à la base :

au niveau du traitement de la route avec la liaison implicite


au niveau du contrôleur pour aller chercher les catégories

Ça serait quand même plus élégant de tout faire d’un coup ! On va changer la liaison
implicite en liaison explicite. Dans le provider RouteServiceProvider qui, comme son
nom l’indique, est consacré aux routes, on va ajouter ce code :

use App\Models\Film;

...

public function boot()


{
...

Route::bind('film', function ($value) {


return Film::with('categories')->find($value) ?? abort(404);
});
}

Maintenant on peu revenir à la version initiale du code dans le contrôleur :

public function show(Film $film)


{
return view('show', compact('film'));
}

On a ainsi un seul accès à la base ! Évidemment on aura le chargement des catégories


pour toutes les routes concernées, mais ce n’est pas gênant et pourra même s’avérer
très utile ! Sauf pour le soft delete où ça va coincer. Alors pour ces deux routes on va
changer le nom du paramètre :

Route::controller(FilmController::class)->group(function () {
Route::delete('films/force/{id}', 'forceDestroy')-
>name('films.force.destroy');
Route::put('films/restore/{id}', 'restore')->name('films.restore');
...
});

Création d’un film


Pour la création d’un film on va aussi devoir modifier le code parce qu’on ne peut pour le
moment choisir qu’une catégorie dans la liste. On va transformer la liste dans la vue
create pour un choix multiple :

8/11
<label class="label">Catégories</label>
<div class="select is-multiple">
<select name="cats[]" multiple>
@foreach($categories as $category)
<option value="{{ $category->id }}" {{ in_array($category-
>id, old('cats') ?: []) ? 'selected' : '' }}>{{ $category->name }}</option>
@endforeach
</select>
</div>

Remarquez que le nom du select est accompagné de


crochets (cats[]) pour signifier qu’on va envoyer un
tableau de valeurs. Remarquez aussi la stratégie pour
récupérer les catégories sélectionnées en cas de souci de
validation.

On va avoir un peu plus de travail dans la méthode store


du contrôleur.

On a dans la requête ces éléments :

array:5 [▼
"_token" =>
"qqARcLpGc6YDJ4jRpzazHeDbPlrxMSnSZYbtO9hJ"
"cats" => array:2 [▼
0 => "1"
1 => "3"
]
"title" => "La vie"
"year" => "1952"
"description" => "Un film d'un terrible ennui."
]

Voici la méthode store modifiée en conséquence :

public function store(FilmRequest $filmRequest)


{
$film = Film::create($filmRequest->all());
$film->categories()->attach($filmRequest->cats);
return redirect()->route('films.index')->with('info', 'Le film a bien été
créé');
}

On vérifie qu’on a les bonnes catégories dans la fiche du film :

9/11
Modification d’un film
Ce qu’on a fait pour la création va nous servir pour la modification. D’ailleurs
précédemment on n’avait pas prévu la modification de la catégorie pour un film pour ne
pas trop alourdir le code, mais là on va le faire.

Déjà dans le provider AppServiceProvider on va ajouter la vue edit dans le composeur


de vue pour récupérer toutes les catégories :

public function boot()


{
View::composer(['index', 'create', 'edit'], function ($view) {
$view->with('categories', Category::all());
});
}

Ensuite dans la vue edit on ajoute la liste des catégories comme on l’a fait pour la vue
create :

<div class="field">
<label class="label">Catégories</label>
<div class="select is-multiple">
<select name="cats[]" multiple>
@foreach($categories as $category)
<option value="{{ $category->id }}" {{ in_array($category->id,
old('cats') ?: $film->categories->pluck('id')->all()) ? 'selected' : '' }}>{{
$category->name }}</option>
@endforeach
</select>
</div>
</div>

10/11
Le remplissage est un peu plus délicat parce qu’on a deux situations :

quand on charge le formulaire au départ on doit sélectionner les catégories


actuelles du film
quand on a un retour de validation incorrecte on doit sélectionner les catégories
précédemment sélectionnées

Laravel possède un système de collections très performant. Par exemple ici quand on
écrit $film->categories on obtient la collection de toutes les catégories du film. La
méthode pluck permet de ne garder que la clé qui nous intéresse, ici l’id. Enfin la
méthode all transforme la collection en tableau parce que c’est ce dont nous avons
besoin ici.

Il ne nous reste plus qu’à modifier la méthode update du contrôleur :

public function update(FilmRequest $filmRequest, Film $film)


{
$film->update($filmRequest->all());
$film->categories()->sync($filmRequest->cats);
return redirect()->route('films.index')->with('info', 'Le film a bien été
modifié');
}

Pour modifier la table pivot on utilise cette fois la puissante méthode sync. On lui donne
un tableau en paramètre, si un enregistrement se trouve dans la table mais pas dans le
tableau alors il est supprimé et si une valeur se trouve dans la tableau mais pas dans la
table alors on crée l’enregistrement dans la table. C’est bien une synchronisation !

On a maintenant une application qui commence à ressembler à quelque chose !

J’ai prévu un ZIP récupérable ici qui contient le code de cet article.

En résumé
Une relation de type n:n nécessite la création d’une table pivot.
L’eager loading permet de limiter le nombre d’accès à la base de données.
Si la liaison implicite ne suffit pas on peut faire de la liaison explicite avec un
traitement personnalisé.
Eloquent gère élégamment les tables pivots avec des méthodes adaptées (attach,
detach, sync…).‌

11/11
Cours Laravel 9 – les données – le polymorphisme
laravel.sillo.org/cours-laravel-9-les-donnees-le-polymorphisme/

17 février 2022

Lors des deux précédents chapitres on a vu les principales relations que nous offre
Eloquent : hasMany et belongsToMany. Je ne vous ai pas parlé de la relation hasOne
parce que c’est juste du hasMany limité à un seul enregistrement et est peu utilisé. Dans
tous les cas qu’on a vus on considère 2 tables en relation, même lorsqu’un table pivot
sert de lien entre les deux. Dans le présent chapitre on va envisager le cas où une table
peut être en relation avec plusieurs autres tables, ce qui se nomme du polymorphisme.

Un peu de théorie

La relation 1:1 ou 1:n

On a vu cette relation, en voici une schématisation pour fixer les esprits :

On a a possède un b (hasOne) ou a possède plusieurs b (hasMany).

La réciproque : b est possédé par un a (belongsTo).

La relation n:n
On a vu aussi cette relation, en voici une schématisation pour fixer les esprits :

1/17
On a a appartient à un ou plusieurs b (belongsToMany).

Et on a b appartient à un ou plusieurs a (belongsToMany).

La relation une table vers plusieurs tables

Type de relation 1:n

Maintenant imaginons cette situation :

La table c peut être en relation soit avec la table a, soit avec la table b. Dans cette
situation comment gérer une clé étrangère dans la table c ? Comment l’appeler et
comment savoir avec quelle table elle est en relation ?

On voit bien qu’il va falloir une autre information : connaître sûrement la table en
relation.

2/17
Puisqu’on a besoin de deux informations il nous faut deux colonnes :

On a donc deux colonnes :

relatable_id : la clé étrangère qui mémorise l’identifiant de l’enregistrement en


relation
relatable_type : la classe du modèle en relation.

Voici la figure complétée avec les noms de ces relations :

3/17
morphOne : c’est le hasOne mais issu de plusieurs tables.
morphMany : c’est le hasMany mais issu de plusieurs tables.
morphTo : c’est le belongsTo mais à destination de plusieurs tables.

Type de relation n:n

On peut avoir le même raisonnement pour une relation de type n:n avec plusieurs tables
d’un côté de la relation :

4/17
morphToMany : c’est le belongsToMany mais issu de plusieurs tables.
morphedByMany : c’est le belongsToMany mais en direction de plusieurs tables.

Les données
On va poursuivre l’exemple de la gestion des films en introduisant du polymorphisme.
Jusque là on avait des catégories et des films, on va ajouter des acteurs, c’est quand
même assez pertinent pour des films ! Un acteur peut jouer dans plusieurs films et un film
a plusieurs acteurs, on a donc à nouveau une relation de type n:n. On pourrait évidement
utiliser une seconde table pivot mais ça sera plus élégant avec une seule et du
polymorphisme.

Les migrations

On ne change rien aux migrations des catégories et des films. On supprime la table pivot
qu’on avait créée. Et on crée une migration pour notre nouvelle table pivot :

php artisan make:migration filmables

Et on va prévoir ce code :

5/17
<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class Filmables extends Migration


{
public function up()
{
Schema::create('filmables', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->foreignId('film_id')
->constrained()
->onDelete('cascade')
->onUpdate('cascade');
$table->morphs('filmable');
});
}

public function down()


{
Schema::dropIfExists('filmables');
}
}

La méthode morphs est bien pratique parce qu’elle crée automatiquement les deux
colonnes pour la relation polymorphique.

On a aussi besoin d’une migration et d’un modèle pour les acteurs :

php artisan make:model Actor --migration

Avec ce code :

Schema::create('actors',
function (Blueprint $table) {
$table->id();
$table->string('name')-
>unique();
$table->string('slug')-
>unique();
$table->timestamps();
});

On prévoit juste un nom et un slug, comme pour les catégories.

On régénère les tables :

php artisan migrate:fresh

Si tout se passe bien on doit avoir les 4 tables qui nous intéressent :

6/17
Les relations

Category

Pour le modèle Category on prévoit cette relation (qui remplace la précédente) :

protected $fillable = ['name', 'slug'];

public function films()


{
return $this->morphToMany(Film::class, 'filmable');
}

Actor

Ca va être pareil pour le modèle Actor :

public function films()


{
return $this->morphToMany(Film::class, 'filmable');
}

Film

Pour le modèle Film on prévoit ces deux relations :

public function categories()


{
return $this->morphedByMany(Category::class, 'filmable');
}

public function actors()


{
return $this->morphedByMany(Actor::class, 'filmable');
}

La population

7/17
On va remplir les tables pour nos essais.

On va avoir besoin d’un factory pour les acteurs :

php artisan make:factory ActorFactory

Avec ce code :

<?php

namespace Database\Factories;

use App\Models\Actor;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

class ActorFactory extends Factory


{

protected $model = Actor::class;

public function definition()


{
$name = $this->faker->name();
return [
'name' => $name,
'slug' => Str::slug($name),
];
}
}

Il ne reste plus qu’à modifier le code de DatabaseSeeder :

8/17
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use App\Models\{ Film, Category, Actor };
use Illuminate\Support\Str;

class DatabaseSeeder extends Seeder


{
/**
* Seed the application's database.
*
* @return void
*/
public function run()
{
Actor::factory()->count(10)->create();

$categories = [
'Comédie',
'Drame',
'Action',
'Fantastique',
'Horreur',
'Animation',
'Espionnage',
'Guerre',
'Policier',
'Pornographique',
];

foreach($categories as $category) {
Category::create(['name' => $category, 'slug' =>
Str::slug($category)]);
}

$ids = range(1, 10);


Film::factory()->count(40)->create()->each(function ($film) use($ids) {
shuffle($ids);
$film->categories()->attach(array_slice($ids, 0, rand(1, 4)));
shuffle($ids);
$film->actors()->attach(array_slice($ids, 0, rand(1, 4)));
});
}
}

Et on lance la population :

php artisan db:seed

Cette fois j’ai prévu des noms de catégories réalistes et non plus aléatoires :

J’ai aussi prévu 10 acteurs avec des noms aléatoires :

9/17
On a aussi 40 films. Et enfin dans la table pivot j’ai
créé aussi de façon aléatoire des liaisons entre
catégories et acteurs et films. En voici un aperçu :

10/17
On voit que pour chaque enregistrement on a le nom du modèle et l’id comme on l’avait
prévu.

La page d’accueil
Dans la page d’accueil pour le moment on a ça :

Une liste des films avec des boutons d’action, et aussi un choix selon la catégorie. Tout
ça fonctionne encore mais ça serait bien d’ajouter le choix par acteur aussi.

On avait utilisé un view composer pour générer l’ensemble des catégories pour les vues
(dans AppServiceProvider) :

public function boot()


{
View::composer(['index', 'create', 'edit'], function ($view) {
$view->with('categories', Category::all());
});
}

On va ajouter les acteurs :

use App\Models\{ Category, Actor };

...

$view->with('actors', Actor::all());

Route

Il nous faut aussi ajouter une route pour la sélection des films par acteur :

Route::controller(FilmController::class)->group(function () {
...
Route::get('actor/{slug}/films', 'index')->name('films.actor');
});

Contrôleur

11/17
Dans le contrôleur pour le moment on a ce code :

public function index($slug = null)


{
$query = $slug ? Category::whereSlug($slug)->firstOrFail()->films() :
Film::query();
$films = $query->withTrashed()->oldest('title')->paginate(5);
return view('index', compact('films', 'slug'));
}

On distingue le cas où on a un paramètre, donc un slug et le cas où on n’en a pas. C’était


parfait avec juste les catégories mais plus maintenant parce qu’on a deux cas avec des
slugs. On va donc le changer ainsi :

use App\Models\{Film, Category, Actor};


use Illuminate\Support\Facades\Route;
...

public function index($slug = null)


{
$model = null;

if($slug) {
if(Route::currentRouteName() == 'films.category') {
$model = new Category;
} else {
$model = new Actor;
}
}

$query = $model ? $model->whereSlug($slug)->firstOrFail()->films() :


Film::query();
$films = $query->withTrashed()->oldest('title')->paginate(5);
return view('index', compact('films', 'slug'));
}

La vue index
Il ne nous reste plus qu’à mettre à jour la vue index :

<div class="select">
<select onchange="window.location.href = this.value">
<option value="{{ route('films.index') }}" @unless($slug) selected
@endunless>Tous acteurs</option>
@foreach($actors as $actor)
<option value="{{ route('films.actor', $actor->slug) }}" {{ $slug ==
$actor->slug ? 'selected' : '' }}>{{ $actor->name }}</option>
@endforeach
</select>
</div>

On ajoute la liste à côté de celle des catégories :

12/17
Et tout fonctionne ! Mais c’est soit le choix par catégorie, soit par acteur. On ne peut pas
combiner les deux avec notre code mais on va s’en contenter…

Voir un film
Dans la fiche d’un film on n’a pas encore les acteurs, alors on va les ajouter. Si vous vous
rappelez on a fait de la liaison implicite pour faire de l’eager loading des catégories quand
on transmet l’identifiant d’un film dans l’url (RouteServiceProvider) :

public function boot()


{
...

Route::bind('film', function ($value) {


return Film::with('categories')->find($value) ?? abort(404);
});
}

On va donc ajouter les acteurs :

return Film::with('categories', 'actors')->find($value) ?? abort(404);

Dans la vue show on reproduit le code des catégories pour les acteurs :

<p>Catégories :</p>
<ul>
@foreach($film->categories as $category)
<li>{{ $category->name }}</li>
@endforeach
</ul>
<hr>
<p>Acteurs :</p>
<ul>
@foreach($film->actors as $actor)
<li>{{ $actor->name }}</li>
@endforeach
</ul>
<hr>

Et voilà le résultat :

13/17
Création d’un film
Dans le formulaire de création d’un film on va devoir ajouter le choix des acteurs. On
change la vue create :

14/17
<form action="{{ route('films.store') }}" method="POST">
@csrf
<div class="field is-grouped is-horizontal">
<label class="label field-label">Catégories</label>
<div class="select is-multiple">
<select name="cats[]" multiple>
@foreach($categories as $category)
<option value="{{ $category->id }}" {{ in_array($category->id,
old('cats') ?: []) ? 'selected' : '' }}>{{ $category->name }}</option>
@endforeach
</select>
</div>
<label class="label field-label">Acteurs</label>
<div class="select is-multiple">
<select name="acts[]" multiple>
@foreach($actors as $actor)
<option value="{{ $actor->id }}" {{ in_array($actor->id,
old('acts') ?: []) ? 'selected' : '' }}>{{ $actor->name }}</option>
@endforeach
</select>
</div>
</div>

On a ainsi les deux listes de choix :

Dans le contrôleur on prévoit l’attachement pour les acteurs en plus des catégories :

public function store(FilmRequest $filmRequest)


{
$film = Film::create($filmRequest->all());

$film->categories()->attach($filmRequest->cats);
$film->actors()->attach($filmRequest->acts);

return redirect()->route('films.index')->with('info', 'Le film a bien été


créé');
}

Et ça devrait être bon !

15/17
Modification d’un film
Dans le formulaire de modification d’un film on va devoir aussi ajouter le choix des
acteurs. On change la vue edit :

<form action="{{ route('films.update', $film->id) }}" method="POST">


@csrf
@method('put')
<div class="field is-grouped is-horizontal">
<label class="label field-label">Catégories</label>
<div class="select is-multiple">
<select name="cats[]" multiple>
@foreach($categories as $category)
<option value="{{ $category->id }}" {{ in_array($category->id,
old('cats') ?: $film->categories->pluck('id')->toArray()) ? 'selected' : '' }}>{{
$category->name }}</option>
@endforeach
</select>
</div>
<label class="label field-label">Acteurs</label>
<div class="select is-multiple">
<select name="acts[]" multiple>
@foreach($actors as $actor)
<option value="{{ $actor->id }}" {{ in_array($actor->id,
old('acts') ?: $film->actors->pluck('id')->toArray()) ? 'selected' : '' }}>{{
$actor->name }}</option>
@endforeach
</select>
</div>
</div>

Et dans le contrôleur on doit synchroniser aussi les acteurs :

public function update(FilmRequest $filmRequest, Film $film)


{
$film->update($filmRequest->all());

$film->categories()->sync($filmRequest->cats);
$film->actors()->sync($filmRequest->acts);

return redirect()->route('films.index')->with('info', 'Le film a bien été


modifié');
}

Et on a terminé le travail ! Avec toutefois un bémol… Quand on supprime un acteur ou


une catégorie, étant donné qu’on n’a pas de cascade, la table pivot reste renseignée
avec des enregistrements orphelins. Mais c’est une autre histoire qui nous entrainerait un
peu trop loin pour le moment. Il y a aussi pas mal de code redondant dans les vues et il y
aurait certainement quelque chose à faire aussi de ce côté.

J’ai prévu un ZIP récupérable ici qui contient le code de cet article.

En résumé

16/17
Lorsque plusieurs tables sont concernées d’un côté d’une relation on doit appliquer
le polymorphisme.
Laravel propose de nombreuses méthodes pour gérer le polymorphisme selon la
situation.

17/17
Cours Laravel 9 – les données – les ressources d’API
laravel.sillo.org/cours-laravel-9-les-donnees-les-ressources-dapi/

19 février 2022

Laravel permet de bâtir facilement des API à partir de ressource Eloquent. Voyons cela
dans cet article.

Les API REST


REST (Representational State Transfer) est un style d’architecture pour constituer des
API. Pour Laravel une API REST est :

organisée autours de ressources


sans persistance (aucune session)
orientée client-serveur
pouvant être mis en cache (donc une requête doit retourner toujours les mêmes
résultats)
retourner du JSON…

Pour continuer avec notre application de films on peut imaginer ce endpoint pour les
informations de tous les films :

GET /api/films
[
{
id: 1,
title: 'Aut numquam.'
},
{
id: 2,
title: 'Sint incidunt consequatur.'
}
...
]

Ou ce endpoint pour les informations concernant un film en particulier :

GET /api/films/2
{
id: 2,
name: 'Sint incidunt consequatur.'
}

On peut imaginer d’autres endpoints pour modifier, ajouter, supprimer…

Il ne reste plus qu’à voir comment on réalise ça avec Laravel mais vous en avez
désormais une idée déjà assez précise avec ce qu’on a déjà vu dans les précédents
articles.

1/11
Le contrôleur

On va créer le contrôleur :

php artisan make:controller Api/FilmController --api

On le retrouve rangé dans le dossier app/Http/Controllers/Api créé par la même


occasion :

Avec évidemment le même code que celui qu’on a eu


quand on avait déjà créé une ressource (et Laravel se
débrouille tout seul pour les espaces de nom) mais sans les
méthodes create et edit qui n’ont pas lieu d’être pour une
API :

2/11
<?php

namespace App\Http\Controllers\Api;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class FilmController extends Controller


{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
//
}

/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//
}

/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
//
}

/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
//
}

/**
* Remove the specified resource from storage.
*

3/11
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
}

Et on va coder les méthodes :

4/11
<?php

namespace App\Http\Controllers\Api;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Film;

class FilmController extends Controller


{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
return Film::all();
}

/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
Film::create($request->all());
}

/**
* Display the specified resource.
*
* @param \App\Film $film
* @return \Illuminate\Http\Response
*/
public function show(Film $film)
{
return $film;
}

/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\Film $film
* @return \Illuminate\Http\Response
*/
public function update(Request $request, Film $film)
{
$film->update($request->all());
}

/**
* Remove the specified resource from storage.

5/11
*
* @param \App\Film $film
* @return \Illuminate\Http\Response
*/
public function destroy(Film $film)
{
$film->delete();
}
}

Évidemment ça ressemble énormément à ce qu’on a fait précédemment et c’est normal


puisqu’il s’agit du même genre de traitement sur les mêmes données.

Les routes
Pour les routes on adopte aussi une ressource dans le fichier routes/api.php :

use App\Http\Controllers\Api\FilmController;

Route::apiResource('films', FilmController::class);

Ce qui donne ces routes :

Si vous ajoutez ces routes à l’application existante que nous avons construite dans les
précédents articles vous allez avoir une redondance des noms des routes et certaines
fonctionnalités ne vont plus marcher puisqu’on a utilisé justement ces noms de routes
pour les boutons d’action. Mais je m’intéresse uniquement à l’API dans cet article.

Le fait de déclarer ces routes dans le fichier routes/api.php a pour effet de leur appliquer
le middleware api au lieu de web. Si vous regardez dans le fichier app/Http/Kernel.php
vous allez voir la différence que ça génère :

6/11
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],

'api' => [
//
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];

Comme on est sans état plus de cookie, de session, de protection CSRF…

Le fonctionnement

Maintenant avec l’url …api/films, on obtient une réponse JSON (Laravel convertit
automatiquement la réponse en JSON sans qu’on lui demande) :

On obtient toutes les colonnes non cachées, c’est-à-dire celles qui ne sont pas définies
dans la propriété $hidden du modèle. Et comme on n’a pas créé cette propriété, on
récupère toutes les colonnes. Alors on va juste renvoyer titre, année et description en
ajoutant la propriété $hidden dans le modèle Film :

protected $hidden = ['id', 'created_at', 'updated_at', 'deleted_at'];

7/11
On dispose aussi de la propriété $visible qui est exactement l’inverse, on précise alors
seulement les colonne qu’on veut voir.

Avec l’url …/api/films/2 on obtient :

Mais pas uniquement ! Comme pour notre application on a prévu le chargement


automatique des catégories et des acteurs, on les récupère également et avec toutes
leurs colonnes ! Comme ce n’est pas très judicieux on va ajouter cette ligne pour les deux
modèles (Actor et Category) :

protected $visible = ['name'];

Maintenant on obtient ce résultat :

On pourrait aussi imaginer utiliser une pagination pour cette API. Il suffit de changer la
méthode index :

public function index()


{
return Film::paginate(4);
}

On peut alors demander la page qu’on veut, par exemple la seconde avec l’url …
api/films?page=2 :

8/11
On ne reçoit que les informations de la page 2 et en plus des renseignements concernant
la pagination.

On peut imaginer aussi filtrer, ordonner…

Je ne traite pas dans ce chapitre de l’authentification spécifique aux API. Je vous invite à
regarder par exemple la documentation de Passport sur le sujet.

Les ressources d’API


On a vu ci-dessus comme il est facile de retourner une réponse JSON à partir des
données d’un modèle. C’est parfait tant qu’on veut une simple sérialisation des valeurs
des colonnes. Mais parfois on désire effectuer quelques traitements intermédiaires (ça
correspond au design pattern transformer). Imaginons que nous souhaitons retourner
seulement les 10 premiers mots de la description…

On utilise Artisan pour créer la ressource :

php artisan make:resource Film

On se retrouve avec la ressource ici :

Avec ce code :

9/11
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class Film extends JsonResource


{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return
array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request)
{
return parent::toArray($request);
}
}

Là on se contente de tout sérialiser, ça ne va donc pas changer grand-chose à ce qu’on


avait précédemment. Dans le contrôleur, on utilise la ressource, par exemple pour la
méthode show :

use App\Http\Resources\Film as FilmResource;

...

public function show(Film $film)


{
return new FilmResource($film);
}

Maintenant pour l’url …/api/films/2 on obtient le même résultat que précédemment.


Maintenant changeons la ressource :

use Illuminate\Support\Str;

...

public function toArray($request)


{
return [
'title' => $this->title,
'year' => $this->year,
'description' => Str::words($this->description, 10),
];
}

Maintenant pour l’url …/api/films/2 on obtient :

10/11
On voit qu’on n’a plus que les 10 premiers mots de la description. On a bien effectué un
traitement intermédiaire sur les valeurs retournées. Mais on remarque aussi qu’on ne
récupère plus les catégories et les acteurs ! On a juste ce qu’on a demandé. Mais
personne ne nous empêche d’en demander plus :

public function toArray($request)


{
return [
'title' => $this->title,
'year' => $this->year,
'description' => Str::words($this->description, 10),
'categories' => $this->categories,
'actors' => $this->actors,
];
}

Et maintenant on récupère bien les éléments en relation.

Il y aurait encore beaucoup à dire sur les ressources et les API, mais vous avez
désormais une bonne base de départ.

En résumé
Laravel permet de créer facilement une API et de permettre des transformations
avec des ressources.

11/11
Cours Laravel 9 – la sécurité – l’authentification
laravel.sillo.org/cours-laravel-9-la-securite-lauthentification/

19 février 2022

L’authentification constitue une tâche fréquente. En effet il y a souvent des parties d’un
site qui ne doivent être accessibles qu’à certains utilisateurs, ne serait-ce que
l’administration. La solution proposée par Laravel est d’une grande simplicité parce que
tout est déjà préparé comme nous allons le voir dans ce chapitre.

Au fil de l’avolution de Laravel ce domaine a connu plusieurs fois des changements. Ainsi
avec Laravel 6 est apparue une nouveauté. Précédemment il y avait la commande php
artisan make:auth pour générer les vues. Cette commande a disparu au profit d’un
package à installer (laravel/ui). Mais les changements ont continué parce que désormais
on nous dit d’utiliser un starter kit avec comme choix Breeze ou Jetstream.

En résumé pour l’authentification on a ces possibilités :

Utiliser laravel/breeze qui utilise Tailwind, c’est le sujet du présent article


Utiliser Jetstream, mais à ce niveau on a deux possibilités :
Livewire
Inertia (ça concerne surtout ceux qui veulent s’orienter sur une SPA)
Utiliser Fortify en créant soi-même les vues et accessoirement le traitement de
fonctionnalités complémentaires

Je sais que le débutant peut trouver cette situation un peu confuse mais c’est comme ça !
L’option la plus simple est certainement d’installer laravel/breeze. Le code est simple et
lisible et facilement accessible et modifiable.

Quelques principes
Comment fonctionne une authentification ? En général l’utilisateur utilise un navigateur,
renseigne son nom et son mot de passe dans un formulaire, ces informations sont reçues
par le serveur, vérifiées, et si tout est bon on mémorise cette authentification en session.
Cette session possède un identifiant qui est transmis à chaque requête pour éviter d’avoir
à s’authentifier à chaque fois. Lorsqu’une requête HTTP arrive avec un identifiant de
session l’application considère l’utilisateur correspondant comme authentifié. Ce principe
ne s’applique évidemment plus dans le cas d’une API parce qu’on ne dispose pas de
session et aucune persistance et on utilise alors un token pour chaque requête.

Laravel est équipé de tout ce qui est nécessaire pour cette authentification au travers des
façades Auth et Session. La gestion du cookie est automatisée. D’autre part on dispose
de nombreuses méthodes pratiques, par exemple pour retrouver facilement les
renseignements sur l’utilisateur authentifié.

1/12
On peut très bien utiliser cette infrastructure pour construire sois-même un système
complet d’authentification mais c’est assez laborieux. Il est beaucoup plus simple
d’utiliser les starter kits.

Laravel Fortify gère les outils d’authentification de Laravel sans se soucier de la partie
frontend. Autrement dit lorsqu’on utilise Fortify on n’a aucune vue. Si on veut quelque
chose de complet il faut aussi utiliser Jetstream qui est un ensemble de vues qui utilisent
l’intendance de Fortify. Mais dans ce cas on se voit imposer un certain nombre de
technologies : Tailwind CSS, Laravel Livewire, et/ou Inertia.js. Alors vous avez le choix,
soit vous passez par ces technologie modernes et un peu dépaysantes, soit vous vous
contentez de Fortify et vous constituez vos vues avec la librairie CSS que vous aimez,
soit vous utilisez Breeze.

Dans cet article je vais présenter l’authentification en utilisant Breeze.

La base de données
Par défaut la partie persistance de l’authentification (c’est à dire la manière de retrouver
les renseignements des utilisateurs) avec Laravel se fait en base de données avec
Eloquent et part du principe qu’il y a un modèle App\Models\User (dans le dossier app).

Lors de l’installation on a vu dans les chapitres précédents qu’il existe déjà des
migrations :

Repartez d’une installation vierge et faites la migration avec Artisan :

Vous devriez normalement obtenir ces tables :

Je rappelle que la table migrations sert seulement d’intendance pour les migrations et
que vous ne devez pas y toucher.

table users : par défaut Laravel considère que cette table existe et il s’en sert
comme référence pour l’authentification.

2/12
table password_resets : cette table va nous servir pour
la réinitialisation des mots de passe.

Les middlewares
Je vous ai déjà parlé des middlewares qui servent à effectuer un traitement à l’arrivée (ou
au départ) des requêtes HTTP. On a vu le middleware de groupe web qui intervient pour
toutes les requêtes qui arrivent. Dans ce chapitre on va voir deux autres middlewares qui
vont nous permettre de savoir si un utilisateur est authentifié ou pas pour agir en
conséquence.

Middleware auth

Le middleware auth permet de n’autoriser l’accès qu’aux utilisateurs authentifiés. Ce


middleware est déjà présent et déclaré dans app\Http\Kernel.php :

protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
...
];

On voit que la classe se trouve dans l’application :

On peut utiliser ce middleware directement sur une route :

Route::get('comptes', function() {
// Réservé aux utilisateurs authentifiés
})->middleware('auth');

Ou un groupe de routes :

Route::middleware('auth')->group(function () {
Route::get('/', function () {
// Réservé aux utilisateurs authentifiés
});
Route::get('comptes', function () {
// Réservé aux utilisateurs authentifiés
});
});

Le groupement de routes avec la méthode group permet de mutualiser des actions et de


simplifier le code.

Ou dans le constructeur d’un contrôleur :

3/12
public function __construct()
{
$this->middleware('auth');
}

Dans ce cas on peut désigner les méthodes concernées avec only ou non concernées
avec except :

public function __construct()


{
$this->middleware('auth')->only(['create', 'update']);
}

Middleware guest
Ce middleware est exactement l’inverse du précédent : il permet de n’autoriser l’accès
qu’aux utilisateurs non authentifiés. Ce middleware est aussi déjà présent et déclaré dans
app\Http\Kernel.php :

protected $routeMiddleware = [
...
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
...
];

La classe se trouve aussi dans l’application :

De la même manière que auth, le middleware


guest, comme d’ailleurs tous les middlewares,
peut s’utiliser sur une route, un groupe de
routes ou dans le constructeur d’un contrôleur,
avec la même syntaxe.

Les routes de l’authentification


Dans l’installation de base vous ne trouvez aucune route pour l’authentification. Pour les
créer (et ça ne créera pas seulement les routes) il faut déjà installer un package :

composer require laravel/breeze --dev

Une fois que le package est installé vous pouvez lancer cette commande :

php artisan breeze:install

C’est avec cette commande que vont être créés les routes, vues, controlleurs…

Il ne reste plus qu’à compiler les assets pour générer le CSS :

npm install
npm run dev

4/12
Regardez ce qui a été ajouté dans le fichier routes/web.php :

Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth'])->name('dashboard');

require __DIR__.'/auth.php';

On a un fichier routes/auth.php bien garni.

Voyons quelles sont les routes générées :

Nous allons voir bientôt tout ça en action.

Les vues de l’authentification


Il y a également eu la génération de nombreuses vues :

On va aussi voir à quoi servent toutes ces vues…

Les contrôleurs de l’authentification

5/12
On a aussi la création de nombreux contrôleurs :

Nous allons aussi voir à quoi ils servent…

L’enregistrement d’un utilisateur


L’enregistrement d’un utilisateur passe par l’url …/register. Et cette route :

On voit qu’on utilise le contrôleur RegisteredUserController avec sa méthode create qui


se contente de renvoyer une vue :

public function create()


{
return view('auth.register');
}

On va donc chercher cette vue :

Elle a cet aspect :

6/12
Cette vue utilise entre autres le composant resources/views/layouts/guest.blade.php.
Je n’ai pas encore parlé des composants de Blade dans ce cours, c’est une autre
manière d’organiser le code des vues, la documentation est ici. Breeze fait un usage
intensif de ces composants. Je ne vais pas entrer dans le détail du code.

La validation

La validation pour l’enregistrement est présente dans le contrôleur :

public function store(Request $request)


{
$request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'confirmed', Rules\Password::defaults()],
]);

De cette manière elle est facile à modifier si vous devez changer des règles ou ajouter un
champ.

On peut vérifier que ça fonctionne :

7/12
Je rappelle qu’on peut ajouter la langue française avec ce complément.

La création de l’utilisateur

La création de l’utilisateur se fait aussi dans le contrôleur :

public function store(Request $request)


{
...

$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);

...
}

On utilise la méthode create d’Eloquent comme on l’a déjà vu au chapitre précédent.


Vous trouvez dans le modèle App\Models\User.php la propriété $fillable renseignée :

protected $fillable = [
'name', 'email', 'password',
];

Donc là encore il est facile d’apporter des modifications si nécessaire.

Une fois que l’utilisateur est créé dans la base il est automatiquement connecté et est
redirigé sur le dashboard :

8/12
La connexion d’un utilisateur
Pour se connecter,il faut utiliser l’url …/login. Voici la route concernée :

Si vous venez juste d’enregistrer un utilisateur il faudra le déconnecter avant d’avoir


accès au formulaire de login.

On voit qu’on utilise le contrôleur AuthenticatedSessionController avec sa méthode


create qui se contente de renvoyer une vue :

public function create()


{
return view('auth.login');
}

On va donc chercher cette vue :

Elle a cet aspect :

Cette vue utilise aussi le composant resources/views/layouts/guest.blade.php.

Dans le formulaire de connexion il y a une case à cocher « se rappeler de moi »


(remember me) :

Si on coche cette case on reste connecté indéfiniment jusqu’à ce


qu’on se déconnecte intentionnellement. Pour que ça fonctionne il
faut une colonne remember_token dans la table users :

9/12
Il se trouve que cette colonne est prévue dans la migration de base pour la table.

La déconnexion d’un utilisateur

L’utilisateur dispose d’un lien pour se déconnecter :

Si on clique sur Logout on utilise l’url …/logout et


cette route ::

Remarquez que la méthode est POST. Du coup la vue (layouts.navigation) utilise un


formulaire et une soumission en Javascript :

<x-slot name="content">
<!-- Authentication -->
<form method="POST" action="{{ route('logout') }}">
@csrf

<x-dropdown-link :href="route('logout')"
onclick="event.preventDefault();
this.closest('form').submit();">
{{ __('Log Out') }}
</x-dropdown-link>
</form>
</x-slot>

C’est la méthode destroy du contrôleur AuthenticatedSessionController qui accomplit


la déconnexion :

public function destroy(Request $request)


{
Auth::guard('web')->logout();

$request->session()->invalidate();

$request->session()->regenerateToken();

return redirect('/');
}

La vérification de l’email
Si vous voulez que l’utilisateur confirme son email, et ainsi être sûr que ce n’est pas un
fake il faut que le modèle User implémente l’interface MustVerifyEmail :

10/12
<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable implements MustVerifyEmail


{
use Notifiable;

// ...
}

Et il reçoit cet email lorqu’il s’enregistre :

Lorsqu’il utilise le lien de validation l’email est bien vérifié et il est dirigé sur la page de
connexion.

Il ne reste plus qu’à utiliser le middleware verified sur les routes qu’on veut protéger et
qui ne seront donc pas accessible avant la vérification de l’email. Par exemple pour le
dashboard :

Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');

Il reçoit alors ce message :

11/12
Changer la langue
On a déjà vu qu’on peut ajouter le français à Laravel en utilisant Laravel-Lang. Dans le
fichier fr.json figurent aussi les traductions pour l’authentification.

En résumé
L’authentification est totalement et simplement prise en charge par Laravel.
Les migrations pour l’authentification sont déjà dans l’installation de base de
Laravel.
L’ajout du package Breeze permet de générer les routes et les vues pour
l’authentification.
Les middlewares auth et guest permettent d’interdire les accès aux routes qui
concernent uniquement les utilisateurs authentifiés ou non authentifiés.

12/12
Cours Laravel 9 – la sécurité – l’oubli du mot de passe
laravel.sillo.org/cours-laravel-9-la-securite-loubli-du-mot-de-passe/

20 février 2022

Un mot de passe est plus facile à oublier qu’à retenir, d’autant qu’on en est littéralement
submergé ! Toute application avec une authentification doit prévoir ce genre de situation
et donner la possibilité aux utilisateurs de se sortir de ce mauvais pas. Le plus simple est
de leur permettre de créer un nouveau mot de passe. Laravel est équipé pour cela et
nous allons le voir dans ce chapitre.

Le présent chapitre est une suite du précédent et il faut avoir mis en place ce qui
concerne l’authentification avec l’installation de Breeze.

La base de données
Avec les migrations réalisées dans le chapitre précédent il y a la table password_resets
dans notre base :

On voit qu’on va mémoriser ici l’adresse email, le jeton


(token) et le timestamp (par défaut les jetons sont valables
pendant une heure). On trouve ces réglages dans le fichier
config/auth.php :

'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 60,
'throttle' => 60,
],
],

Les routes et les contrôleurs


On a 4 routes pour le renouvellement du mot de passe générées par Breeze :

Tout est géré par ces deux contrôleurs générés par Breeze :

1/6
Première étape : la demande
Pour l’utilisateur la première étape va consister à cliquer sur le lien « Forgot You
Password? » prévu sur la vue de connexion :

Ce lien correspond à cette route :

On appelle la méthode create du contrôleur PasswordResetLinkController.

Pour le frontend Breeze a créé la vue auth.forgot-password.blade à l’installation du


package :

Avec un formulaire qui a cet aspect :

2/6
On demande l’adresse email à l’utilisateur. La soumission correspond à cette route :

On appelle la méthode store du contrôleur PasswordResetLinkController.

On peut vérifier que ça fonctionne :

Si la validation passe avec succès, on envoie un email à l’utilisateur. Voici le message


reçu :

3/6
L’envoi de l’email se fait avec un système de notification dont je vous parlerai dans la
quatrième partie de ce cours.

Et si tout s’est bien passé on renvoie le formulaire avec un message :

Dans la table un jeton (token) a été généré :

Il sera ainsi possible de vérifier la provenance de la demande.

Deuxième étape : le renouvellement


Quand on appuie sur le bouton du message ou si on clique sur le lien de secours, on
utilise cette route :

On appelle la méthode create du contrôleur.

Pour le frontend on a la vue auth.reset-password.blade à l’installation du package :

Voici l’aspect du formulaire :

4/6
Dans ce formulaire, on retrouve le jeton (token) dans un contrôle caché :

<input type="hidden" name="token"


value="wfeL46gpege1GDk1fe8tlXXxOaF878QN09vOrqBX">

La soumission de ce formulaire utilise cette route :

On appelle la méthode store du contrôleur NewPasswordController.

On commence par une validation :

$request->validate([
'token' => ['required'],
'email' => ['required', 'email'],
'password' => ['required', 'confirmed', Rules\Password::defaults()],
]);

Ensuite on procède au changement du mot de passe. Enfin si tout va bien on renvoie sur
la page de connexion avec un message :

5/6
En résumé
Un système complet de renouvellement du mot de passe est prévu. Avec ses
routes, ses contrôleurs et ses vues, en ajoutant le package Breeze.
Le renouvellement se fait en deux étapes : une demande qui reçoit une réponse
sous forme d’email sécurisé avec un jeton (token) puis une soumission du nouveau
mot de passe avec un formulaire.

6/6
Cours Laravel 9 – la sécurité – Jetstream
laravel.sillo.org/cours-laravel-9-la-securite-jetstream/

21 février 2022

On a vu précédemment l’utilisation de Breeze pour installer un système complet


d’authentification. Il existe un deuxième starter kit dans Laravel, il s’agit de Jetstream.
Celui-ci reprend toutes les fonctionnalités de Breeze et en ajoute d’autres :
authentification à deux facteurs, gestion des sessions, et possibilité de gérer des équipes.
Nous allons voir dans cet article comment l’installer et l’utiliser.

Les routes de l’authentification


Dans l’installation de base vous ne trouvez aucune route pour l’authentification. Pour les
créer (et ça ne créera pas seulement les routes) il faut déjà installer Jetstream :

composer require laravel/jetstream

Ensuite on a le choix entre Livewire (avec Blade) et Inertia (avec Vue.js) selon les goûts
ou habitudes. Pour ce cours je vais utiliser Livewire qui a un grand succès dans la
communauté.

php artisan jetstream:install livewire


npm install
npm run dev
php artisan migrate

Ça prend un peu de temps parce qu’il y a pas mal de chose à faire. Je parlerai de cet
aspect dans un article ultérieur.

Pendant ce temps regardez ce qui a été ajouté dans le fichier routes/web.php :

Route::middleware(['auth:sanctum', 'verified'])->get('/dashboard', function () {


return view('dashboard');
})->name('dashboard');

Voyons quelles sont les routes générées (vous devez maintenant bien connaître la
commande correspondante d’Artisan :

1/12
Vous voyez qu’on a produit pas mal de routes mais elles ne servent pas toutes pour
l’authentification !

Une autre chose intéressante aussi est la création d’un dossier pour les actions de Fortify
et Jetstream :

En ce qui concerne la base de données vous


pouvez avoir un aperçu précis dans DrawSQL.

Nous allons voir bientôt tout ça en action.

Les vues de l’authentification


Il y a également eu la génération de nombreuses vues :

On va aussi voir à quoi servent toutes ces vues. Elles ne sont pas toutes utilisées par
l’authentification.

2/12
L’enregistrement d’un utilisateur
Commençons par le commencement avec l’enregistrement d’un utilisateur. Si vous allez
sur la page d’accueil vous allez remarquer la présence en haut à droite de la page de
deux liens :

On trouve ce code dans la page d’accueil :

3/12
@if (Route::has('login'))
<div class="hidden fixed top-0 right-0 px-6 py-4 sm:block">
@auth
<a href="{{ url('/dashboard') }}" class="text-sm text-gray-700
dark:text-gray-500 underline">Dashboard</a>
@else
<a href="{{ route('login') }}" class="text-sm text-gray-700 dark:text-
gray-500 underline">Log in</a>

@if (Route::has('register'))
<a href="{{ route('register') }}" class="ml-4 text-sm text-gray-
700 dark:text-gray-500 underline">Register</a>
@endif
@endauth
</div>
@endif

On en profite pour voir les opérateurs conditionnels de Blade.

Avec un @if on vérifie si dans les routes (Route) on a (has) une route login et si c’est le
cas on vérifie si l’utilisateur est authentifié avec @auth, si c’est le cas on affiche un lien
vers le dashboard et si ce n’est pas le cas (@else) on affiche les deux liens de
l’authentification.

Si on clique sur Register on appelle une méthode de Fortify :

Remarquez au passage la déclaration du middleware guest dans les routes de Fortify (il
faut aller voir ça dans le dossier vendor) :

Route::get('/register', [RegisteredUserController::class, 'create'])


->middleware(['guest:'.config('fortify.guard')])
->name('register');

En effet si on est authentifié c’est qu’on n’a pas besoin de s’enregistrer !

La vue register
On va donc chercher cette vue :

Elle a cet aspect :

4/12
Cette vue utilise le composant resources/views/layouts/guest.blade.php. Je n’ai pas
encore parlé des composants de Blade dans ce cours, c’est une autre manière
d’organiser le code des vues, la documentation est ici. Jetstream fait un usage intensif de
ces composants. Je ne vais pas entrer dans le détail du code.

La validation
La validation pour l’enregistrement est présente dans l’action
app/Actions/Fortify/CreateNewUser :

public function create(array $input)


{
Validator::make($input, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => $this->passwordRules(),
'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature() ? ['accepted',
'required'] : '',
])->validate();

...
}

De cette manière elle est facile à modifier si vous devez changer des règles ou ajouter un
champ.

On peut vérifier que ça fonctionne :

5/12
Je rappelle qu’on peut ajouter la langue française avec ce complément.

La création de l’utilisateur
La création de l’utilisateur se fait aussi dans app/Actions/Fortify/CreateNewUser :

public function create(array $input)


{
...

return User::create([
'name' => $input['name'],
'email' => $input['email'],
'password' => Hash::make($input['password']),
]);
}

On utilise la méthode create d’Eloquent comme on l’a déjà vu au chapitre précédent.


Vous trouvez dans le modèle App\Models\User.php la propriété $fillable renseignée :

protected $fillable = [
'name', 'email', 'password',
];

Donc là encore il est facile d’apporter des modifications si nécessaire.

Une fois que l’utilisateur est créé dans la base il est automatiquement connecté et est
redirigé sur le dashboard :

6/12
La connexion d’un utilisateur
Pour se connecter, un utilisateur doit cliquer sur le lien login de la page d’accueil :

Si vous venez juste d’enregistrer un utilisateur il faudra le


déconnecter avant d’avoir accès au formulaire de login.

Si on clique sur Login on appelle une méthode de Fortify :

Remarquez aussi la déclaration du middleware guest dans les routes de Fortify (il faut
aller voir ça dans le dossier vendor) :

Route::post('/login', [AuthenticatedSessionController::class, 'store'])


->middleware(array_filter([
'guest:'.config('fortify.guard'),
$limiter ? 'throttle:'.$limiter : null,
]));

La vue login
On va donc chercher cette vue :

Elle a cet aspect :

7/12
Cette vue utilise aussi le composant resources/views/layouts/guest.blade.php.

La validation

La validation pour la connexion est présente dans la requête de formulaire


fortify\src\Http\Requests\LoginRequest :

public function rules()


{
return [
Fortify::username() => 'required|string',
'password' => 'required|string',
];
}

Dans le formulaire de connexion il y a une case à cocher « se rappeler de moi »


(remember me) :

Si on coche cette case on reste connecté indéfiniment jusqu’à ce


qu’on se déconnecte intentionnellement. Pour que ça fonctionne il
faut une colonne remember_token dans la table users :

Il se trouve que cette colonne est prévue dans la migration de base pour la table.

8/12
La redirection

Lorsqu’on installe Fortify on a la création d’un fichier de configuration :

On y trouve un configuration de la redirection après la connexion


:

'home' => RouteServiceProvider::HOME,

Dans le provider on a le chemin de redirection :

public const HOME = '/dashboard';

Par défaut on redirige sur le dashboard. Donc pour changer la


redirection par défaut il suffit de créer cette propriété.

En fait ce que j’ai écrit ci-dessus est incomplet, il faut préciser


quelque chose. On peut arriver sur le formulaire de connexion parce qu’on clique sur le
lien correspondant, mais on peut aussi y arriver par l’action du middleware auth. En effet
si on veut atteindre une page pour laquelle il faut être authentifié le middleware auth va
nous bloquer et nous rediriger sur le formulaire de connexion. Dans ce cas ce qui serait
bien serait, à l’issue de la connexion, d’aller directement sur la page qu’on désirait
atteindre au départ.Sans entrer dans les détails la route désirée est mémorisée dans la
session.

La déconnexion d’un utilisateur

L’utilisateur dispose d’un lien pour se déconnecter :

Si on clique sur Logout on appelle une méthode de


Fortify :

Remarquez que la méthode est POST. Du coup la vue (navigation-menu) utilise un


formulaire et une soumission en Javascript :

9/12
<!-- Authentication -->
<form method="POST" action="{{ route('logout') }}">
@csrf

<x-jet-dropdown-link href="{{ route('logout') }}"


onclick="event.preventDefault();
this.closest('form').submit();">
{{ __('Log Out') }}
</x-jet-dropdown-link>
</form>

C’est la méthode destroy du contrôleur AuthenticatedSessionController de Fortify qui


accomplit la déconnexion :

public function destroy(Request $request): LogoutResponse


{
$this->guard->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return app(LogoutResponse::class);
}

Si on veut changer des choses


Tout ce qu’on a vu fonctionne très bien mais évidemment il arrive souvent qu’on veuille
changer des choses, faisons un petit point…

Changer l’email de connexion

Par défaut on attend un email pour se connecter, mais comment faire si on préfre utiliser
par exemple le nom ? Il suffit d’aller dans le fichier de configuration de Fortify
(config/fortify.php) et de modifier cette ligne de code :

'username' => 'email',

Le règles du mot de passe

Par défaut le mot de passe doit avoir au minimum 8 caractères et c’est tout ce qu’on
exige de lui. Si on a envie de rendre les mots de passe plus sûr on peut ajouter d’autres
contraintes. Ouvrez ce fichier d’action :

Vous y trouvez les règles de validation du mot de


passe :

protected function passwordRules()


{
return ['required', 'string', new Password,
'confirmed'];
}

C’est sur l’objet Password qu’on peut intervenir :

10/12
(new Password)->length(10)

// Au moins une majuscule


(new Password)->requireUppercase()

// Au moins un chiffre
(new Password)->requireNumeric()

// Au moins un caractère spécial


(new Password)->requireSpecialCharacter()

La vérification de l’email

Par défaut Jetstream n’impose pas la vérification de l’email mais on peut très facilement
activer cette fonctionnalité. Regardez dans le fichier de configuration de Fortify
(config/fortify.php) :

'features' => [
Features::registration(),
Features::resetPasswords(),
// Features::emailVerification(),
Features::updateProfileInformation(),
Features::updatePasswords(),
Features::twoFactorAuthentication([
'confirmPassword' => true,
]),
],

On trouve toutes les fonctionnalités de Jetstream. Pour le moment on n’a vu que la


première, les utilisateurs peuvent s’enregistrer, se connecter et se déconnecter. Mais on
voit qu’il y a d’autres possibilités, en particulier la vérification de l’email qui est par défaut
commentée. Il suffit de la décommenter pour que ça fonctionne. Mais il faut aussi que le
modèle User implémente l’interface MustVerifyEmail :

class User extends Authenticatable implements MustVerifyEmail

Maintenant lorsqu’un utilisateur s’enregistre il a ce message :

Et il reçoit cet email :

11/12
Lorqu’il utilise le lien d’activation il est automatiquement connecté.

Changer la langue

On a déjà vu qu’on peut ajouter le français à Laravel en utilisant Laravel-Lang. Je parlerai


plus loin dans ce cours de la localisation et de la manière d’intervenir dans les vues.

En résumé
L’authentification est totalement et simplement prise en charge par Laravel.
Les migrations pour l’authentification sont déjà dans l’installation de base de
Laravel.
L’ajout du package Jetstream permet de générer les routes et les vues pour
l’authentification.
L’installation de Jetstream entraine l’installation de Fortify qui gère le backend de
l’authentification.

12/12
Cours Laravel 9 – la sécurité – gestion du profil et API
laravel.sillo.org/cours-laravel-9-la-securite-gestion-du-profil-et-api/

21 février 2022

Lorsqu’on installe Jetstream on n’obtient pas seulement une authentification complète


avec enregistrement, connexion et déconnexion, vérification de l’email, oubli du mot de
passe, comme on l’a vu dans les deux articles précédents. On obtient beaucoup plus
comme on va le voir dans cet article. Déjà une gestion du profil de l’utilisateur qui peut
ainsi modifier ses données personnelles, mais également la possibilité de passer par une
authentification à deux facteurs qui devient de plus en plus à la mode. L’utilisateur peut
en plus supprimer sa session ou carrément son compte. On verra par ailleurs la
possibilité de demander la confirmation du mot de passe pour l’accès à des pages
sensibles. Pour terminer j’évoquerai aussi les API.

Le dashboard
On a vu que lorsqu’un utilisateur est connecté il arrive dans le dashbord (pour mémoire,
on verra plus tard dans ce cours l’aspect localisation pour changer toutes ces
appellations dans notre langue préférée). Par défaut sur la page, on trouve des
renseignements et des liens de Laravel et dans la partie supérieure cet aspect :

On a un menu déroulant à droite en cliquant sur l’image (par défaut, on a les deux
premiers caractères du nom, mais on va pouvoir mettre une vraie image) :

Là on peut procéder à 2 actions :

accéder au profil
se déconnecter

Le profil
Quand on accède au profil, c’est assez fourni.

Les informations personnelles

1/8
Là on peut modifier le nom et l’email. Pour pouvoir également intervenir au niveau de la
photo, il faut activer l’option dans le fichier config.jetstream.php :

'features' => [
// Features::termsAndPrivacyPolicy(),
Features::profilePhotos(),
// Features::api(),
// Features::teams(['invitations' => true]),
Features::accountDeletion(),
],

Maintenant on y a accès :

La photo

2/8
Pour la gestion de la photo Jetstream ajoute à son installation le trait
Laravel\Jetstream\HasProfilePhoto dans le modèle User :

use Laravel\Jetstream\HasProfilePhoto;

...

class User extends Authenticatable


{
...
use HasProfilePhoto;

Si on regarde ce trait on y trouve une intendance complète pour gérer la photo. Comme il
s’agit d’un trait, on peut surcharger les méthodes dans le modèle pour modifier ou ajouter
des fonctionnalités.

Quand on charge une photo elle est placée provisoirement ici :

À la sauvegarde elle est placée là :

J’ai déjà parlé dans cet article de la gestion des fichiers de Laravel. Jetstream utilise le
disque public pour la gestion des photos (sauf si on utilise Vapor, dans ce cas on va sur
S3). Si on crée un lien symbolique tel que c’est préconisé dans la documentation on va
dans le dossier storage.

Le mot de passe
On peut aussi modifier le mot de passe à partir du profil :

3/8
L’authentification à deux facteurs
Une autre possibilité intéressante est d’activer l’authentification à deux facteurs (A2F).
Pour mémoire avec ce type d’authentification, on ne se contente plus du login et du mot
de passe, mais il faut également une information de sécurité transmise par un autre
moyen (application, email, sms…). Ce type de sécurité devient de plus en plus répandue
et on parle même maintenant de triple facteur ! Voyons comment Jetstream met cela en
place…

Dans le profil, on peut activer cette sécurité :

Toujours par sécurité le mot de passe est réclamé :

4/8
On obtient cet affichage :

On obtient un QR code pour Google Authenticator (ou autre). On a aussi des codes de
récupération à enregistrer en cas de perte de la double authentification.

Abandon de session

5/8
Dans le profil, on a aussi la possibilité de purger la session :

Suppression de compte
Enfin on peut dans le profil supprimer son compte :

Les API
Jetstream est équipé pour l’utilisation de Laravel Sanctum, qui est un package pour
Laravel qui permet de mettre en place simplement des API à partir de l’utilisation de
tokens. Un utilisateur peut générer des tokens qui permettent un accès à une API avec
des permissions bien définies. Sanctum est installé en même temps que Jetstream et est
donc directement disponible.

Vous pouvez activer l’option dans le fichier config.jetstream.php :

'features' => [
// Features::termsAndPrivacyPolicy(),
Features::profilePhotos(),
Features::api(),
// Features::teams(['invitations' => true]),
Features::accountDeletion(),
],

6/8
La commande apparaît alors dans le menu :

Jetstream propose une interface simple pour la


génération des tokens et de leurs permissions :

Quand on crée un token une fenêtre s’ouvre et il faut copier le token qui n’apparaitra que
là :

On a la liste des tokens disponibles :

7/8
On peut supprimer un token ou changer ses permissions :

Il y a un trait installé par Jetstream dans le modèle User pour le fonctionnement de ces
tokens :

use Laravel\Sanctum\HasApiTokens;

...

class User extends Authenticatable


{
use HasApiTokens;

Dans le code, on peut vérifier ce token :

$request->user()->tokenCan('update');

Conclusion
On a vu dans cet article que Jetstream permet une gestion du profil de l’utilisateur :

informations personnelles
photo
mot de passe
authentification à deux facteurs
abandon de session
suppression de compte

D’autre part par l’intermédiaire de Sanctum on peut gérer des tokens d’API et leurs
permissions.

8/8
Cours Laravel 9 – la sécurité – les équipes
laravel.sillo.org/cours-laravel-9-la-securite-les-equipes/

21 février 2022

Dans cet article, on va finir l’exploration des possibilités de Jetstream. On a déjà vu


précédemment qu’on dispose avec ce package de tout l’arsenal de l’authentification
(connexion, déconnexion, oubli du mot de passe, vérification de l’email, vérification du
mot de passe…). On a vu également que l’utilisateur dispose d’une gestion complète de
son profil (informations personnelles, changement du mot de passe, authentification à
double facteur, purge de session…). On va voir dans cet article que Jetstream offre aussi
la possibilité de gérer des équipes (teams) avec des permissions.

Installation
Pour l’installation on retrouve exactement ce qu’on a vu. En partant d’un Laravel 8 tout
neuf on installe Jetstream :

composer require laravel/jetstream

Mais ensuite dans la mise en route de jetstream, on ajoute une option :

php artisan jetstream:install livewire --teams

Et enfin on génère les assets et les tables de la base de données :

npm install
npm run dev
php artisan migrate

On voit apparaître trois tables nouvelles :

Ces tables correspondent à la gestion des équipes (teams).


On a la table pour enregistrer les équipes (teams) et la table
pivot pour la relation n:n avec les utilisateurs (team_user).
D’autre part on a la table team_invitations pour les
invitations.

Quand un utilisateur s’enregistre on va trouver un nouveau


menu dans son dashboard :

On trouve des liens pour la gestion des équipes. Par défaut un


utilisateur dispose au départ d’une équipe personnelle, ici
Durand’s Team. On la retrouve dans la table teams :

1/8
L’utilisateur (avec l’item Team Settings du menu) peut gérer complètement les équipes :

2/8
On voit ici qu’il peut modifier le nom de son équipe personnelle et qu’il peut gérer les
membres.

Gérer les équipes

3/8
On peut créer une nouvelle équipe avec l’item Create New Team du menu, on arrive
alors sur ce formulaire :

Là on demande juste un nom. Lorsque la nouvelle équipe est créée on la retrouve dans
le menu :

Là avec un clic, on peut passer d’une équipe à une autre :

C’est l’équipe active à un moment donné.

On peut aussi supprimer une équipe :

4/8
Gestion des membres
On peut ajouter un membre à une équipe à partir de ce formulaire :

On renseigne l’email qui doit évidemment correspondre à celui d’un utilisateur déjà
enregistré dans la base. On spécifie aussi son rôle, on voit qu’on a le choix entre deux
rôles.

Une fois que le membre est effectivement ajouté il apparaît dans la liste des membres :

On peut modifier le rôle en utilisant le lien, ici Editor :

5/8
Si l’email n’existe pas dans la base, on se contente d’envoyer une invitation :

On envoie automatiquement un email d’invitation :

Là on peut créer un compte. On est alors ajouté dans l’équipe :

6/8
Les rôles et permissions
On a vu ci-dessus qu’on dispose de deux rôles (Administrator et Editor), mais comment
en ajouter ou les modifier ? De plus quelles sont les permissions associées et comment
changer ça ? On ne trouve rien au niveau de l’interface alors il faut aller piocher dans le
code…

On trouve tout ça dans le provider JetstreamServiceProvider :

protected function configurePermissions()


{
Jetstream::defaultApiTokenPermissions(['read']);

Jetstream::role('admin', 'Administrator', [
'create',
'read',
'update',
'delete',
])->description('Administrator users can perform any action.');

Jetstream::role('editor', 'Editor', [
'read',
'create',
'update',
])->description('Editor users have the ability to read, create, and update.');
}

On peut donc facilement jouer sur les rôles et permissions :

Jetstream::role('writer', 'Author', [
'create',
'update',
])->description('Author users have the ability to create, and update.');

Les autorisations et l’équipe active


C’est bien de prévoir des permissions, mais encore faut-il pouvoir les utiliser…

Jetstream ajoute un trait dans le modèle User :

use Laravel\Jetstream\HasTeams;

...

class User extends Authenticatable


{
use HasTeams;

7/8
Dans ce trait on trouve la méthode
hasTeamPermission qui va permettre de
vérifier ces permissions sur les requêtes
entrantes :

if ($request->user()-
>hasTeamPermission($team, 'update')) {
// Ici on fait quelque chose si
l'utilisateur a le droit de le faire
}

Je n’ai pas encore parlé des autorisations


dans ce cours, pour ceux qui sont pressés la
documentation est ici.

Mais le trait ajouté ne se limite bien sûr pas aux permissions. On peut aussi connaître
l’équipe active de l’utilisateur :

$user->currentTeam

On peut obtenir des tas d’autres renseignements dont vous avez le détail dans la
documentation.

Conclusion
Jetstream offre une gestion des rôles et permissions au travers de la notion d’équipes
(teams), ce qu’on appelle en général groupes dans d’autres librairies. On peut donc se
passer de package supplémentaire pour accomplir des actions de gestion des rôles et
permissions, ce que l’on faisait généralement avant Laravel 8.

8/8
Cours Laravel 9 – la sécurité – on se protège
laravel.sillo.org/cours-laravel-9-la-securite-on-se-protege/

22 février 2022

Lorsqu’on développe une application on prend plein de précautions, par exemple les
utilisateurs doivent s’authentifier pour éviter des actions non autorisées. Dans le code on
peut vérifier si la personne est authentifiée et quel est son degré d’habilitation. On a vu
que Jetstream introduit une gestion complète des équipes avec des rôles et des
permissions.

En dehors de l’authentification on doit gérer certaines situations comme par exemple


savoir si un utilisateur authentifié a le droit de modifier une ressource particulière. Laravel
nous offre un système d’autorisations bien pratique.

L’authentification

Les middlewares
On a déjà vu l’authentification en détail dans un précédent chapitre. On peut aussi gérer
les utilisateurs authentifiés selon un rôle ou leur degré d’habilitation sans nécessairement
utiliser Jetstream. On peut filtrer les accès selon un statut avec des middlewares. Par
exemple on peut créer un middleware Admin :

Avec ce code :

1/8
<?php

namespace App\Http\Middleware;

use Closure;

class Admin
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$user = $request->user();

if ($user && $user->role === 'admin') {


return $next($request);
}

return redirect()->route('home');
}
}

Dans la méthode handle on récupère l’utilisateur en cours ($request->user()) et on teste


s’il s’agit d’un administrateur en imaginant que dans la table users on a créé une colonne
role ($user->role === ‘admin’).

On peut ainsi filtrer les routes réservées aux administrateurs :

Route::middleware('admin')->group(function () {
...
}

Le throttle

Jusqu’à la version 7 Laravel intégrait automatiquement dans l’authentification une


sécurisation de la connexion contre les attaques « brute force ». On disposait d’un trait
Illuminate\Foundation\Auth\AuthenticatesUsers qu’il suffisait d’inclure dans le
contrôleur de connexion. Désormais il faut utiliser Breeze ou Jetstream pour en
bénéficier. Au niveau du fonctionnement l’utilisateur est bloqué pendant une minute au
bout d’un certain nombre d’essais infructueux.

Rate limiter
Laravel 9 propose un limiteur d’accès qu’on peut appliquer à une route ou un groupe de
routes. Comme il s’agit de route on utilise le provider RouteServiceProvider. Il y a déjà
ce code par défaut :

2/8
protected function configureRateLimiting()
{
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
}

Ici on a un limiteur qui va fixer le maximum à 60 accès par minutes pour les API. Au-delà
on obtient une erreur 429 ou une réponse personnalisée. Il est possible d’affiner selon
l’utilisateur ou le type, je vous renvoie à la documentation pour ces précisions.

Pour l’application du limiteur on utilise un middleware :

Route::middleware(['throttle:uploads'])->group(function () {
Route::post('/articles', function () {
//
});

...
});

Quelques éléments à prendre en considération

Les injections SQL

Une injection SQL est une attaque qui consiste à ajouter à une requête inoffensive un
complément qui peut être dangereux pour l’application (des explications plus précises
ici).

Si vous utilisez Eloquent vous êtes automatiquement immunisé contre ces attaques.

Par contre si vous faites des requêtes avec par exemple DB::raw() ou whereRaw,
autrement dit si vous contournez Eloquent, alors vous devez vous-même vous prémunir
contre les attaques SQL.

CSRF
Je vous ai déjà parlé de la protection CSRF dans ce cours, elle est automatiquement
mise en place par Laravel.

XSS (Cross-Site Scripting)

Dans ce cas c’est une injection insidieuse de Html ou de JavaScript. Laravel ne fait pas
de nettoyage des entrées en dehors de la validation. Si vous voulez en faire un libre à
vous, par exemple avec ce composant. Par contre vous avez avec Blade la syntaxe
sécuritaire {{ .. }} qui « échappe » les données à l’affichage.

Faites très attention avec la syntaxe {!! … !!} ! En gros vous devez l’éviter avec des
données issues de l’extérieur de l’application.

3/8
L’assignation de masse
Je vous ai déjà parlé de l’assignation de masse. Vous devez choisir avec soin les
colonnes des tables qui sont sans danger dans une mise à jour de masse, celles que
vous mettez dans la propriété $fillable (ou $guarded avec la logique inverse) d’un
modèle.

Les requêtes de formulaire


Un autre lieu où on peut sécuriser l’application est au niveau des requêtes de formulaires.
On a vu qu’il y a une méthode authorize.

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

abstract class Request extends FormRequest


{
/**
* Authorization
*
* @return boolean
*/
public function authorize()
{
return true;
}
}

Vous pouvez ici vérifier par exemple si un utilisateur a le droit d’utiliser effectivement le
formulaire et renvoyer false si ce n’est pas le cas.

Généralités
Il y a des choses qui vont sans dire mais beaucoup mieux en les disant. Laravel ne peut
pas être en toute sécurité sur un serveur qui ne l’est pas, donc le choix de l’hébergement
est un facteur important à prendre en compte et la présence d’un bon Firewall est
indispensable. De plus le SSH devient de nos jours incontournable.

Il faut aussi régulièrement mettre à jour Laravel et les packages associés. Il y a


régulièrement des correctifs de sécurité et si on ne les installe pas on se retrouve exposé.

Faites des sauvegarde de votre site et surtout de la base de données ! On n’est jamais à
l’abri d’un accident…

Les autorisations

4/8
En plus de ces possibilités Laravel nous offre un système complet d’autorisations. Pour
voir de quoi il s’agit on va prendre un exemple simple. On va créer une ressource pour
les utilisateurs à partir d’une installation de base de Laravel équipé de l’authentification :

php artisan make:controller UserController --resource

Changez ainsi le code de ces deux fonctions :

public function edit($id)


{
return 'Formulaire pour modifier';
}

public function update(Request $request, $id)


{
return 'Ok on a modifié !';
}

Et les routes correspondantes qu’on protège avec le middleware auth :

use App\Http\Controllers\UserController;

Route::resource('users', UserController::Class)->middleware('auth');

Maintenant on est sûrs que seules les personnes authentifiées peuvent accéder à la
ressource mais… si quelqu’un veux faire une modification sur un utilisateur, donc passer
par les routes users.edit et users.update il serait souhaitable de ne laisser l’accès à un
utilisateur que pour ses propres informations. Autrement dit si on a l’utilisateur avec l’id 6
il ne doit avoir accès qu’aux urls :

users/6
users/6/edit

Comment réaliser celà ?

On dispose d’une commande pour créer une autorisation :

php artisan make:policy UserPolicy --model=User

Comme on a mentionné le modèle on a déjà toutes les méthodes prêtes :

5/8
<?php

namespace App\Policies;

use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;

class UserPolicy
{
use HandlesAuthorization;

/**
* Determine whether the user can view any models.
*
* @param \App\Models\User $user
* @return mixed
*/
public function viewAny(User $user)
{
//
}

/**
* Determine whether the user can view the model.
*
* @param \App\Models\User $user
* @param \App\Models\User $model
* @return mixed
*/
public function view(User $user, User $model)
{
//
}

/**
* Determine whether the user can create models.
*
* @param \App\Models\User $user
* @return mixed
*/
public function create(User $user)
{
//
}

/**
* Determine whether the user can update the model.
*
* @param \App\Models\User $user
* @param \App\Models\User $model
* @return mixed
*/
public function update(User $user, User $model)
{
//
}

6/8
/**
* Determine whether the user can delete the model.
*
* @param \App\Models\User $user
* @param \App\Models\User $model
* @return mixed
*/
public function delete(User $user, User $model)
{
//
}

/**
* Determine whether the user can restore the model.
*
* @param \App\Models\User $user
* @param \App\Models\User $model
* @return mixed
*/
public function restore(User $user, User $model)
{
//
}

/**
* Determine whether the user can permanently delete the
model.
*
* @param \App\Models\User $user
* @param \App\Models\User $model
* @return mixed
*/
public function forceDelete(User $user, User $model)
{
//
}
}

Il faut ensuite déclarer cette autorisation dans le provider AuthServiceProvider. Mais


comme Laravel est conciliant, si on respecte les appelations, ce qui est le cas ici parce
que j’ai respecté le nom du modèle, la déclaration est automatique. On va donc protéger
la méthode update :

public function update(User $user, User $model)


{
return auth()->id() === $model->id;
}

Il ne reste plus qu’à utiliser l’autorisation dans le contrôleur :

7/8
use App\User;

...

public function edit(User $user)


{
$this->authorize('update', $user);

return 'Formulaire pour modifier';


}

public function update(Request $request, User $user)


{
$this->authorize('update', $user);

return 'Ok on a modifié !';


}

Et maintenant si c’est le bon utilisateur il a l’accès sinon il tombe sur une erreur 404.

Plutôt que d’intervenir dans chaque méthode de la ressource on peut aussi faire agir
l’autorisation globalement sur la ressource avec ce code dans le constructeur :

public function __construct()


{
$this->authorizeResource(User::class, 'user');
}

Vous pouvez trouver la documentation complète ici.

En résumé
L’authentification permet de sécuriser une application et d’adapter l’affichage selon
le degré d’habilitation de l’utilisateur.
Eloquent immunise contre les injections SQL.
La protection CSRF est automatiquement mise en place par Laravel.
Il faut être très prudent avec la syntaxe {!! … !!} de Blade.
On peut sécuriser les requêtes de formulaire avec leur méthode authorize.
Laravel comporte un système complet et simple de gestion des autorisations
(policies).

8/8
Cours Laravel 9 – les événements
laravel.sillo.org/cours-laravel-9-les-evenements/

22 février 2022

Il y a plusieurs façons de programmer. En général la plus simple et lisible consiste à


procéder de façon séquentielle : on a une suite d’instructions (modulables par des
conditions) qui sont toujours les mêmes. Le flux est facile à suivre.

Mais on a aussi l’approche événementielle. On va réagir à des événements susceptibles


de se produire. On peut mettre ainsi en place un ensemble constitué par une partie qui
détecte des événements et une autre qui les gère.

Laravel permet de gérer avec facilité les événements comme on va le voir dans ce
chapitre.

Le design pattern Observateur


Le design pattern Observateur (observer) établit une relation de type 1:n entre des
objets. Si l’objet côté 1 change d’état il en informe les objets côté n pour qu’ils puissent
agir en conséquence. Ça implique une intendance et les objets doivent pouvoir s’inscrire
et se mettre à l’écoute des événements du sujet.

Voici une visualisation de ce design pattern :

1/11
On a un sujet et des observateurs. Les observateurs doivent s’inscrire (on dit aussi
s’abonner) auprès du sujet. Lorsque le sujet change d’état il notifie tous ses abonnés. Un
observateur peut aussi se désinscrire, il ne recevra alors plus les notifications.

Vous pouvez trouver une description détaillée de ce design pattern ici.

Organisation du code
Laravel implémente ce design pattern et le rend très simple d’utilisation. On va avoir :

des classes Event placées dans le dossier app/Events :

des classes Listener placées dans le dossier app/Listeners


:

Ces dossiers n’existent pas dans l’installation de base, ils sont


ajoutés dès qu’on crée la première classe avec Artisan qui
dispose de commandes pour le faire :

2/11
Les événements déjà présents dans Laravel

Il n’y a pas que les événements que vous allez créer. Vous pouvez utiliser les
événements existants déjà dans le framework qui en propose au niveau de
l’authentification :

Registered
CurrentDeviceLogout
OtherDeviceLogout
Attempting
Authenticated
Login
Failed
Logout
Lockout
PasswordReset
Validated
Verified

On trouve aussi des événements avec Eloquent :

retrieved
creating
created
updating
updated
saving
saved
deleting
deleted
restoring
restored

3/11

Pour tous ces cas vous n’avez donc pas à vous inquiéter du déclencheur qui existe déjà.

Une application de test


Pour nos essais créez une nouvelle application Laravel 9 :

composer create-project laravel/laravel laravel9

Créez une base, renseignez correctement le fichier .env. Installez Breeze et lancez les
migrations :

composer require laravel/breeze --dev


php artisan breeze:install
npm install
npm run dev
php artisan migrate

Créez un utilisateur à partir du formulaire d’enregistrement :

Le fournisseur de service (service provider)


Il y a un fournisseur de service dédié aux événements :

Par défaut on a ce code :

4/11
<?php

namespace App\Providers;

use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as
ServiceProvider;
use Illuminate\Support\Facades\Event;

class EventServiceProvider extends ServiceProvider


{
/**
* The event listener mappings for the application.
*
* @var array<class-string, array<int, class-string>>
*/
protected $listen = [
Registered::class => [
SendEmailVerificationNotification::class,
],
];

/**
* Register any events for your application.
*
* @return void
*/
public function boot()
{
//
}

/**
* Determine if events and listeners should be automatically discovered.
*
* @return bool
*/
public function shouldDiscoverEvents()
{
return false;
}
}

On a une propriété $listen pour les abonnements. C’est un simple tableau qui prend
l’événement (event) comme clé et l’observateur (listener) comme valeur. On voit qu’on a
déjà l’événement Registered qui renvoie à l’observateur
SendEmailVerificationNotification. On a vu avec jetstream que lorsqu’un utilisateur
s’enregistre on peut lui envoyer un email, mais on ne s’était pas posé la question de
savoir comment ça se passait en interne. On a ici une réponse, on observe l’événement
Registered et on lance le code pour la notification avec le listener.

5/11
Laravel prévoit de placer les observateurs (listeners) dans un dossier app/Listeners (qui
n’existe pas au départ) en tant que classes, de même pour les événements (events)
dans le dossier Events (qui n’existe pas au départ).

On crée une écoute


On va utiliser l’événement intégré à Laravel qui se déclenche lorsqu’un utilisateur se
connecte. La classe correspondante est Illuminate\Auth\Events\Login. Puisque
l’événement existe on n’a pas à créer une classe Event. Par contre il nous faut une
écoute (Listener) :

php artisan make:listener UserLogin

La classe UserLogin se crée ainsi que le dossier :

Voici le code généré :

<?php

namespace App\Listeners;

use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;

class UserLogin
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}

/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
//
}
}

On va établir le lien entre l’événement et l’écoute dans le provider EventServiceProvider


:

6/11
use Illuminate\Auth\Events\Login;
use App\Listeners\UserLogin;

...

protected $listen = [
...
Login::class => [UserLogin::class],
];

Donc maintenant dès que utilisateur va se connecter la méthode handle de notre écoute
va être appelée. Mais qu’y a-t-il dans la variable $event ? On va faire un petit test :

public function handle($event)


{
dd($event);
}

Là dès qu’on se connecte on obtient :

Illuminate\Auth\Events\Login {#399 ▼
+guard: "web"
+user: App\Models\User {#1370 ▶}
+remember: false
}

Une instance de la classe Illuminate/Auth/Events/Login avec plusieurs propriétés, en


particulier une instance du modèle de l’utilisateur et un booléen pour le remember. On
aurait aussi pu trouver ces informations en allant voir directement la classe.

Changez ainsi le code :

public function handle($event)


{
dd($event->user->name . " s'est connecté.");
}

Cette fois à la connexion on obtient quelque chose dans ce genre :

"Durand s'est connecté."

On peut donc ici effectuer tous les traitements qu’on veut, comme comptabiliser les
connexions.

Découverte automatique des événements


On a vu ci-dessus qu’on a créé un listener pour un événement existant dans Laravel
(Login) et qu’on a dû déclarer le lien entre événement et listener dans le provider
EventServiceProvider. Mais il est possible de rendre ce lien automatique, pour ça il faut
faire deux choses :

7/11
comme Laravel utilise la réflexion il faut un peu l’aider et préciser la classe de
l’événement dans la fonction handle du listener :

use Illuminate\Auth\Events\Login;

...

public function handle(Login $event)

il faut aussi changer la vaeur de retour dans cette méthode du provider


EventServiceProvider :

/**
* Determine if events and listeners should be automatically discovered.
*
* @return bool
*/
public function shouldDiscoverEvents()
{
return true;
}

Maintenant on peut s’éviter l’écriture de la liaison dans la propriété $listen !

On crée un événement
Maintenant on va créer un événement et aussi son écoute. On va imaginer qu’on veut
savoir quand quelqu’un affiche la page d’accueil et mémoriser son IP ainsi que le moment
du chargement de la page, en gros un peu de statistique.

On va commencer par créer une migration :

php artisan make:migration create_visits_table --create=visits

Avec ces créations de colonnes :

public function up()


{
Schema::create('visits', function (Blueprint $table) {
$table->id();
$table->string('ip', 45);
$table->timestamp('created_at');
});
}

8/11
Et on lance la migration :

php artisan migrate

On crée l’événement :

php artisan make:event Accueil

On le trouve ici avec création du dossier :

Avec ce code par défaut :

<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class Accueil
{
use Dispatchable, InteractsWithSockets, SerializesModels;

/**
* Create a new event instance.
*
* @return void
*/
public function __construct()
{
//
}

/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}

On ne va pas toucher à ce code.

On crée aussi l’écoute :

php artisan make:listener Accueil

9/11
On modifie ainsi le code :

<?php

namespace App\Listeners;

use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;
use App\Events\Accueil as AcceuilEvent;

class Accueil
{
public function __construct()
{
//
}

public function handle(AcceuilEvent $event)


{
DB::table('visits')->insert([
'ip' => request()->ip(),
'created_at' => Carbon::now(),
]);
}
}

J’utilise directement le Query Builder sans passer par Eloquent.

Il ne reste plus qu’à déclencher cet événement dans la route :

use App\Events\Accueil;

Route::get('/', function () {
event(new Accueil);
return view('welcome');
});

Maintenant chaque fois que la page d’accueil est ouverte l’événement Accueil est
déclenché et donc l’écoute Accueil en est informée. On sauvegarde alors dans la base
l’IP et le moment du chargement :

Si le traitement de vos événements est long (envoie


d’email, requête…) il est conseillé d’utiliser un système
de file d’attente. Vous pouvez consulter la
documentation correspondante.

En résumé
Laravel permet d’utiliser la programmation événementielle.
Laravel comporte des événements préexistants au niveau de l’authentification et
d’Eloquent.

10/11
On peut créer des événements (Events) et des écoutes (Listeners) et les mettre
en relation automatiquement.

11/11
Cours Laravel 9 – la localisation
laravel.sillo.org/cours-laravel-9-la-localisation/

24 février 2022

Lorsqu’on crée un site c’est souvent dans une optique multi-langages. On parle alors
d’internationalisation (i18n) et de localisation (L10n). L’internationalisation consiste à
préparer une application pour la rendre potentiellement adaptée à différents langages. La
localisation consiste quant à elle à ajouter un composant spécifique à une langue.

C’est un sujet assez complexe qui ne se limite pas à la traduction des textes mais qui
impacte aussi la représentation des dates, la gestion des pluriels, parfois la mise en
page…

Qu’a à nous proposer Laravel dans ce domaine ? Nous allons le voir dans ce chapitre.

Pour les curieux i18n parce qu’il y a 18 lettres entre le « i » et le « n » de


internationalisation et L10n parce qu’il y a 10 lettres entre le « L » et le « n » de
Localisation.

Le principe

Les fichiers de langage


On a deux façons de gérer les langues.

Clés et valeurs

On a déjà parlé des fichiers de langage. Lorsque Laravel est installé on a cette
architecture :

Il n’est prévu au départ que la langue anglaise (en) avec 4


fichiers. Pour ajouter une langue, il suffit de créer un nouveau
dossier, par exemple fr pour le Français.

Il faut évidemment avoir le même contenu comme nous allons le


voir.

Vous n’allez pas être obligé de faire toute cette traduction vous-
même ! Certains s’en sont déjà chargés avec ce package. Nous
l’avons déjà utilisé dans ce cours.

On va ajouter la traduction française à partir de ce dépôt :

Attention au fichier fr.json qui doit être extrait du dossier pour être placé à la racine.

1/7
Les fichiers présents sont constitués exactement de la
même manière mis à part trois fichiers qui servent pour
des installations supplémentaires comme Nova). Prenons
le plus léger : pagination.php, pour l’anglais on a :

return [
'previous' => '&laquo; Previous',
'next' => 'Next &raquo;',
];

On voit qu’on se contente de renvoyer un tableau avec


des clés et des valeurs. On ne peut pas faire plus simple !
Si on prend le même fichier en version française :

return [
'previous' => '&laquo; Précédent',
'next' => 'Suivant &raquo;',
];

On retrouve bien sûr les mêmes clés avec des valeurs adaptées au langage.

Les textes

Le système clé/valeur peut devenir rapidement lourd et confus lorsqu’on a beaucoup de


textes à traduire. Laravel propose une autre approche qui consiste à utiliser la traduction
par défaut comme clé.

Si la langue par défaut est l’anglais par exemple, on aura le texte anglais directement
dans le code. Les autres traductions se trouveront dans des fichiers JSON.

Par exemple pour le français, on a le fichier fr.json :

Ce fichier contient des éléments de ce type :

"90 Days": "90 jours",

On a :

90 Days : le texte en langage par défaut présent dans le code


90 jours : la traduction en français

Les deux systèmes peuvent cohabiter et se compléter.

Configuration de la locale

2/7
La configuration de la locale est effectuée dans le fichier config/app.php :

'locale' => 'en',

On peut changer cette locale en cours d’exécution si nécessaire :

App::setLocale('fr');

Si une traduction n’est pas trouvée dans la locale actuelle alors on va chercher la
traduction de la locale par défaut définie également dans le fichier config/app.php :

'fallback_locale' => 'en',

On peut connaître la locale actuelle :

$locale = App::getLocale();

Et même tester si on a une certaine locale :

if (App::isLocale('fr')) {
//
}

Dans le code

L’helper __

Concrètement au niveau du code, on utilise l’helper __. Il suffit de préciser la clé comme
paramètre (la clé pour le système clé-valeur et le texte dans la langue d’origine pour le
second système). Prenons un exemple :

$info = __('Posts for category: ');

Si la locale est en c’est ce texte qui sera directement affiché. Si la locale est fr c’est le
texte correspondant qu’on trouve dans le fichier fr.json qui sera affiché :

"Posts for category: ": "Articles pour la catégorie : ",

Blade

On peut utiliser l’helper __ dans une vue Blade :

{{ __('Get In Touch With Us') }}

Mais il est plus pratique d’utiliser la directive @lang :

@lang('Get In Touch With Us')

Le pluriel
La gestion du pluriel n’est pas toujours facile. On peut prévoir le texte du singulier et celui
du pluriel séparés par le caractère | :

3/7
'pommes' => 'une pomme|plusieurs pommes',

On peut affiner :

'pommes' => '{0} aucune pomme|[1,10] quelques pommes|[10,*] beaucoup de pommes',

On utilise dans le code la fonction trans_choice. Voici un exemple tiré d’une application :

{{ trans_choice(__('comment|comments'), $post->valid_comments_count) }}

Selon la valeur de $post->valid_comments_count, autrement dit le nombre de


commentaires valides, on prend le singulier ou le pluriel grâce à trans_choice et dans la
langue voulue grâce à l’helper __.

Si on a un seul commentaire, on a le singulier :

Sinon on a le pluriel :

Des assistants
Pour gérer toutes ces traductions, il existe des assistants bien utiles.

Pour le système clé-valeur voici un package intéressant :

4/7
Il copie les fichiers de langue dans la base de données et permet une gestion à partir
d’une interface web.

Pour la gestion des fichiers JSON j’ai aussi créé un package :

Il ajoute 4 commandes à Artisan :

language:strings pour visualiser tous les textes du code (dans app et


resource/views)
language:make pour créer un fichier JSON pour une locale complétée avec les
textes du code
language:diff pour voir les différences entre une locale JSON et les textes du code

5/7
language:sync pour synchroniser une locale JSON avec les textes du code

Les noms pour la validation


Il y a un autre élément à prendre en compte pour la traduction : le nom des contrôles de
saisie. Ces noms ne sont pas visibles tant qu’il n’y a pas de souci de validation, ils sont
alors transmis dans le texte de l’erreur.

‌ n voit mal présenter en langue française « Le champ name est obligatoire… ». Alors
O
comment faire ?

Si vous regardez dans le fichier resources/lang/fr/validation.php vous trouvez ce


tableau :

'attributes' => [
'name' => 'nom',
'username' => 'nom d'utilisateur'

Ici on fait correspondre le nom d’un attribut avec sa version linguistique pour justement
avoir quelque chose de cohérent au niveau du message d’erreur.

Les dates
Il ne vous a sans doute pas échappé que les dates et les heures ne sont pas présentées
de la même manière selon la langue utilisée. En particulier entre le français et l’anglais.

En France nous utilisons le format jj/mm/aaaa alors que les Américains utilisent le format
mm/jj/aaaa. De la même façon les heures sont présentées différemment.

Laravel utilise Carbon pour la gestion des dates. Carbon hérite de la classe PHP
DateTime. On dispose donc de toutes les possibilités de cette classe plus toutes celles
ajoutées par Carbon, et elles sont nombreuses. Depuis Laravel 6 on est passé à la
version 2 de Carbon qui apporte pas mal de nouvelles possibilités et je vous conseille
vivement la lecture de la documentation.

Quand Eloquent récupère une date de created_at ou updated_at il la transforme


automatiquement en instance Carbon. On peut modifier ce comportement, mais je n’en
vois pas l’intérêt sauf cas bien particulier.

Partez d’une nouvelle installation de Laravel et allez dans le fichier AppServiceProvider


pour ajouter ce code :

public function boot()


{
setlocale(LC_TIME, config('app.locale'));

...
}

6/7
La méthode setLocale fait appel à la fonction PHP strftime. C’est ainsi qu’on a la date et
l’heure bien affichées pour l’anglais et le français.

Dans la vue welcome.blade.php entrez ce code :

Date : {{ \Carbon\Carbon::now()->isoFormat('LL') }}

En anglais vous affichez :

Date : October 1, 2020

Et en français :

Date : 1 octobre 2020

Dans la vue welcome.blade.php entrez maintenant ce code :

Date : {{ \Carbon\Carbon::now()->calendar() }}

En anglais vous affichez :

Date : Today at 12:27 PM

Et en français :

Date : Aujourd’hui à 12:27

Le but de ce chapitre est de montrer comment localiser l’interface d’une page web. S’il
s’agit d’adapter le contenu selon la langue, c’est une autre histoire et il faut alors
intervenir au niveau de l’url. En effet, un principe fondamental du web veut que pour une
certaine url on renvoie toujours le même contenu.

En résumé
Laravel possède les outils de base pour la localisation.
Laravel propose deux systèmes complémentaires : clé-valeur ou texte dans le code
associé à des fichiers JSON.
Il faut créer autant de fichiers de localisation qu’on a de langues à traiter.
Il faut prévoir la version linguistique des attributs.
Grâce à Carbon la présentation des dates est heures selon la locale est simplifiée.

7/7
Cours Laravel 9 – les notifications
laravel.sillo.org/cours-laravel-9-les-notifications/

25 février 2022

On a vu dans ce cours comment envoyer un email avec Laravel. Mais on dispose aussi
d’un système complet de notifications, par exemple par SMS, qui inclue aussi les emails
ou même la base de données.

Classiquement une notification est un message court pour informer un utilisateur qu’il
s’est passé quelque chose qui le concerne dans l’application.

Par exemple une donnée sensible a été mise à jour, on envoie un SMS par sécurité en
informant l’utilisateur de ce changement et, si ce n’est pas lui qui l’a effectué, il peut alors
intervenir.

Évidemment pour tout ce qui n’est pas email ou base de données il faut utiliser un service
externe. Il y a un site dédié pour tous les drivers existants et la liste est longue !

Organisation du code
Il y a une commande d’artisan pour créer une notification :

php artisan make:notification MaNotification

Les notifications se trouvent dans le dossier app/Notifications :

Ce dossier n’existe pas dans l’installation de base de Laravel,


il est ajouté lorsqu’on crée la première notification avec
Artisan.

Bien sûr, pour les packages les notifications se situent dans un


dossier du package.

Le renouvellement du mot de passe


On a vu en détail le renouvellement du mot de passe dans ce chapitre. J’ai alors précisé
qu’on envoyait un email par le système de notification en précisant que je vous en
parlerai plus tard. C’est ce que je vais faire maintenant.

On a vu que Laravel, après la demande de renouvellement de l’utilisateur, expédie ce


genre d’email :

1/8
Par défaut la classe de notification se situe dans le framework :

Avec ce code (extrait) :

2/8
<?php

namespace Illuminate\Auth\Notifications;

use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Lang;

class ResetPassword extends Notification


{
...

/**
* Get the notification's channels.
*
* @param mixed $notifiable
* @return array|string
*/
public function via($notifiable)
{
return ['mail'];
}

/**
* Build the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
if (static::$toMailCallback) {
return call_user_func(static::$toMailCallback, $notifiable, $this-
>token);
}

return $this->buildMailMessage($this->resetUrl($notifiable));
}

/**
* Get the reset password notification mail message for the given URL.
*
* @param string $url
* @return \Illuminate\Notifications\Messages\MailMessage
*/
protected function buildMailMessage($url)
{
return (new MailMessage)
->subject(Lang::get('Reset Password Notification'))
->line(Lang::get('You are receiving this email because we received a
password reset request for your account.'))
->action(Lang::get('Reset Password'), $url)
->line(Lang::get('This password reset link will expire in :count
minutes.', ['count' =>
config('auth.passwords.'.config('auth.defaults.passwords').'.expire')]))
->line(Lang::get('If you did not request a password reset, no further

3/8
action is required.'));
}

...
}

Comme c’est une notification par email, on a une méthode toMail. D’autre part on précise
dans la méthode via qu’on retourne un email. La classe MailMessage offre un certain
nombre de méthodes bien pratiques comme :

line : pour écrire une ligne


action : pour afficher un bouton
from : pour définir l’adresse de l’expéditeur
subject : pour définir le sujet
cc et bcc : pour faire des copies
attach : pour joindre un document
priority : pour fixer la priorité…

Vous constatez qu’on a une mise en forme de l’email plutôt esthétique. Cette mise en
forme est issue d’une vue par défaut dans le framework :

Si vous voulez modifier cette mise en forme il faut publier cette


vue :

php artisan vendor:publish --tag=laravel-notifications

Vous retrouvez alors la vue dans le dossier


resources/views/vendor/notifications :

Vous pouvez vous demander à présent comment est


effectivement commandée cette notification. Ça se passe dans le
trait CanResetPassword avec la méthode notify :

<?php

namespace Illuminate\Auth\Passwords;

use Illuminate\Auth\Notifications\ResetPassword as
ResetPasswordNotification;

trait CanResetPassword
{
...

public function sendPasswordResetNotification($token)


{
$this->notify(new
ResetPasswordNotification($token));
}
}

4/8
Un exemple
Comme rien ne vaut un exemple, on va en réaliser un. On va à nouveau partir d’une
installation vierge de Laravel avec Breeze. On va décider d’envoyer un email à un
utilisateur qui s’enregistre avec succès. On commence par créer la notification avec
Artisan :

php artisan make:notification RegisteredNotification

On a ce code à la création :

5/8
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class RegisteredNotification extends Notification


{
use Queueable;

/**
* Create a new notification instance.
*
* @return void
*/
public function __construct()
{
//
}

/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['mail'];
}

/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
return (new MailMessage)
->line('The introduction to the notification.')
->action('Notification Action', url('/'))
->line('Thank you for using our application!');
}

/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{

6/8
return [
//
];
}
}

On va modifier la fonction toMail pour changer le message :

public function toMail($notifiable)


{
return (new MailMessage)->line('Vous avez bien été enregistré sur notre site
!');
}

Il ne reste plus qu’à envoyer la notification à partir de la méthode store du contrôleur


RegisteredUserController :

use App\Notifications\RegisteredNotification;

...

protected function store(Request $request)


{
...
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);

$user->notify(new RegisteredNotification());

...
}

Maintenant quand un utilisateur s’enregistre il reçoit un email :

7/8
C’est tout simple !

Si on veut le texte de fond en français, ou tout autre langage, on peut publier le template
comme on l’a vu plus haut :

Vous pouvez d’ailleurs remarquer que ce template est déjà


prévu i18n. Par exemple pour l’accueil on a :

@lang('Hello!')

Autrement dit, comme on l’a vu dans l’article précédent sur la


localisation, il suffit de prévoir par exemple un fichier fr.json :

Avec ce code :

{
"Hello!": "Bonjour !"
}

Et maintenant dans l’email :

Il y aurait encore beaucoup à dire sur


les notifications au-delà des exemples
de ce chapitre, reportez-vous à la documentation officielle pour en savoir plus.

En résumé
Laravel comporte un système complet et performant de notifications.
On peut envoyer des emails avec les notifications, leur mise en forme est facilitée
par la présence de puissantes méthodes et d’un template qu’on peut personnaliser.

8/8
Cours Laravel 9 – les vues
laravel.sillo.org/cours-laravel-9-les-vues/

25 février 2022

Je vous ai déjà parlé des vues dans ce cours et de Blade et on a eu plusieurs exemples
de code. Dans ce chapitre, je vais faire un peu le point et montrer des possibilités
intéressantes qui n’ont pas encore été évoquées.

Pour les besoins d’illustrer ce chapitre, on va repartir de l’application des films que j’ai
créée pour ce cours et dont vous pouvez récupérer la version définitive ici. Il suffit de
décompresser dans un dossier et ensuite de lancer cette commande :

composer install

Il faut aussi créer une base de données et bien renseigner dans le fichier .env :

DB_DATABASE=laravel9
DB_USERNAME=root
DB_PASSWORD=

Ensuite lancer les migrations et la population :

php artisan migrate --seed

Ensuite l’application doit fonctionner avec l’url …/films.

Le cache

1/9
Les vues générées par Blade sont en PHP et sont en cache jusqu’à ce qu’elles soient
modifiées, ce qui assure de bonnes performances. Le cache se situe ici :

Si vous avez besoin de nettoyer ce


cache ce n’est pas la peine d’aller
supprimer ces fichiers dans le
dossier, il y a une commande
Artisan pour ça :

php artisan view:clear

On en a besoin parfois en cours de


développement lorsqu’une vue
n’est pas régénérée correctement.

L’héritage
On a vu qu’une vue peut en étendre une autre, c’est un héritage. Ainsi pour les vues de
l’application, on a un template de base :

Ce template comporte la structure globale des pages et est


déclaré comme parent par les autres vues :

@extends('template')

Dans le template on prévoit un emplacement (@yield) pour que


les vues enfants puissent placer leur code :

<main class="section">
<div class="container">
@yield('content')
</div>
</main>

Ainsi dans la vue index.blade.php on utilise cet emplacement :

@section('content')
// Code de la vue
@endsection

On peut également déclarer une section dans le template avec @section lorsqu’on a du
code dans le template qu’on veut soit utiliser, soit surcharger.

L’inclusion

2/9
On peut faire beaucoup de choses avec l’héritage, mais il est souvent utile de pouvoir
inclure une vue dans une autre, classiquement on parle de vue partielle (partial).

Si on regarde de près la vue index, on a un tableau avec comme corps des lignes avec
les films :

On peut mettre le code qui génère ces lignes dans une vue partielle :

En récupérant le code de la vue index :

3/9
@foreach($films as $film)
<tr @if($film->deleted_at) class="has-background-grey-lighter" @endif>
<td><strong>{{ $film->title }}</strong></td>
<td>
@if($film->deleted_at)
<form action="{{ route('films.restore', $film->id) }}"
method="post">
@csrf
@method('PUT')
<button class="button is-primary"
type="submit">Restaurer</button>
</form>
@else
<a class="button is-primary" href="{{ route('films.show',
$film->id) }}">Voir</a>
@endif
</td>
<td>
@if($film->deleted_at)
@else
<a class="button is-warning" href="{{ route('films.edit',
$film->id) }}">Modifier</a>
@endif
</td>
<td>
<form action="{{ route($film->deleted_at? 'films.force.destroy' :
'films.destroy', $film->id) }}" method="post">
@csrf
@method('DELETE')
<button class="button is-danger" type="submit">Supprimer</button>
</form>
</td>
</tr>
@endforeach

Et dans la vue index, on se contente d’inclure cette vue :

<tbody>
@include('partials.lines')
</tbody>

On dispose aussi des directives @includeIf, @includeWhen et @includeFirst.

Une autre possibilité consiste à gérer l’itération à partir de la vue principale :

@each('partials.lines', $films, 'film')

Et évidemment on ne conserve dans la vue partielle que le code inclus dans la boucle :

<tr @if($film->deleted_at) class="has-background-grey-lighter" @endif>


...
</tr>

Les composants

4/9
Comme alternative à l’inclusion, on dispose des composants. Voyons de quoi il s’agit.

Dans la vue index, on a une partie du code consacrée à la notification lorsqu’une action
a bien abouti comme la modification d’un film :

Voici le code correspondant :

@if(session()->has('info'))
<div class="notification is-success">
{{ session('info') }}
</div>
@endif

On crée le composant avec Artisan :

php artisan make:component Notification

On a création d’une classe :

Et d’une vue :

Pour la classe, on prévoit ce code :

<?php

namespace App\View\Components;

use Illuminate\View\Component;

class Notification extends Component


{

public $text;

public function __construct($text)


{
$this->text = $text;
}

public function render()


{
return view('components.notification');
}
}

Et pour la vue :

<div class="notification is-success">


{{ $text }}
</div>

5/9
La variable $text va recevoir ce qu’on va injecter dans le composant. Voici le code
modifié dans la vue index :

@if(session()->has('info'))
<x-notification :text="session('info')"/>
@endif

On peut aussi prévoir une variable {{ $slot }} dans le composant et injecter directement
du contenu HTML.

Les structures de contrôle

@if

La directive @if permet d’introduire une condition. Par exemple dans la vue partielle lines
qu’on a créée ci-dessus on trouve ce code :

@if($film->deleted_at) class="has-background-grey-lighter" @endif

Ce qui permet de griser le fond de la ligne pour un film dans la corbeille :

Les directives @else et @unless permettent de compléter la logique.

Remarquez au passage la directive @php qui permet d’insérer du code PHP dans la vue.

@isset et @empty
La directive @isset est l’équivalent de la fonction PHP isset().

La directive @empty est l’équivalent de la fonction PHP empty().

@auth et @guest
Il est fréquent d’avoir à tester si un utilisateur est connecté et on peut écrire :

@if (Auth::check())

Mais il est plus élégant d’utiliser @auth. de la même manière on dispose de @guest.

Il existe aussi la directive @switch équivalente de la directive PHP switch.

Condition personnalisée

6/9
On peut aussi se construire une condition personnalisée. On pourrait prévoir ce code
dans AppServiceProvider:

public function boot()


{
...

Blade::if('admin', function () {
return auth()->user()->role === 'admin';
});

Blade::if('redac', function () {
return auth()->user()->role === 'redac';
});

Blade::if('request', function ($url) {


return request()->is($url);
});
}

On crée ainsi 3 directives :

admin : vérifie qu’un utilisateur est administrateur


redac : vérifie qu’un utilisateur est rédacteur
request : vérifie que la requête correspond à une certaine url

Du coup, on peut écrire ce genre de code :

@admin
<li>
<a href="{{ url('admin') }}">@lang('Administration')</a>
</li>
@endadmin
@redac
<li>
<a href="{{ url('admin/posts') }}">@lang('Administration')</a>
</li>
@endredac

Et dans une vue :

@request('password/reset')
<li class="current">
<a href="{{ request()->url() }}">@lang('Password')</a>
</li>
@endrequest
@request('password/reset/*')
<li class="current">
<a href="{{ request()->url() }}">@lang('Password')</a>
</li>
@endrequest

Les boucles

7/9
Pour les boucles, on dispose de @for, @foreach, @each, @forelse et @while. La
logique est la même que les directives équivalentes de PHP. Voyons un exemple dans la
vue show :

@foreach($film->categories as $category)
<li>{{ $category->name }}</li>
@endforeach

C’est la partie du code qui remplit la liste des catégories quand on affiche un film :

Avec la directive @foreach on dispose de la variable interne


$loop qui offre des informations sur l’index en cours. Voici ce qui
est disponible :

Propriété Description

$loop->index L’index en cours (commence à 0)

$loop->iteration L’itération courante (commence à 1)

$loop->remaining Les itérations restantes

$loop->count Le nombre total d’itérations

$loop->first Indique si c’est le premier élément

$loop->last Indique si c’est le dernier élément

$loop->depth Le niveau d’imbrication actuel

$loop->parent La variable d’itération parente si imbrication

Les composeurs de vues (view composers)


Les composeurs de vues sont des fonctions de rappel ou des classes qui sont appelées
lorsqu’une vue est générée, ce qui permet de localiser du code. Voyons un exemple dans
l’application.

Regardez ce code dans AppServiceProvider (on aurait pu créer un provider spécifique)


:

public function boot()


{
View::composer(['index', 'create', 'edit'], function ($view) {
$view->with('categories', Category::all());
$view->with('actors', Actor::all());
});
}

On dit là que lorsqu’on affiche une des vues index, create ou edit il faut envoyer les
variables $categories et $actors.

8/9
En résumé
Avec Blade on peut :

utiliser l’héritage de vue,


inclure une vue,
utiliser un composant,
utiliser de nombreuses directives pour organiser le code,
utiliser des boucles,

D’autre part les composeurs de vues permettent de localiser du code facilement.

9/9
Cours Laravel 9 – les tests
laravel.sillo.org/cours-laravel-9-les-tests/

26 février 2022

Les développeurs PHP n’ont pas été habitués à faire des tests pour leurs applications.
Cela est dû à l’histoire de ce langage qui n’était au départ qu’une possibilité de scripter
au milieu du code HTML mais qui s’est peu à peu développé comme un langage de plus
en plus évolué. Les créateurs de frameworks ont initié une autre façon d’organiser le
code de PHP, en particulier ils ont mis en avant la séparation des tâches qui a rendu la
création de tests possible.‌

Laravel a été pensé pour intégrer des tests. Il comporte une infrastructure élémentaire et
des helpers. Nous allons voir dans ce chapitre cet aspect de Laravel. Considérez ce que
je vais vous dire ici comme une simple introduction à ce domaine qui mériterait à lui seul
un cours spécifique. Je vais m’efforcer de vous démontrer l’utilité de créer des tests,
comment les préparer et comment les isoler.

Lorsqu’on développe avec PHP on effectue forcément des tests au moins manuels. Par
exemple si vous créez un formulaire vous allez l’utiliser, entrer diverses informations,
essayer des fausses manœuvres… Imaginez que tout cela soit automatisé et que vous
n’ayez qu’à cliquer pour lancer tous les tests. C’est le propos de ce chapitre.

Vous pouvez aussi vous dire qu’écrire des tests conduit à du travail supplémentaire, que
ce n’est pas toujours facile, que ce n’est pas nécessaire dans tous les cas. C’est à vous
de voir si vous avez besoin d’en créer ou pas. Pour des petites applications la question
reste ouverte. Par contre dès qu’une application prend de l’ampleur ou lorsqu’elle est
conduite par plusieurs personnes alors il devient vite nécessaire de créer des tests
automatisés.

L’intendance des tests

PHPUnit

Laravel utilise PHPUnit pour effectuer les tests. C’est un framework créé par Sebastian
Bergmann qui fonctionne à partir d’assertions.

Ce framework est installé comme dépendance de Laravel en mode développement :

"require-dev": {
...
"phpunit/phpunit": "^9.5.10"
},

Mais vous pouvez aussi utiliser le fichier phar que vous pouvez télécharger à partir de
cette page et le placer à la racine de votre application et vous êtes prêt à tester !

1/14
Vous pouvez vérifier que ça fonctionne en entrant cette commande :

php phpunit(numéro de version).phar -h

Vous obtenez ainsi la liste de toutes les commandes disponibles.

Si vous utilisez la version installée avec Laravel ça donne :

php vendor\phpunit\phpunit\phpunit -h

Mais il y a bien plus simple en utilisant Artisan :

php artisan test

L’intendance de Laravel
Si vous regardez les dossiers de Laravel vous allez en trouver un qui est consacré aux
tests :

Vous avez déjà deux dossiers et deux fichiers. Voilà le code


de TestCase.php :

<?php

namespace Tests;

use Illuminate\Foundation\Testing\TestCase as
BaseTestCase;

abstract class TestCase extends BaseTestCase


{
use CreatesApplication;
}

On voit qu’on utilise le trait CreatesApplication.

Cette classe est chargée de créer une application pour les tests dans un environnement
spécifique (ce qui permet de mettre en place une configuration adaptée aux tests).

Elle étend la classe Illuminate\Foundation\Testing\TestCase qui elle-même étend la


classe PHPUnit\Framework\TestCase en lui ajoutant quelques fonctionnalités bien
pratiques, comme nous allons le voir.

Toutes les classes de test que vous allez créer devront étendre cette classe TestCase.

On a 2 exemples de tests déjà présents, un dans Unit\ExampleTest.php :

2/14
<?php

namespace Tests\Unit;

use PHPUnit\Framework\TestCase;

class ExampleTest extends TestCase


{
/**
* A basic test example.
*
* @return void
*/
public function test_that_true_is_true()
{
$this->assertTrue(true);
}
}

Et un autre dans Features\ExampleTest.php :

<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;

class ExampleTest extends TestCase


{
/**
* A basic test example.
*
* @return void
*/
public function test_the_application_returns_a_successful_response()
{
$response = $this->get('/');

$response->assertStatus(200);
}
}

Pourquoi 2 dossiers ?

Si on lit la documentation sur le sujet on trouve cette explication :

By default, your application's tests directory contains two directories: Feature


and Unit. Unit tests are tests that focus on a very small, isolated portion of
your code. In fact, most unit tests probably focus on a single method...

Feature tests may test a larger portion of your code, including how several
objects interact with each other or even a full HTTP request to a JSON endpoint.

Generally, most of your tests should be feature tests. These types of tests provide

3/14
En gros dans Feature on va mettre des tests plus généraux, pas vraiment unitaires pour
le coup. Mais vous pouvez utiliser ces deux dossiers à votre convenance et n’en utiliser
qu’un seul.

Sans entrer pour le moment dans le code sachez simplement que dans le premier
exemple on se contente de demander si un truc vrai est effectivement vrai (bon c’est sûr
que ça devrait être vrai ^^). Dans le second on envoie une requête pour la route de base
et on attend une réponse positive (200).

Pour lancer ces tests c’est très simple, entrez la commande php artisan test :

On voit qu’ont été effectués 2 tests et 2 assertions et que tout s’est bien passé.

L’environnement de test
Je vous ai dit que les tests s’effectuent dans un environnement particulier, ce qui est bien
pratique.

Où se trouve cette configuration ?

Regardez le fichier phpunit.xml :

4/14
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory suffix="Test.php">./tests/Feature</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./app</directory>
</include>
</coverage>
<php>
<server name="APP_ENV" value="testing"/>
<server name="BCRYPT_ROUNDS" value="4"/>
<server name="CACHE_DRIVER" value="array"/>
<!-- <server name="DB_CONNECTION" value="sqlite"/> -->
<!-- <server name="DB_DATABASE" value=":memory:"/> -->
<server name="MAIL_MAILER" value="array"/>
<server name="QUEUE_CONNECTION" value="sync"/>
<server name="SESSION_DRIVER" value="array"/>
<server name="TELESCOPE_ENABLED" value="false"/>
</php>
</phpunit>

On trouve déjà 6 variables d’environnement :

APP_ENV : là on dit qu’on est en mode testing,


BCRYPT_ROUNDS : avec une valeur de 4 (par défaut c’est 10),
CACHE_DRIVER : en mode array ce qui signifie qu’on ne va rien mettre en cache
pendant les tests (par défaut on a file),
MAIL_DRIVER : en mode array donc on n’envoie pas les mails,
QUEUE_CONNECTION : en mode sync, donc on aura pas de file d’attente,
SESSION_DRIVER : en mode array ce qui signifie qu’on ne va pas faire persister
la session (par défaut on a file),
TELESCOPE_ENABLED : ne sert que si Telescope a été installé.

On peut évidemment ajouter les variables dont on a besoin. Par exemple si pendant les
tests je ne veux plus MySql mais sqlite.

Il y a une variable dans le fichier .env :

DB_CONNECTION=mysql

Du coup dans phpunit.xml je peux écrire :

5/14
<env name="DB_CONNECTION" value="sqlite"/>

Maintenant pour les tests je vais utiliser sqlite.

Construire un test

Les trois étapes d’un test

Pour construire un test on procède généralement en trois étapes :

1. on initialise les données,


2. on agit sur ces données,
3. on vérifie que le résultat est conforme à notre attente.

Comme tout ça est un peu abstrait prenons un exemple. Remplacez le code avec celui-ci
(peu importe dans quel dossier) :

public function testBasicTest()


{
$data = [10, 20, 30];
$result = array_sum($data);
$this->assertEquals(60, $result);
}

Supprimez le test dans l’autre dossier pour éviter de polluer les résultats.

On trouve nos trois étapes. On initialise les données :

$data = [10, 20, 30];

On agit sur ces données :

$result = array_sum($data);

On teste le résultat :

$this->assertEquals(60, $result);

La méthode assertEquals permet de comparer deux valeurs, ici 60 et $result. Si vous


lancez le test vous obtenez :

Vous voyez à nouveau l’exécution d’un test. Le


tout s’est bien passé. Changez la valeur 60 par
une autre et vous obtiendrez ceci :

6/14
Vous connaissez maintenant le principe de base d’un test et ce qu’on peut obtenir
comme renseignement en cas d’échec.

Assertions et appel de routes

Les assertions

Les assertions constituent l’outil de base des tests. On en a vu une ci-dessus et il en


existe bien d’autres. Vous pouvez en trouver la liste complète ici.

Voici quelques assertions et l’utilisation d’un helper de Laravel que l’on teste au passage :

use Illuminate\Support\Str;

...

public function testBasicTest()


{
$data = 'Je suis petit';
$this->assertTrue(Str::startsWith($data, 'Je'));
$this->assertFalse(Str::startsWith($data, 'Tu'));
$this->assertSame(Str::startsWith($data, 'Tu'), false);
$this->assertStringStartsWith('Je', $data);
$this->assertStringEndsWith('petit', $data);
}

Lorsqu’on lance le test on obtient ici :

Tout se passe bien…

7/14
Appel de route et test de réponse

Il est facile d’appeler une route pour effectuer un test sur la réponse. Modifiez la route de
base pour celle-ci :

Route::get('/', function () {
return 'coucou';
});

On a donc une requête avec l’url de base et comme réponse la chaîne coucou. Nous
allons tester que la requête aboutit bien, qu’il y a une réponse correcte et que la réponse
est coucou (effectuez ce test dans le dossier Feature) :

public function testBasicTest()


{
$response = $this->get('/');
$response->assertSuccessful();
$this->assertEquals('coucou', $response->getContent());
}

L’assertion assertSuccessful nous assure que la réponse est correcte. Ce n’est pas une
assertion de PHPUnit mais une spécifique de Laravel. Vous trouvez toutes les assertion
de Laravel ici.

La méthode getContent permet de lire la réponse. Le test est à nouveau correct.

Les vues et les contrôleurs

Les vues

Qu’en est-il si on retourne une vue ?

Mettez ce code pour la route :

Route::get('/', function () {
return view('welcome')->with('message', 'Vous y êtes !');
});

Ajoutez dans cette vue ceci

{{ $message }}

Maintenant voici le test :

8/14
public function testBasicTest()
{
$response = $this->get('/');
$response->assertViewHas('message', 'Vous y êtes !');
}

On envoie la requête et on récupère la réponse. On peut tester la valeur de la variable


$message dans la vue avec l’assertion assertViewHas. A nouveau le test est correct.

Maintenant changer le code ainsi :

$response->assertViewHas('message', 'Vous n\'y êtes pas !');

Cette fois le test donne une erreur :

Et vous voyez la précision du commentaire.

Les contrôleurs

Créez ce contrôleur :

9/14
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class WelcomeController extends Controller


{
public function index()
{
return view('welcome');
}
}

Créez cette route pour mettre en œuvre le contrôleur ci-dessus :

use App\Http\Controllers\WelcomeController;

Route::get('welcome', [ WelcomeController::class, 'index']);

Vérifiez que ça fonctionne (vous aurez peut-être besoin de retoucher la vue où nous
avons introduit une variable).

Supprimez le fichier ExampleTest.php qui ne va plus nous servir.

Créez ce test avec Artisan :

php artisan make:test WelcomeControllerTest

Par défaut vous obtenez ce code :

<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;

class WelcomeControllerTest extends TestCase


{
/**
* A basic feature test example.
*
* @return void
*/
public function testExample()
{
$response = $this->get('/');

$response->assertStatus(200);
}
}

Changez ainsi le code de la méthode :

10/14
public function testIndex()
{
$response = $this->get('welcome');
$response->assertStatus(200);
}

Là encore le test est bon.

Isoler les tests


Nous allons maintenant aborder un aspect important des tests qui ne s’appellent pas
unitaires pour rien.

Pour faire des tests efficaces il faut bien les isoler, donc savoir ce qu’on teste, ne tester
qu’une chose à la fois et ne pas mélanger les choses.

Ceci est possible si le code est bien organisé, ce que je me suis efforcé de vous montrer
depuis le début de ce cours.

Avec PHPUnit chaque test est effectué dans une application spécifique, il n’est donc pas
possible de les rendre dépendants les uns des autres.

En général on utilise Mockery, un composant qui permet de simuler le comportement


d’une classe. Il est déjà prévu dans l’installation de Laravel en mode développement :

"require-dev": {
...,
"mockery/mockery": "^1.4.4",
...
"phpunit/phpunit": "^9.5.10"
},

Le fait de prévoir ce composant uniquement pour le développement simplifie ensuite la


mise en œuvre pour le déploiement. Normalement vous devriez trouver ce composant
dans vos dossiers :

11/14
Simuler une classe

Nous allons voir maintenant comment l’utiliser mais pour cela on va mettre en place le
code à tester. Ce ne sera pas trop réaliste mais c’est juste pour comprendre le
mécanisme de fonctionnement de Mockery. Remplacez le code du contrôleur
WelcomeController par celui-ci :

<?php

namespace App\Http\Controllers;

use App\Services\Livre;

class WelcomeController extends Controller


{
public function __construct()
{
$this->middleware('guest');
}

public function index(Livre $livre)


{
$titre = $livre->getTitle();

return view('welcome', compact('titre'));


}
}

J’ai prévu l’injection d’une classe dans la méthode index. Voilà la classe en question :

<?php

namespace App\Services;

class Livre
{
public function getTitle() {
return 'Titre';
}
}

Bon d’accord ce n’est pas très joli mais c’est juste pour la démonstration…

La difficulté ici réside dans la présence de l’injection d’une classe. Comme on veut isoler
les tests, l’idéal serait de pouvoir simuler cette classe. C’est justement ce que permet de
faire Mockery.

Voici la classe de test que nous allons utiliser :

12/14
<?php
namespace Tests\Feature;
use Tests\TestCase;
use App\Services\Livre;

class WelcomeControllerTest extends TestCase


{
public function testIndex()
{
// Création Mock
$this->mock(Livre::class, function ($mock) {
$mock->shouldReceive('getTitle')->andReturn('Titre');
});

// Action
$response = $this->get('welcome');

// Assertions
$response->assertSuccessful();
$response->assertViewHas('titre', 'Titre');
}
}

Et voici le code à ajouter dans la vue pour faire réaliste :

{{ $titre }}

Si je lance le test àa se passe bien.

Voyons de plus près ce code… On crée un objet Mock en lui demandant de simuler la
classe Livre :

$this->mock(Livre::class, function ($mock) {

Ensuite on définit le comportement que l’on désire pour cet objet :

->shouldReceive('getTitle')->andReturn('Titre');

On lui dit qu’il reçoit (shouldReceive) l’appel de la méthode getTitle et doit retourner
Titre.

Ensuite on fait l’action, ici la requête :

$response = $this->get('welcome');

Pour finir on prévoit deux assertions, une pour vérifier qu’on a une réponse correcte et la
seconde pour vérifier qu’on a bien le titre dans la vue :

$response->assertSuccessful();
$response->assertViewHas('titre', 'Titre');

Vous connaissez maintenant le principe de l’utilisation de Mockery. Il existe de vastes


possibilités avec ce composant.

13/14
Il n’y a pas vraiment de règle quant à la constitution des tests, quant à ce qu’il faut tester
ou pas. L’important est de comprendre comment les faire et de juger ce qui est utile ou
pas selon les circonstances. Une façon efficace d’apprendre à réaliser des tests tout en
comprenant mieux Laravel est de regarder comment ses tests ont été conçus.

En résumé
Laravel utilise PHPUnit pour effectuer les tests unitaires.
En plus des méthodes de PHPUnit on dispose d’helpers pour intégrer les tests
dans une application réalisée avec Laravel.
Le composant Mockery permet de simuler le comportement d’une classe et donc
de bien isoler les tests.

14/14
Cours Laravel 9 – mix
laravel.sillo.org/cours-laravel-9-mix/

26 février 2022

Laravel est un framework PHP consacré à la gestion côté serveur, mais on ne peut pas
créer une application web sans générer du HTML et pour l’accompagner du CSS et du
Javascript. Laravel n’impose rien en la matière, mais il a tendance à fortement influer. On
a vu l’utilisation de Breeze et Jetstream qui imposent quelque chose à ce niveau.

Toujours est-il que Laravel, côté frontend, propose NPM comme installeur. Je vais
expliquer dans cet article comment ça se passe avec comme exemple Breeze mais
évidemment ça restera valable quel que soit le système qu’on a envie de mettre en place
côté client.

On va donc installer Laravel avec Breeze comme on l’a déjà vu précédemment :

composer require laravel/breeze --dev


php artisan breeze:install
php artisan migrate

Arrivé à ce stade, on n’a pas encore généré l’intendance du frontend et c’est justement le
sujet de cet article…

Le CSS avec Laravel Mix


Si vous voulez utiliser un langage de génération de CSS comme Sass (qui connait un
grand succès) ou Less alors vous pouvez opter pour Laravel Mix. Il est vrai que ces
langages présentent de nombreux avantages par rapport au simple CSS :

utilisation de variables
imbrication de code
importations
mixins (groupe de déclaration CSS réutilisable)
héritage
opérateurs…

On peut très bien se contenter de la console pour générer le CSS à partir par exemple
d’un fichier Sass :

sass input.scss output.css

On peut en plus mettre en place un observateur pour régénérer le code.

Mais on peut aussi utiliser Laravel Mix. Sous le capot, il utilise Webpack qui n’est pas si
simple que ça à utiliser directement. Du coup, c’est une façon plus simple d’utiliser
Webpack.

1/5
Installation

Pour utiliser Laravel Mix vous devez disposer de NPM, donc aussi de node.js. Si vous
n’êtes pas trop sûr d’avoir ça sur votre machine entrez ça dans votre console :

node -v
npm -v

Si vous obtenez un numéro de version, c’est que vous en disposez, mais assurez-vous
quand même que ce ne soient pas des versions trop anciennes sinon vous aurez des
soucis !

Si vous n’avez rien alors installez-les en suivant les procédures sur les sites
correspondants.

Dans l’installation de Laravel vous trouvez déjà un fichier package.json à la racine :

Il comporte toutes les déclarations de scripts et de dépendances


nécessaires. Selon vos besoins, vous pourrez adapter tout ça. Pour
le moment on a ce code :

{
"private": true,
"scripts": {
"dev": "npm run development",
"development": "mix",
"watch": "mix watch",
"watch-poll": "mix watch -- --watch-options-poll=1000",
"hot": "mix watch --hot",
"prod": "npm run production",
"production": "mix --production"
},
"devDependencies": {
"@tailwindcss/forms": "^0.4.0",
"alpinejs": "^3.4.2",
"autoprefixer": "^10.4.2",
"axios": "^0.25",
"laravel-mix": "^6.0.6",
"lodash": "^4.17.19",
"postcss": "^8.4.6",
"postcss-import": "^14.0.2",
"tailwindcss": "^3.0.18"
}
}

On voit les dépendances de développement déclarées : tailwindcss, axios, lodash…

On a aussi des scripts pour simplifier l’utilisation.

Pour installer il ne vous reste qu’à entrer :

npm install

2/5
Là vous avez le temps d’aller boire tranquillement un café…

Vous allez avoir la création d’un dossier node_modules contenant toutes les
dépendances :

Utilisation

Maintenant vous pouvez utiliser le fichier webpack.mix.js pour


préciser ce que vous voulez compiler. Par défaut, on a ce code :

const mix = require('laravel-mix');

mix.js('resources/js/app.js',
'public/js').postCss('resources/css/app.css', 'public/css', [
require('postcss-import'),
require('tailwindcss'),
require('autoprefixer'),
]);

On a la méthode postCss (on dispose également de less , sass, et stylus) et on


demande de compiler le fichier resources/css/app.scss dans public/css. On a
effectivement ce fichier ici :

On y trouve l’importation de tailwind :

@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';

Pour lancer le processus en mode développement on entre :

npm run dev

Ce qui a pour effet de créer (en fait ici régénérer parce qu’il existe déjà) le fichier app.css
(et aussi celui du Javascript mais j’en parlerai plus loin) :

Le code CSS est en clair puisqu’on est en mode développement :

html {
line-height: 1.15; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
}

En mode production on utilise :

npm run prod

3/5
Là on obtient un code minifié pour gagner en dimension de fichier, donc en temps de
téléchargement.

Un aspect intéressant est le mode observateur :

npm run watch

Dans ce mode le code est surveillé et la compilation se lance à chaque changement, ce


qui est parfait quand on développe.

Le Javascript avec Laravel Mix


Pour le Javascript on peut aussi utiliser Laravel Mix comme on l’a vu ci-dessus pour le
CSS. D’ailleurs on a dans le fichier webpack.mix.js une compilation de javascript :

mix.js('resources/js/app.js', 'public/js')

On utilise la méthode js pour compiler le fichier resources/js/app.js dans public/js. On a


effectivement ce fichier ici :

Et le résultat compilé va se retrouver ici :

Les commandes sont strictement les mêmes que celles qu’on a vues pour le CSS
puisque les opérations sont groupées.

Voyons de plus près le fichier resources/js/app.js :

require('./bootstrap');
import Alpine from 'alpinejs';
window.Alpine = Alpine;
Alpine.start();

On importe Alpine et on ne fait appel au fichier bootstrap. Voyons son contenu :

window._ = require('lodash');

window.axios = require('axios');

window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

On voit qu’on charge lodash et axios.

Les actions de Laravel Mix


Laravel Mix sait faire bien d’autres choses que compiler du CSS ou du Javascript.

4/5
Par exemple il sait copier des fichiers :

mix.copy('node_modules/foo/bar.css', 'public/css/bar.css');

Copier des dossiers :

mix.copyDirectory('assets/img', 'public/img');

Je vous invite à consulter la documentation officielle pour en apprendre plus.

En résumé
Laravel Mix permet de compiler du CSS.
Laravel Mix permet de compiler du Javascript.
Laravel permet aussi d’effectuer des opérations sur les dossiers et les fichiers.

5/5
Ma première application Laravel 9
laravel.sillo.org/ma-premiere-application-laravel9/

7 mars 2022

Je vous propose dans cet article de voir comment créer une simple application Laravel en
détaillant toutes les étapes (j’avais déjà fait ça pour Laravel 7 alors ça va être une petite
mise à jour). Il s’adresse donc aux débutants qui désirent découvrir ce framework et peut-
être aux moins débutants qui aimeraient se rafraichir un peu les idées !

Évidemment je ne vais pas exposer tous les aspects de Laravel ici mais juste les
éléments essentiels à prendre en compte. Toutefois on arrivera à une application
totalement fonctionnelle.

On va ainsi créer un simple gestionnaire de tâches.

Vous pouvez télécharger le code final de l’article.

Les prérequis
Laravel, qui en est actuellement à sa version 9, a besoin de quelques éléments côté
serveur :

PHP >= 8
des extensions PHP (que du classique) :
Extension PDO,
Extension Mbstring,
Extension OpenSSL,
Extension Tokenizer,
Extension XML.‌,
Extension BCMath,
Extension Ctype,
Extension JSON
Extension Fileinfo
Extension DOM
Extentsion PCRE

Vous pouvez trouver tout ça facilement sur un serveur local comme WAMPP ou XAMPP
mais franchement si vous ne voulez pas vous compliquer la vie utilisez Laragon !

On va aussi utiliser MySQL comme serveur de données.

Vous aurez aussi besoin de Composer pour gérer les librairies PHP.

Enfin pour le frontend il vous faudra node.js.

Vous avez tout ça ? Alors c’est parti !

1/33
Installation de Laravel
Si vous utilisez Laragon l’installation est d’une extrême simplicité :

Il suffit ensuite de préciser le nom :

Et c’est parti ! Vous obtenez :

un nouveau dossier todolist avec une


installation fraîche de Laravel dernière
version
une base de données MySQL du même
nom
et un hôte local todolist.oo (chez moi c’est
oo mais peut-être que pour vous ce sera
une autre extension)

Si vous n’utilisez pas Laragon c’est plus laborieux, dans la console entrez :

composer create-project laravel/laravel todolist

Et évidemment il vous faudra créer manuellement la base. D’autre part vous n’aurez pas
automatiquement un hôte local.

Laravel dispose d’un serveur PHP local léger qu’on démarre en utilisant l’outil Artisan de
Laravel :

php artisan serve

Mais il vaut mieux utiliser un serveur plus complet comme celui proposé par Laragon.

Si tout se passe bien vous devez arriver sur cette page avec todolist.oo (ou l’extension
que vous utilisez) :

2/33
Base de données
Vous avez créé la base de données mais il faut renseigner Laravel pour qu’il s’en serve.
Pour ça on va utiliser le fichier de configuration .env qui se situe à la racine :

Si pour une raison quelconque ce fichier n’existe pas faite une copie
de .env.example pour le créer.

Dans ce fichier on va préciser le nom de la base et les identifiants


pour y accéder :

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=todolist
DB_USERNAME=root
DB_PASSWORD=

Pour l’instant la base est vide. Laravel dispose d’un outil de


migration pour créer des tables, les modifier, créer des index…

On trouve ça dans le dossier database :

3/33
On y trouve 4 fichiers de migration :

pour la table users


pour la table password_resets
pour la table failed_jobs
pour la table personnal_access_tokens

Comme on ne se servira pas des deux dernières on va supprimer ces fichiers. Il ne va


donc plus nous rester que la migration pour la table users et password_resets. Si on
regarde un peu le code pour users on a une fonction up :

public function up()


{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}

C’est ici qu’on a le code pour créer la table et ses colonnes. Je ne vais pas entrer dans le
détail mais le code est explicite.

Pour créer les tables à partir de cette migration on utilise encore Artisan :

Là il peut vous sembler étrange que, bien qu’ayant supprimé la migration pour la table
personnal_access_tokens, elle soit quand même créée. Cela est dû au fait que le
package Laravel\Sanctum (qui sert à facilité l’authentification pour les application SPA)
est installé par défaut et comporte cette migration. Pour corriger ça vous allez intervenir
dans la classe App\Providers\AppServiceProvider :

use Laravel\Sanctum\Sanctum;

...

public function register()


{
Sanctum::ignoreMigrations();
}

On lance une commande pour raffraichir la base :

4/33
Si on regarde dans la base on se retrouve avec 3 tables :

On a bien nos tables users et password_reset mais aussi un table


migrations qui mémorise les actions de migration de Laravel. C’est
une table d’intendance que vous n’avez pas à toucher.

On a dans la table users les colonnes telles que définies dans la


migration :

L’authentification
Laravel n’est pas équipé à la base d’un système d’authentification (c’était le cas avec
d’anciennes versions) mais on peut l’ajouter avec un package complémentaire, c’est ce
qu’on va faire en utilisant Composer :

composer require laravel/breeze --dev

5/33
Composer a ajouté la librairie dans le fichier composer.json :

"require-dev": {
..
"laravel/breeze": "^1.14",
...
},

Et l’a chargée dans le dossier vendor :

On va poursuivre l’installation de ce package :

php artisan breeze:install

On va aussi compiler les assets :

npm install
npm run dev

En cas d’erreur à ce niveau vérifiez que vous utilisez la


dernière version de node.js

On va avoir la création d’un dossier node_modules avec toutes les


dépendances. Ça prend un certain temps parce qu’il y a beaucoup
de dépendances.

6/33
Au niveau de la page d’accueil on se retrouve avec deux liens :

On a ainsi un formulaire pour le login :

Et un pour l’inscription :

Il est d’autre part possible de réinitialiser le mot de passe. Vous pouvez explorer tout ça…

Les langues

7/33
Évidemment à la base tout est en anglais mais Laravel sait très bien gérer l’aspect
linguistique. Vous avez toutes les traductions de base ici.

Vous pouvez avoir une installation automatique en utilisant le Publisher.

Vous installez les package utiles :

composer require laravel-lang/publisher laravel-lang/lang laravel-lang/attributes


--dev

ET ensuite les langues que vous voulez :

php artisan lang:add

Et les fichiers apparaissent comme par magie :

Ensuite vous changez la locale dans config/app.php :

'locale' => 'fr',

Vous aurez ainsi en français les messages pour l’authentification et


la validation. Par exemple pour le login :

Par contre on voit que çe ne change rien sur la page d’accueil :

Toutes les vues (fichiers qui génèrent les pages HTML) de


Laravel sont dans le dossier resources/views :

La package qu’on a installé à ajouté ici les vues du dossier


auth. La vue de l’accueil est welcome.blade.php. On a les deux textes dans ce code :

<a href="{{ route('login') }}" class="text-sm text-gray-700 dark:text-gray-500


underline">Log in</a>

@if (Route::has('register'))
<a href="{{ route('register') }}" class="ml-4 text-sm text-gray-700 dark:text-
gray-500 underline">Register</a>
@endif

8/33
Je peux faire un changement brutal et mettre les textes en
français dans la vue, mais alors mon application ne sera plus
multilangues. Après tout si je veux mon site exclusivement en
français ça ne pose aucun problème. Mais si je veux
conserver la possibilité d’avoir plusieurs langue je dois trouver
une méthode plus élégante…

J’utilise un helper de Blade :

<a href="{{ route('login') }}" class="text-sm text-gray-


700 dark:text-gray-500 underline">@lang('Log in')</a>

@if (Route::has('register'))
<a href="{{ route('register') }}" class="ml-4 text-sm
text-gray-700 dark:text-gray-500
underline">@lang('Register')</a>
@endif

On obtient le même résultat mais maintenant le site est multilingues !

Vous pouvez vous demander où sont les traductions


correspondantes, allez voir dans le fichier lang/fr/fr.json.

Les données
Il est maintenant temps de se poser la question des données nécessaires pour notre
application. On va partir sur cette base, pour chaque tâche on a avoir :

un titre (« Tondre la pelouse »)


un texte de détail (« Ne pas oublier de demander la tondeuse au voisin la veille »)
une date de création
une date de modification
un état (a priori deux états : à faire et fait)

On va donc créer une table pour mémoriser tout ça. On a vu qu’avec Laravel on utilise
une migration. D’autre part Laravel est équipé d’un ORM efficace : Eloquent. Chaque
table est représenté par une classe qui permet de manipuler les données. On dispose
ainsi d’un modèle pour chaque table à manipuler. On va demander à Artisan de créer à la
fois une table et son modèle Eloquent associé :

On trouve le modèle ici :

On a déjà le modèle par défaut User pour gérer les données de la table users.

9/33
La migration a été créée ici :

Par défaut on a ce code :

public function up()


{
Schema::create('tasks',
function (Blueprint $table) {
$table->id();
$table->timestamps();
});
}

On a :

une clé id
deux colonnes (created_at
et updated_at) créée par la
méthode timestamps.

On va ajouter les colonnes nécessaires :

Schema::create('tasks', function (Blueprint $table) {


$table->id();
$table->timestamps();
$table->string('title');
$table->text('detail');
$table->boolean('state')->default(false);
});

Et on lance la migration :

On doit trouver la table dans la base avec ses colonnes :

10/33
Le contrôleur et les routes
Laravel est basé sur le modèle MVC :

Modèle : c’est Eloquent qui se charge de cet aspect et on a créé un modèle (Task)
pour nos tâches,
Vue : on a déjà des vues pour la partie authentification, on a devoir en créer aussi
pour nos tâches,
Contrôleur : c’est le chef d’orchestre de l’application, on va créer à présent un
contrôleur pour gérer toutes les actions nécessaires pour les tâches.

php artisan make:controller TaskController --resource

On trouve ce contrôleur ici :

C’est un contrôleur de ressource, ce qui signifie qu’il est déjà


équipé de 7 fonctions pour les actions suivantes :

Verbe URI Action Route

GET /tasks index tasks.index

GET /tasks/create create tasks.create

POST /tasks store tasks.store

GET /tasks/{task} show tasks.show

GET /tasks/{task}/edit edit tasks.edit

PUT/PATCH /tasks/{task} update tasks.update

DELETE /tasks/{task} destroy tasks.destroy

On va créer aussi les routes pour accéder à ces actions dans le fichier routes/web.php :

use App\Http\Controllers\TaskController;

Route::resource('tasks', TaskController::class)->middleware('auth');

Si vous utilisez la commande php artisan route:list vous obtenez toutes les routes de
l’application et en particulier les 7 pour le contrôleur :

11/33
Maintenant que tout ça est en place on va coder ces actions et créer les vues
correspondantes !

Un petit mot sur le middleware auth que j’ai ajouté pour ces routes. Un middleware est
une étape pour la requête HTTP qui arrive, ça permet d’accomplir des vérifications, ici on
demande que l’accès à ces routes soir limitées aux utilisateurs authentifiés. Ceux qui ne
le sont pas seront automatiquement renvoyés à la page de connexion. Pour la suite de ce
tutoriel vous devrez donc créer un utilisateur avec les formulaires qu’on a déjà mis en
place.

Organisation des vues


Par défaut Breeze utilise Tailwind avec un thème spécifique et toutes les vues de
l’authentification ainsi que le tableau de bord l’utilisent, on peut évidemment changer tout
ça si on n’aime pas Tailwind, ce qui est d’ailleurs mon cas. Pour cet article je vais me
contenter de prendre la situation telle qu’elle est proposée de base pour ne pas alourdir
cet article, mais évidemment on reste totalement libre d’utiliser ce qu’on veut au niveau
du frontend.

Laravel propose plusieurs façon d’organiser les vues, on peut les combiner avec un
simple héritage ou utiliser des composants. Breeze met en place de nombreux
composants et en fait une utilisation intensive, alors voyosn un peu de quoi il s’agit. Si
vous regardez la vue du dashboard (views/dashboard.blade.php) on a ce code :

<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Dashboard') }}
</h2>
</x-slot>

<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
You're logged in!
</div>
</div>
</div>
</div>
</x-app-layout>

La syntaxe un peu particulière x-app-layout indique qu’on utilise un composant, mais


lequel et où le trouver ?

12/33
Regardez ici :

Un composant possède classiquement une vue et une classe


associée. Ici on s’intéresse à la classe AppLayout. Dans cette
classe on a en paticulier ce code :

public function render()


{
return view('layouts.app');
}

On voit qu’on retourne la vue views/layouts/app.blade.php.


Donc quand j’utilise le composant <x-app-layout> je fais en fait
appel à cette vue. Si vous ouvrez ce fichier vous allez trouver le
code HTML de base de la page.

On trouve dans cette vue ce code :

@include('layouts.navigation')

Qui veut dire que l’on inclut ici la vue views/layouts/navigation.blade.php qui
correspond à cette partie de la page :

Parmi les nombreux composants on en trouve un pour un contrôle simple de formulaire :

Le code en est simple :

@props(['disabled' => false])

<input {{ $disabled ? 'disabled' : '' }} {!!


$attributes->merge(['class' => 'rounded-md shadow-sm
border-gray-300 focus:border-indigo-300 focus:ring
focus:ring-indigo-200 focus:ring-opacity-50']) !!}>

Mais l’avantage est de mutualiser ce code et de l’utiliser


pour tous les formulaires. Pour notre usage on va créer
un composant équivalent pour les textarea :

Avec ce code :

@props(['disabled' => false])

<textarea {{ $disabled ? 'disabled' : '' }} {!!


$attributes->merge(['class' => 'rounded-md shadow-
sm border-gray-300 focus:border-indigo-300
focus:ring focus:ring-indigo-200 focus:ring-
opacity-50']) !!}>{{ $slot }}</textarea>

13/33
On va aussi ajouter le composant tasks-card :

Avec ce code :

<div class="mt-8 flex flex-col sm:justify-center


items-center pt-6 sm:pt-0 bg-gray-100">
<div class="w-full sm:max-w-md mt-6 px-6 py-4
bg-white shadow-md overflow-hidden sm:rounded-lg">
{{ $slot }}
</div>
</div>

Je ne vais pas entrer dans tous les détails des vues, vous pouvez les trouver dans la
documentation.

Créer une tâche

Le formulaire

Pour la création d’une tâche on va avoir besoin d’un formulaire. On crée un dossier tasks
et la vue create :

Avec ce code :

14/33
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Create a task') }}
</h2>
</x-slot>

<x-tasks-card>

<!-- Message de réussite -->


@if (session()->has('message'))
<div class="mt-3 mb-4 list-disc list-inside text-sm text-green-600">
{{ session('message') }}
</div>
@endif

<form action="{{ route('tasks.store') }}" method="post">


@csrf

<!-- Titre -->


<div>
<x-input-label for="title" :value="__('Title')" />

<x-text-input id="title" class="block mt-1 w-full" type="text"


name="title" :value="old('title')" required autofocus />

<x-input-error :messages="$errors->get('title')" class="mt-2" />


</div>

<!-- Détail -->


<div class="mt-4">
<x-input-label for="detail" :value="__('Detail')" />

<x-textarea class="block mt-1 w-full" id="detail" name="detail">{{


old('detail') }}</x-textarea>

<x-input-error :messages="$errors->get('detail')" class="mt-2" />


</div>

<div class="flex items-center justify-end mt-4">


<x-button class="ml-3">
{{ __('Send') }}
</x-button>
</div>
</form>

</x-tasks-card>
</x-app-layout>

Il y a beaucoup à dire sur ce code mais je ne vais pas entrerI dans les détails.

Le formulaire est défini avec ce code :

<form action="{{ route('tasks.store') }}" method="post">

15/33
On utilise l’helper route qui, à partir du nom de la route, génère l’url. Pour insérer des
éléments avec PHP on utilise des accolades.

On a ensuite cette commande Blade :

@csrf

On demande ici à Blade d’insérer un token dans un champ caché qui va permettre à
Laravel de contrer les attaques CSRF, le code généré ressemble à ça :

<input type="hidden" name="_token"


value="sUTuW5dYrt2l2iPgAJ5EVXn5UwMjEGFLAZaH4jHz">

C’est automatique et on n’a pas besoin de s’en préoccuper ensuite. Un middleware se


charge à l’arrivée de ce contrôle de sécurité.

On va compléter le code de la fonction create du contrôleur TaskController pour appeler


la vue :

public function create()


{
return view('tasks.create');
}

Maintenant avec l’url todolist.oo/tasks/create on obtient la page avec le formulaire (si


on est connecté !) :

On a avancé mais ça serait quand même mieux en français… On va compléter le fichier


lang/fr.json :

16/33
{
...
"Create a task": "Créer une tâche",
"Title": "Titre",
"Detail": "Détail",
"Send": "Envoyer"
}

Et ça fonctionne :

La soumission

Pour créer effectivement la tâche on va devoir gérer la soumission du formulaire dans la


méthode store du contrôleur :

17/33
use App\Models\Task;

...

public function store(Request $request)


{
$data = $request->validate([
'title' => 'required|max:100',
'detail' => 'required|max:500',
]);

$task = new Task;


$task->title = $request->title;
$task->detail = $request->detail;
$task->save();

return back()->with('message', "La tâche a bien été créée !");


}

On commence par vérifier les entrées avec la validation. Les deux champs sont requis et
on vérifie une longueur maximale.

Ensuite on utilise Eloquent pour créer la tâche.

Pour finir on renvoie dans la vue.

Vous pouvez vérifier que la validation fonctionne :

Si la validation est bonne, la tâche est créée et on retourne la même vue (back) mais
cette fois on flashe une information en session (ce qui signifie qu’elle ne sera valable que
pour la prochaine requête) avec with :

return back()->with('message', "La tâche a bien été créée !");

Dans la vue on vérifie s’il y a une information en session et si c’est le cas on l’affiche :

@if (session()->has('message'))
<div class="mt-3 mb-4 list-disc list-inside text-sm text-green-600">
{{ session('message') }}
</div>
@endif

18/33
On trouve les données dans la table :

Par défaut l’état est à 0 (false). Remarquez aussi le renseignement automatique des
deux dates.

Pour accéder à ce formulaire de création on va jouter un item dans le menu, ça se passe


dans la vue views/layouts/navigation.blade.php :

<!-- Navigation Links -->


<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
<x-nav-link :href="route('dashboard')" :active="request()-
>routeIs('dashboard')">
{{ __('Dashboard') }}
</x-nav-link>
</div>
<!-- Lien pour la création d'une tâche -->
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
<x-nav-link :href="route('tasks.create')" :active="request()-
>routeIs('tasks.create')">
{{ __('Create a task') }}
</x-nav-link>
</div>
</div>

Modifier une tâche

Le formulaire

Pour la modification d’une tâche on va avoir besoin d’un formulaire. On crée la vue edit :

Évidemment cette vue va beaucoup ressembler à celle de la création mais il va falloir


renseigner les champ on va ajouter la possibilité de modifier l’état :

19/33
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800
leading-tight">
{{ __('Edit a task') }}
</h2>
</x-slot>

<x-tasks-card>

<!-- Message de réussite -->


@if (session()->has('message'))
<div class="mt-3 mb-4 list-disc list-inside
text-sm text-green-600">
{{ session('message') }}
</div>
@endif

<form action="{{ route('tasks.update', $task->id)


}}" method="post">
@csrf
@method('put')

<!-- Titre -->


<div>
<x-input-label for="title"
:value="__('Title')" />

<x-text-input id="title" class="block mt-1


w-full" type="text" name="title" :value="old('title',
$task->title)" required autofocus />

<x-input-error :messages="$errors-
>get('title')" class="mt-2" />
</div>

<!-- Détail -->


<div class="mt-4">
<x-input-label for="detail"
:value="__('Detail')" />

<x-textarea class="block mt-1 w-full"


id="detail" name="detail">{{ old('detail', $task->detail)
}}</x-textarea>

<x-input-error :messages="$errors-
>get('detail')" class="mt-2" />
</div>

<!-- Tâche accomplie -->


<div class="block mt-4">
<label for="state" class="inline-flex
items-center">
<input id="state" type="checkbox"
class="rounded border-gray-300 text-indigo-600 shadow-sm
focus:border-indigo-300 focus:ring focus:ring-indigo-200

20/33
focus:ring-opacity-50" name="state" @if(old('state',
$task->state)) checked @endif>
<span class="ml-2 text-sm text-gray-
600">{{ __('Task done') }}</span>
</label>
</div>

<div class="flex items-center justify-end mt-


4">
<x-button class="ml-3">
{{ __('Send') }}
</x-button>
</div>
</form>

</x-tasks-card>
</x-app-layout>

Dans la déclaration du formulaire on a changé l’url :

<form action="{{ route('tasks.update', $task->id) }}" method="post">

On précise la route et il faut ajouter l’identifiant de la tâche à modifier ($task->id).


remarquez que la route attend une méthode PUT mais que là on déclare une méthode
POST. C’est parce que les navigateurs gèrent encore très mal ce genre de méthode
alors on déclare un POST mais après on précise qu’on veut un PUT :

@method('put')

Ca crée un champ caché qui va servir à Laravel pour savoir de quelle méthode il s’agit :

<input type="hidden" name="_method" value="put">

Pour le reste ça ne change pas beaucoup de la création sauf la case à cocher ajoutée.

Dans le contrôleur on appelle la vue :

public function edit(Task $task)


{
return view('tasks.edit', compact('task'));
}

Remarquez le paramètre qui est du type du modèle Task. C’est une façon de dire à
Laravel : le paramètre transmis est un nombre mais en fait c’est l’identifiant d’une
instance de Task. Du coup Eloquent peut aller chercher dans la table la tâche
correspondante. Il suffit ensuite de transmettre ça à la vue.

Maintenant avec une url de la forme todolist.oo/tasks/1/edit on atteint le formulaire.

La soumission

Pour modifier effectivement la tâche on va devoir gérer la soumission du formulaire dans


la méthode update du contrôleur :

21/33
public function update(Request $request, Task $task)
{
$data = $request->validate([
'title' => 'required|max:100',
'detail' => 'required|max:500',
]);

$task->title = $request->title;
$task->detail = $request->detail;
$task->state = $request->has('state');
$task->save();

return back()->with('message', "La tâche a bien été modifiée !");


}

On a la même validation que pour la création. D’ailleurs il faudrait pour bien faire
mutualiser ce code pour éviter cette redondance qui n’est jamais une bonne chose
(principe DRY). Laravel dispose d’une stratégie élégante pour la validation avec les Form
Request, vous pouvez trouver les détails dans la documentation.

On met à jour les valeurs de la tâche. Pour la case à cocher il faut savoir qu’on ne
retrouve une valeur que si elle est cochée. Donc on va vérifier si la valeur existe pour
mettre à jour dans la table.

On va aussi compléter les traductions :

"Task done": "Tâche accomplie"


}

Voir une tâche


On va partir du principe qu’on aura un tableau avec juste le titre des tâches et pas le
détail. Il faut donc prévoir de pouvoir afficher chaque tâche.

22/33
On crée la vue :

<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800
leading-tight">
@lang('Show a task')
</h2>
</x-slot>

<x-tasks-card>
<h3 class="font-semibold text-xl text-gray-
800">@lang('Title')</h3>
<p>{{ $task->title }}</p>
<h3 class="font-semibold text-xl text-gray-800 pt-
2">@lang('Detail')</h3>
<p>{{ $task->detail }}</p>
<h3 class="font-semibold text-xl text-gray-800 pt-
2">@lang('State')</h3>
<p>
@if($task->state)
La tâche a été accomplie !
@else
La tâche n'a pas encore été accomplie.
@endif
</p>
<h3 class="font-semibold text-xl text-gray-800 pt-
2">@lang('Date creation')</h3>
<p>{{ $task->created_at->format('d/m/Y') }}</p>
@if($task->created_at != $task->updated_at)
<h3 class="font-semibold text-xl text-gray-800 pt-
2">@lang('Last update')</h3>
<p>{{ $task->updated_at->format('d/m/Y') }}</p>
@endif
</x-tasks-card>
</x-app-layout>

On complète le contrôleur :

public function show(Task $task)


{
return view('tasks.show', compact('task'));
}

Et avec une url de la forme todolist.oo/tasks/1 on affiche les éléments d’une tâche :

23/33
On a encore bessoin d’ajouter quelques traductions :

"Task done": "Tâche accomplie",


"Show a task": "Voir une tâche",
"State": "Etat",
"Date creation": "Date de création",
"Last update": "Dernière modification"
}

24/33
On affiche la date de mise à jour que si elle est différente de celle de la création.

Liste des tâches


Maintenant qu’on sait créer, modifier et afficher des tâches on va voir comment afficher
en afficher la liste en prévoyant des boutons pour les différentes actions.

Au niveau du contrôleur on va récupérer toutes les tâches et les envoyer dans une vue :

public function index()


{
$tasks = Task::all();

return view('tasks.index', compact('tasks'));


}

Comme il n’y aura pas de très nombreuses tâches on ne prévoie pas de pagination mais
Laravel sait très bien s’occuper de ça également.

On va ajouter un composant pour les boutons dans la liste :

Avec ce code :

25/33
<td class="px-2 py-2">
<a role="button" {{ $attributes->merge(['class'
=> 'inline-flex items-center px-2 py-1 bg-gray-800
border border-transparent rounded-md font-semibold
text-xs text-white uppercase tracking-widest
hover:bg-gray-700 active:bg-gray-900 focus:outline-
none focus:border-gray-900 focus:ring ring-gray-300
disabled:opacity-25 transition ease-in-out duration-
150']) }}>
{{ $slot }}
</a>
</td>

On crée la vue :

26/33
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
@lang('Tasks List')
</h2>
</x-slot>
<div class="container flex justify-center mx-auto">
<div class="flex flex-col">
<div class="w-full">
<div class="border-b border-gray-200 shadow pt-6">
<table>
<thead class="bg-gray-50">
<tr>
<th class="px-2 py-2 text-xs text-gray-500">#</th>
<th class="px-2 py-2 text-xs text-gray-500">@lang('Title')
</th>
<th class="px-2 py-2 text-xs text-gray-500">Etat</th>
<th class="px-2 py-2 text-xs text-gray-500"></th>
<th class="px-2 py-2 text-xs text-gray-500"></th>
<th class="px-2 py-2 text-xs text-gray-500"></th>
</tr>
</thead>
<tbody class="bg-white">
@foreach($tasks as $task)
<tr class="whitespace-nowrap">
<td class="px-4 py-4 text-sm text-gray-500">{{ $task->id
}}</td>
<td class="px-4 py-4">{{ $task->title }}</td>
<td class="px-4 py-4">@if($task->state) {{ __('Done') }}
@else {{ __('To do') }} @endif</td>
<x-link-button href="{{ route('tasks.show', $task->id)
}}">
@lang('Show')
</x-link-button>
<x-link-button href="{{ route('tasks.edit', $task->id)
}}">
@lang('edit')
</x-link-button>
<x-link-button onclick="event.preventDefault();
document.getElementById('destroy{{ $task->id }}').submit();">
@lang('Delete')
</x-link-button>
<form id="destroy{{ $task->id }}" action="{{
route('tasks.destroy', $task->id) }}" method="POST" style="display: none;">
@csrf
@method('DELETE')
</form>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</x-app-layout>

27/33
On se retrouve avec un tableau des tâches :

On va ajouter quelques traductions :

"Done": "Effectuée",
"Tasks List": "Liste des tâches",
"To do": "A faire",
"Show": "Voir",
"Edit": "Editer"
}

On ajoute un item dans le menu (navigation.blade.php) :

<!-- Lien pour la liste des tâches -->


<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
<x-nav-link :href="route('tasks.index')" :active="request()-
>routeIs('tasks.index')">
{{ __('Tasks list') }}
</x-nav-link>
</div>
</div>

Les boutons permettent d’accéder aux tâches qu’on a codées précédemment sauf la
suppression. On va coder pour cela cette fonction dans le contrôleur :

28/33
public function destroy(Task $task)
{
$task->delete();
}

Je n’ai pas prévu de boîte de dialogue d’avertissement avant la suppression mais ça


serait à ajouter dans une application réelle. Pour cette suppression j’ai prévu un
formulaire caché pour chaque tâche et un peu de javascript pour la soumission. Ce n’est
évidemment pas la seule façon de faire mais ça ne concerne pas directement Laravel.

Les tests
Laravel permet de faire facilement de tests. Il utilise PHPUnit et on trouve par défaut le
fichier de configuration phpunit.xml à la racine. Par défaut la base de donnée est celle
définie dans le fichier .env. Vous pouvez utiliser sqlite en mémoire en décommentant ces
lignes pour éviter d’impacter votre base de données avec les tests :

<server name="DB_CONNECTION" value="sqlite"/>


<server name="DB_DATABASE" value=":memory:"/>

Les fichiers de test se trouvent dans le dossier tests :

Lancez les tests :

29/33
On voit qu’il y a déjà de nombreux tests. La plupart ont été ajoutés par Breeze et
concernent l’authentification. Voyons comment sont constitués ces tests en analysant la
classe AuthenticationTest :

class AuthenticationTest extends TestCase


{
use RefreshDatabase;

public function test_login_screen_can_be_rendered()


{
$response = $this->get('/login');

$response->assertStatus(200);
}
...

On commence par vider la base de données avec RefreshDatabase.

Ensuite on envoie une requête HTTP GET sur l’url /login. Le test consiste à vérifier
(assertStatus) qu’on a bien une réponse 200.

30/33
Ca correspond à cette ligne dans les tests :

Remarquez l’importance du libellé de la méthode


(test_login_screen_can_be_rendered) pour obtenir un text explicite dans le test (login
screen can be rendered).

Voyons la seconde méthode :

public function test_users_can_authenticate_using_the_login_screen()


{
$user = User::factory()->create();

$response = $this->post('/login', [
'email' => $user->email,
'password' => 'password',
]);

$this->assertAuthenticated();
$response->assertRedirect(RouteServiceProvider::HOME);
}

Là on veut vérifier qu’un utilisateur peut effectivement se connecter. On commence donc


par en créer un :

$user = User::factory()->create();

Ensuite on envoie un requête POST sur l’url /login en précisant les identifiant de
l’utilisateur qu’on vient de créer :

$response = $this->post('/login', [
'email' => $user->email,
'password' => 'password',
]);

On vérifie ensuite qu’il est bien authentifié :

$this->assertAuthenticated();

Laravel propose de nombreuse assertions, vous les trouvez ici.

On vérifie enfin qu’on a la bonne redirection :

$response->assertRedirect(RouteServiceProvider::HOME);

On retrouve ce test ici :

31/33
Maintenant qu’on a vu le principe des tests on a va en créer un pour notre application,
par exemple la création d’une tâche. On crée d’abord la classe de test :

php artisan make:test CreateTaskTest

On va prévoir cette fonction dans la classe :

...

use App\Models\User;

class CreateTaskTest extends TestCase


{
use RefreshDatabase;

public function test_auth_can_create_task()


{
$user = User::factory(\App\User::class)-
>create();

$response = $this->actingAs($user)-
>post('/tasks', [
'title' => 'Ma nouvelle tâche',
'detail' => 'Tous les détails de ma nouvelle
tâche',
]);

$this->assertDatabaseHas('tasks', [
'title' => 'Ma nouvelle tâche'
]);

$this->get('/tasks')->assertSee('Ma nouvelle
tâche');
}
}

On fait les tests suivants :

un utilisateur authentifié soumet le


formulaire
la tâche est bien dans la table
on trouve la tâche dans la liste des tâches

On peut tester ainsi tous les aspects de l’application, je vous renvoie à la documentation
détaillée pour tous les détails.

Conclusion
J’espère que ce petit exemple pourra donner envie de découvrir ce framework. Il est à la
fois très chargé pour un débutant et frustrant pour quelqu’un de plus avancé mais son
seul objectif est de permettre la découverte de Laravel au travers d’un exemple léger
mais réaliste.

32/33
33/33

Vous aimerez peut-être aussi