Académique Documents
Professionnel Documents
Culture Documents
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.
(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.
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 :
MVC ? POO ?
MVC
4/6
C’est un modèle d’organisation du code :
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
un serveur,
PHP,
MySql,
Node,
Composer…
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.
Donc en résumé :
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.
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"
}
{
"identité1" : {
"nom": "Durand",
"prénom": "Jean"
},
"identité2" : {
"nom": "Dupont",
"prénom": "Albert"
}
}
"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 ».
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.
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.
Le serveur
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 :
Prérequis
1/11
même pour les adeptes de Windows. Pour trouver la console sur ce système il faut
chercher l’invite de commande :
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 :
2/11
Pour les mises à jour ultérieures il suffit encore d’utiliser Composer avec la commande
update :
3/11
composer update
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.
<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews -Indexes
</IfModule>
RewriteEngine On
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.
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
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
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.
Route::post('/', function () {
return view('welcome');
});
Ouvrez l’url de base, vous obtenez une page d’erreur avec en particulier cette information
:
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 :
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.
Petit rappels
On va commencer par un petit rappel sur ce qu’est une requête HTTP. Voici un schéma
illustratif :
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.
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
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
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
Pour que ça fonctionne il faut que le serveur Apache ait le module mod_rewrite activé.
Le cycle de la requête
Route::get('/', function () {
return view('welcome');
});
Comme Laravel est explicite vous pouvez déjà deviner à quoi sert ce code :
3/8
dans la fonction anonyme on retourne (return) une vue (view) à partir du fichier
« welcome ».
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.
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).
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 :
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 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 . ' !';
});
On dit que la route est paramétrée parce qu’elle possède un paramètre qui peut prendre
n’importe quelle valeur.
Route::get('{n?}', function($n = 1) {
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 :
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')
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.
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.
Route::get('{n}', function($n) {
return 'Je suis la page ' . $n . ' !';
});
Route::get('contact', function() {
return "C'est moi le contact.";
});
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.
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.
Route::get('test', function () {
return 'un test';
});
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 :
Route::get('test', function () {
return ['un', 'deux', 'trois'];
});
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 :
2/9
403 : ressource interdite,
404 : la ressource demandée n’a pas été
trouvée,
503 : serveur indisponible.
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.
Route::get('/', function() {
return view('vue1');
});
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 ».
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]+');
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>
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);
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 :
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 :
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.
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
@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 :
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');
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');
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');
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();
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.
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.
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…
php artisan
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 :
Route::get('/', function () {
return view('welcome');
});
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.
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 :
Avec ce code :
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
3/7
<?php
...
class WelcomeController extends Controller
{
public function index()
{
return view('welcome');
}
}
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;
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 :
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');
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).
<?php
namespace App\Http\Controllers;
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;
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.
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 :
use App\Http\Controllers\UsersController;
Jusque-là on avait vu seulement des routes avec le verbe get, on a maintenant aussi une
route avec le verbe 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.
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 :
2/8
de gérer une session,
de gérer la protection CSRF (dont je parle plus loin dans ce chapitre)…
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,
],
];
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 :
$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>
@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
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 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 :
4/8
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
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
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.
@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
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 :
@extends('errors::minimal')
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')
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
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 :
Le contrôleur
On va encore utiliser Artisan pour générer le contrôleur :
2/12
<?php
namespace App\Http\Controllers;
Routes
use App\Http\Controllers\ContactController;
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 :
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
/**
* Get the validation rules that apply to the
request.
*
* @return array
*/
public function rules()
{
return [
//
];
}
}
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.
4/12
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
/**
* 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 :
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 !
5/12
<?php
namespace App\Http\Controllers;
use App\Http\Requests\ContactRequest;
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
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.
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 :
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.
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
10/12
Ce qui donne cette apparence :
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
return view('confirm');
}
}
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;
if ($validator->fails()) {
return back()->withErrors($validator)->withInput();
}
return view('confirm');
}
}
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ûtset 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}"
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 :
Comme le dossier n’existe pas il est créé en même temps que le fichier de la classe :
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\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.
3/10
return $this->view('emails.contact');
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.
4/10
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\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;
return view('confirm');
}
}
Mail::to('administrateur@chezmoi.com')
->send(new Contact($request->except('_token')));
On a :
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 !'
]);
});
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 :
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
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 :
<?php
return [
'paths' => [
resource_path('views'),
],
...
];
config('view.paths');
On utilise le nom du fichier (view) et le nom de la clé (paths) séparés par un point.
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 :
<?php
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.
SESSION_DRIVER=file
SESSION_LIFETIME=120
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 :
$valeur = $request->session()->get('clé');
2/10
$valeur = session('clef');
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.
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.
Et si j’utilise ce code :
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 :
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.
'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.
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 :
5/10
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
/**
* 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'];
}
}
use App\Http\Controllers\PhotoController;
6/10
<?php
namespace App\Http\Controllers;
use App\Http\Requests\ImagesRequest;
return view('photo_ok');
}
}
config('images.path')
$request->image->store(config('images.path'), 'public')
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>
@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
8/10
Remarquez comment est créé le formulaire :
enctype="multipart/form-data"
@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
9/10
On retrouve normalement le fichier bien rangé dans le dossier prévu :
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 :
1/8
public function store(ImagesRequest $request)
{
$request->image->store(config('images.path'), 'public');
return view('photo_ok');
}
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
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;
return view('photo_ok');
}
}
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 :
<?php
namespace App\Repositories;
use Illuminate\Http\UploadedFile;
class PhotosRepository
{
public function save(UploadedFile $image)
{
$image->store(config('images.path'), 'public');
}
}
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);
}
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;
return view('photo_ok');
}
}
Le souci c’est que Laravel n’arrive pas à deviner la classe à instancier à partir de cette
interface :
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.
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.
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';
}
}
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 :
J’obtiens le même résultat que si j’écris en allant chercher le routeur dans le conteneur :
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=
2/15
Pour le moment cette table est vide, elle va se remplir au fil des
migrations pour les garder en mémoire.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('users');
}
};
3/15
down : ici on a le code de suppression de la table
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) :
4/15
Les méthodes down des migrations sont exécutées et les tables sont supprimées.
Pour éviter d’avoir à coder la méthode down on a la commande fresh qui supprime
automatiquement les tables concernées :
5/15
Avec ce code de base :
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
};
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:
6/15
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
On a un appel commenté pour remplir la table users. Décommentez cette ligne et lancez
la population avec cette commande :
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 :
Pour ceux qui ont travaillé avec les versions anciennes de Laravel on a
désormais un dossier Models par défaut.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
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 :
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
8/15
On va créer ces deux méthodes :
Les routes
Il nous faut deux routes :
use App\Http\Controllers\ContactsController;
Le modèle et la migration
9/15
Dans le code généré pour la migration on va ajouter deux colonnes (message et email) :
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
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>
@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 :
L’enregistrement
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 :
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 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 !
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
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.
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 :
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.
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
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.
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()
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()
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.
User::where('name', 'Dupont')->get()
On récupère une collection qui ne contient que le modèle qui correspond à Dupont.
On a maintenant 3 utilisateurs :
5/8
Maintenant entrez cette expression :
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.
6/8
Ici on ne retourne que les noms.
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 :
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éé :
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
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 :
On a les champs :
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
:
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 :
3/18
use App\Models\Film;
...
Une ressource
Le contrôleur
On va maintenant créer un contrôleur de ressource avec Artisan :
C’est la commande qu’on a déjà vue pour créer un contrôleur avec en plus l’option –
resource.
Avec ce code :
4/18
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
/**
* 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 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);
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 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
7/18
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
/**
* 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'],
];
}
}
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
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 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;
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
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);
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 :
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
</nav>
@endif
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
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 :
La 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.
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.
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.
La vue show
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();
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">
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
Le contrôleur
Dans le contrôleur ce sont les méthodes create et store qui sont concernées. On va
donc les coder :
...
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
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',
],
];
6/15
Modifier un film
Les routes
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'));
}
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 :
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>
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
Le modèle
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
Les routes
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');
});
Le contrôleur
Dans le contrôleur il faut déjà modifier la méthode index pour inclure les films de la
corbeille :
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.
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 :
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 :
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.
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.
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 :
C’est pour cette raison que j’ai ajouté cette ligne dans la migration :
Schema::disableForeignKeyConstraints();
La population
On va remplir les tables avec des enregistrements pour nos essais.
Les catégories
On complète le code :
3/13
use Illuminate\Support\Str;
...
La relation
On a la situation suivante :
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 :
Le modèle Category
Le modèle Category se trouve ici (on l’a créé en même temps que la migration) :
4/13
public function films()
{
return $this->hasMany(Film::class);
}
Le modèle Film
Dans le modèle Film on va coder la réciproque :
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.
$films = App\Models\Category::find(1)->films;
$category = App\Models\Film::find(1)->category;
La population (seeding)
Pour la population on va justement utiliser la relation qu’on vient de mettre en place. On
modifie ainsi DatabaseSeeder :
...
On lance la population :
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.
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…
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 :
...
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>
select, .is-info {
margin: 0.3em;
}
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 :
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 :
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 :
10/13
On ajoute le champ category_id dans la propriété $fillable du modèle Film :
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.
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'));
}
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 :
12/13
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\View;
use App\Models\Category;
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.
Les données
La relation n:n
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 :
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 :
Par convention on met les deux noms au singulier et dans l’ordre alphabétique donc
category_film.
$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 :
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 :
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
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 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 :
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 :
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
7/11
Il y a quand même une chose qui me dérange. On a deux accès à la base :
Ç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;
...
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');
...
});
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>
array:5 [▼
"_token" =>
"qqARcLpGc6YDJ4jRpzazHeDbPlrxMSnSZYbtO9hJ"
"cats" => array:2 [▼
0 => "1"
1 => "3"
]
"title" => "La vie"
"year" => "1952"
"description" => "Un film d'un terrible ennui."
]
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.
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 :
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.
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 !
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 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).
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 :
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.
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 :
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;
La méthode morphs est bien pratique parce qu’elle crée automatiquement les deux
colonnes pour la relation polymorphique.
Avec ce code :
Schema::create('actors',
function (Blueprint $table) {
$table->id();
$table->string('name')-
>unique();
$table->string('slug')-
>unique();
$table->timestamps();
});
Si tout se passe bien on doit avoir les 4 tables qui nous intéressent :
6/17
Les relations
Category
Actor
Film
La population
7/17
On va remplir les tables pour nos essais.
Avec ce code :
<?php
namespace Database\Factories;
use App\Models\Actor;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
8/17
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\{ Film, Category, Actor };
use Illuminate\Support\Str;
$categories = [
'Comédie',
'Drame',
'Action',
'Fantastique',
'Horreur',
'Animation',
'Espionnage',
'Guerre',
'Policier',
'Pornographique',
];
foreach($categories as $category) {
Category::create(['name' => $category, 'slug' =>
Str::slug($category)]);
}
Et on lance la population :
Cette fois j’ai prévu des noms de catégories réalistes et non plus 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) :
...
$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 :
if($slug) {
if(Route::currentRouteName() == 'films.category') {
$model = new Category;
} else {
$model = new Actor;
}
}
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>
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) :
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>
Dans le contrôleur on prévoit l’attachement pour les acteurs en plus des catégories :
$film->categories()->attach($filmRequest->cats);
$film->actors()->attach($filmRequest->acts);
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 :
$film->categories()->sync($filmRequest->cats);
$film->actors()->sync($filmRequest->acts);
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.
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.'
}
...
]
GET /api/films/2
{
id: 2,
name: 'Sint incidunt consequatur.'
}
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 :
2/11
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
/**
* 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)
{
//
}
}
4/11
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Film;
/**
* 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();
}
}
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);
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,
],
];
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 :
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.
On pourrait aussi imaginer utiliser une pagination pour cette API. Il suffit de changer la
méthode index :
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.
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.
Avec ce code :
9/11
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
...
use Illuminate\Support\Str;
...
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 :
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.
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.
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 :
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
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
...
];
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
});
});
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 :
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,
...
];
Une fois que le package est installé vous pouvez lancer cette commande :
C’est avec cette commande que vont être créés les routes, vues, controlleurs…
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';
5/12
On a aussi la création de nombreux contrôleurs :
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
De cette manière elle est facile à modifier si vous devez changer des règles ou ajouter un
champ.
7/12
Je rappelle qu’on peut ajouter la langue française avec ce complément.
La création de l’utilisateur
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
...
}
protected $fillable = [
'name', 'email', 'password',
];
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 :
9/12
Il se trouve que cette colonne est prévue dans la migration de base pour la table.
<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>
$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;
// ...
}
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');
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 :
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 60,
'throttle' => 60,
],
],
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 :
2/6
On demande l’adresse email à l’utilisateur. La soumission correspond à cette route :
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.
4/6
Dans ce formulaire, on retrouve le jeton (token) dans un contrôle caché :
$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
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é.
Ç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.
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 :
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 :
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
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.
Remarquez au passage la déclaration du middleware guest dans les routes de Fortify (il
faut aller voir ça dans le dossier vendor) :
La vue register
On va donc chercher cette vue :
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 :
...
}
De cette manière elle est facile à modifier si vous devez changer des règles ou ajouter un
champ.
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 :
return User::create([
'name' => $input['name'],
'email' => $input['email'],
'password' => Hash::make($input['password']),
]);
}
protected $fillable = [
'name', 'email', 'password',
];
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 :
Remarquez aussi la déclaration du middleware guest dans les routes de Fortify (il faut
aller voir ça dans le dossier vendor) :
La vue login
On va donc chercher cette vue :
7/12
Cette vue utilise aussi le composant resources/views/layouts/guest.blade.php.
La validation
Il se trouve que cette colonne est prévue dans la migration de base pour la table.
8/12
La redirection
9/12
<!-- Authentication -->
<form method="POST" action="{{ route('logout') }}">
@csrf
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 :
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 :
10/12
(new Password)->length(10)
// Au moins un chiffre
(new Password)->requireNumeric()
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,
]),
],
11/12
Lorqu’il utilise le lien d’activation il est automatiquement connecté.
Changer la langue
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
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) :
accéder au profil
se déconnecter
Le profil
Quand on accède au profil, c’est assez fourni.
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;
...
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.
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…
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.
'features' => [
// Features::termsAndPrivacyPolicy(),
Features::profilePhotos(),
Features::api(),
// Features::teams(['invitations' => true]),
Features::accountDeletion(),
],
6/8
La commande apparaît alors dans le menu :
Quand on crée un token une fenêtre s’ouvre et il faut copier le token qui n’apparaitra que
là :
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;
...
$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
Installation
Pour l’installation on retrouve exactement ce qu’on a vu. En partant d’un Laravel 8 tout
neuf on installe Jetstream :
npm install
npm run dev
php artisan migrate
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.
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 :
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 :
5/8
Si l’email n’existe pas dans la base, on se contente d’envoyer une invitation :
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…
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.');
}
Jetstream::role('writer', 'Author', [
'create',
'update',
])->description('Author users have the ability to create, and update.');
use Laravel\Jetstream\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
}
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.
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();
return redirect()->route('home');
}
}
Route::middleware('admin')->group(function () {
...
}
Le throttle
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.
Route::middleware(['throttle:uploads'])->group(function () {
Route::post('/articles', function () {
//
});
...
});
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.
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.
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
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.
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 :
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
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)
{
//
}
}
7/8
use App\User;
...
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 :
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
Laravel permet de gérer avec facilité les événements comme on va le voir dans ce
chapitre.
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.
Organisation du code
Laravel implémente ce design pattern et le rend très simple d’utilisation. On va avoir :
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
…
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à.
Créez une base, renseignez correctement le fichier .env. Installez Breeze et lancez les
migrations :
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;
/**
* 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).
<?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)
{
//
}
}
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 :
Illuminate\Auth\Events\Login {#399 ▼
+guard: "web"
+user: App\Models\User {#1370 ▶}
+remember: false
}
On peut donc ici effectuer tous les traitements qu’on veut, comme comptabiliser les
connexions.
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;
...
/**
* Determine if events and listeners should be automatically discovered.
*
* @return bool
*/
public function shouldDiscoverEvents()
{
return true;
}
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.
8/11
Et on lance la migration :
On crée l’événement :
<?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');
}
}
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()
{
//
}
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 :
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.
Le principe
Clés et valeurs
On a déjà parlé des fichiers de langage. Lorsque Laravel est installé on a cette
architecture :
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.
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' => '« Previous',
'next' => 'Next »',
];
return [
'previous' => '« Précédent',
'next' => 'Suivant »',
];
On retrouve bien sûr les mêmes clés avec des valeurs adaptées au langage.
Les textes
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.
On a :
Configuration de la locale
2/7
La configuration de la locale est effectuée dans le fichier config/app.php :
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 :
$locale = App::getLocale();
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 :
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é :
Blade
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 :
On utilise dans le code la fonction trans_choice. Voici un exemple tiré d’une application :
{{ trans_choice(__('comment|comments'), $post->valid_comments_count) }}
Sinon on a le pluriel :
Des assistants
Pour gérer toutes ces traductions, il existe des assistants bien utiles.
4/7
Il copie les fichiers de langue dans la base de données et permet une gestion à partir
d’une interface web.
5/7
language:sync pour synchroniser une locale JSON avec les textes du code
n voit mal présenter en langue française « Le champ name est obligatoire… ». Alors
O
comment faire ?
'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.
...
}
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.
Date : {{ \Carbon\Carbon::now()->isoFormat('LL') }}
Et en français :
Date : {{ \Carbon\Carbon::now()->calendar() }}
Et en français :
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 :
1/8
Par défaut la classe de notification se situe dans le framework :
2/8
<?php
namespace Illuminate\Auth\Notifications;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Lang;
/**
* 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 :
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 :
<?php
namespace Illuminate\Auth\Passwords;
use Illuminate\Auth\Notifications\ResetPassword as
ResetPasswordNotification;
trait CanResetPassword
{
...
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 :
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;
/**
* 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 [
//
];
}
}
use App\Notifications\RegisteredNotification;
...
$user->notify(new RegisteredNotification());
...
}
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 :
@lang('Hello!')
Avec ce code :
{
"Hello!": "Bonjour !"
}
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=
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 :
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 :
@extends('template')
<main class="section">
<div class="container">
@yield('content')
</div>
</main>
@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 :
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
<tbody>
@include('partials.lines')
</tbody>
Et évidemment on ne conserve dans la vue partielle que le code inclus dans la boucle :
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 :
@if(session()->has('info'))
<div class="notification is-success">
{{ session('info') }}
</div>
@endif
Et d’une vue :
<?php
namespace App\View\Components;
use Illuminate\View\Component;
public $text;
Et pour la vue :
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.
@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 :
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().
@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.
Condition personnalisée
6/9
On peut aussi se construire une condition personnalisée. On pourrait prévoir ce code
dans AppServiceProvider:
Blade::if('admin', function () {
return auth()->user()->role === 'admin';
});
Blade::if('redac', function () {
return auth()->user()->role === 'redac';
});
@admin
<li>
<a href="{{ url('admin') }}">@lang('Administration')</a>
</li>
@endadmin
@redac
<li>
<a href="{{ url('admin/posts') }}">@lang('Administration')</a>
</li>
@endredac
@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 :
Propriété Description
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 :
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.
PHPUnit
Laravel utilise PHPUnit pour effectuer les tests. C’est un framework créé par Sebastian
Bergmann qui fonctionne à partir d’assertions.
"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 vendor\phpunit\phpunit\phpunit -h
L’intendance de Laravel
Si vous regardez les dossiers de Laravel vous allez en trouver un qui est consacré aux
tests :
<?php
namespace Tests;
use Illuminate\Foundation\Testing\TestCase as
BaseTestCase;
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).
Toutes les classes de test que vous allez créer devront étendre cette classe TestCase.
2/14
<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
<?php
namespace Tests\Feature;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
$response->assertStatus(200);
}
}
Pourquoi 2 dossiers ?
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.
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 peut évidemment ajouter les variables dont on a besoin. Par exemple si pendant les
tests je ne veux plus MySql mais sqlite.
DB_CONNECTION=mysql
5/14
<env name="DB_CONNECTION" value="sqlite"/>
Construire un test
Comme tout ça est un peu abstrait prenons un exemple. Remplacez le code avec celui-ci
(peu importe dans quel dossier) :
Supprimez le test dans l’autre dossier pour éviter de polluer les résultats.
$result = array_sum($data);
On teste le résultat :
$this->assertEquals(60, $result);
6/14
Vous connaissez maintenant le principe de base d’un test et ce qu’on peut obtenir
comme renseignement en cas d’échec.
Les assertions
Voici quelques assertions et l’utilisation d’un helper de Laravel que l’on teste au passage :
use Illuminate\Support\Str;
...
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) :
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.
Les vues
Route::get('/', function () {
return view('welcome')->with('message', 'Vous y êtes !');
});
{{ $message }}
8/14
public function testBasicTest()
{
$response = $this->get('/');
$response->assertViewHas('message', 'Vous y êtes !');
}
Les contrôleurs
Créez ce contrôleur :
9/14
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\WelcomeController;
Vérifiez que ça fonctionne (vous aurez peut-être besoin de retoucher la vue où nous
avons introduit une variable).
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
$response->assertStatus(200);
}
}
10/14
public function testIndex()
{
$response = $this->get('welcome');
$response->assertStatus(200);
}
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.
"require-dev": {
...,
"mockery/mockery": "^1.4.4",
...
"phpunit/phpunit": "^9.5.10"
},
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;
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.
12/14
<?php
namespace Tests\Feature;
use Tests\TestCase;
use App\Services\Livre;
// Action
$response = $this->get('welcome');
// Assertions
$response->assertSuccessful();
$response->assertViewHas('titre', 'Titre');
}
}
{{ $titre }}
Voyons de plus près ce code… On crée un objet Mock en lui demandant de simuler la
classe Livre :
->shouldReceive('getTitle')->andReturn('Titre');
On lui dit qu’il reçoit (shouldReceive) l’appel de la méthode getTitle et doit retourner
Titre.
$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');
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.
Arrivé à ce stade, on n’a pas encore généré l’intendance du frontend et c’est justement le
sujet de cet article…
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 :
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.
{
"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"
}
}
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
mix.js('resources/js/app.js',
'public/js').postCss('resources/css/app.css', 'public/css', [
require('postcss-import'),
require('tailwindcss'),
require('autoprefixer'),
]);
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
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) :
html {
line-height: 1.15; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
}
3/5
Là on obtient un code minifié pour gagner en dimension de fichier, donc en temps de
téléchargement.
mix.js('resources/js/app.js', 'public/js')
Les commandes sont strictement les mêmes que celles qu’on a vues pour le CSS
puisque les opérations sont groupées.
require('./bootstrap');
import Alpine from 'alpinejs';
window.Alpine = Alpine;
Alpine.start();
window._ = require('lodash');
window.axios = require('axios');
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
4/5
Par exemple il sait copier des fichiers :
mix.copy('node_modules/foo/bar.css', 'public/css/bar.css');
mix.copyDirectory('assets/img', 'public/img');
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.
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 !
Vous aurez aussi besoin de Composer pour gérer les librairies PHP.
1/33
Installation de Laravel
Si vous utilisez Laragon l’installation est d’une extrême simplicité :
Si vous n’utilisez pas Laragon c’est plus laborieux, dans la console entrez :
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 :
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.
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=todolist
DB_USERNAME=root
DB_PASSWORD=
3/33
On y trouve 4 fichiers de migration :
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;
...
4/33
Si on regarde dans la base on se retrouve avec 3 tables :
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 :
5/33
Composer a ajouté la librairie dans le fichier composer.json :
"require-dev": {
..
"laravel/breeze": "^1.14",
...
},
npm install
npm run dev
6/33
Au niveau de la page d’accueil on se retrouve avec deux liens :
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.
@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…
@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
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 :
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 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 :
On a :
une clé id
deux colonnes (created_at
et updated_at) créée par la
méthode timestamps.
Et on lance la migration :
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.
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.
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>
12/33
Regardez ici :
@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 :
Avec ce code :
13/33
On va aussi ajouter le composant tasks-card :
Avec ce code :
Je ne vais pas entrer dans tous les détails des vues, vous pouvez les trouver dans la
documentation.
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>
</x-tasks-card>
</x-app-layout>
Il y a beaucoup à dire sur ce code mais je ne vais pas entrerI dans les détails.
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.
@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 :
16/33
{
...
"Create a task": "Créer une tâche",
"Title": "Titre",
"Detail": "Détail",
"Send": "Envoyer"
}
Et ça fonctionne :
La soumission
17/33
use App\Models\Task;
...
On commence par vérifier les entrées avec la validation. Les deux champs sont requis et
on vérifie une longueur maximale.
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 :
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.
Le formulaire
Pour la modification d’une tâche on va avoir besoin d’un formulaire. On crée la vue edit :
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>
<x-input-error :messages="$errors-
>get('title')" class="mt-2" />
</div>
<x-input-error :messages="$errors-
>get('detail')" class="mt-2" />
</div>
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>
</x-tasks-card>
</x-app-layout>
@method('put')
Ca crée un champ caché qui va servir à Laravel pour savoir de quelle méthode il s’agit :
Pour le reste ça ne change pas beaucoup de la création sauf la case à cocher ajoutée.
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.
La soumission
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();
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.
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 :
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 :
24/33
On affiche la date de mise à jour que si elle est différente de celle de la création.
Au niveau du contrôleur on va récupérer toutes les tâches et les envoyer dans une vue :
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.
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 :
"Done": "Effectuée",
"Tasks List": "Liste des tâches",
"To do": "A faire",
"Show": "Voir",
"Edit": "Editer"
}
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();
}
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 :
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 :
$response->assertStatus(200);
}
...
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 :
$response = $this->post('/login', [
'email' => $user->email,
'password' => 'password',
]);
$this->assertAuthenticated();
$response->assertRedirect(RouteServiceProvider::HOME);
}
$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',
]);
$this->assertAuthenticated();
$response->assertRedirect(RouteServiceProvider::HOME);
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 :
...
use App\Models\User;
$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 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