URL: https://linuxfr.org/news/laravel-8-est-sorti Title: Laravel 8 est sorti Authors: windu.2b yPhil, Benoît Sibaud, yal et palm123 Date: 2020-12-30T14:58:23+01:00 License: CC By-SA Tags: php et laravel Score: 4 Laravel 8, la dernière version du framework PHP est sortie le 8 septembre 2020. ---- ---- [Laravel](https://fr.wikipedia.org/wiki/Laravel) est un framework PHP, qui existe depuis 2011 ([il a fêté ses 10 ans en juin](https://laravel-news.com/laravel-is-10-years-old)). [Parfois comparé](https://www.grafikart.fr/tutoriels/laravel-symfony-866) à [Symfony](https://fr.wikipedia.org/wiki/Symfony) (qui date de 2005), avec qui [il partage certaines briques de code](https://symfony.com/projects/laravel) tout en se voulant plus facile à configurer, il n'a cependant pas à rougir de ses possibilités. Que ce soit en interne, ou _via_ les nombreuses extensions existantes. Cette nouvelle version continue le travail commencé avec la version 7, en enrichissant le framework de nouvelles possibilités et améliorations. Calendrier de sortie -------------------- À l'origine, cette dépêche devait parler de Laravel 9.0 LTS, prévue initialement pour mars 2021. Mais les développeurs du framework ont entre-temps jugé que Laravel était désormais suffisamment stable et abouti pour ne plus avoir besoin d'une sortie majeure tous les 6 mois. [Le rythme est donc désormais d'une sortie majeure tous les 12 mois, et une sortie mineure toutes les semaines](https://blog.laravel.com/updates-to-laravels-versioning-policy). Tout ceci reportait donc Laravel 9 à septembre 2021, entraînant la parution de certaines nouvelles fonctionnalités dans des versions mineures de Laravel 8, et la prolongation de la durée du support de Laravel 6 LTS. (cf. [le calendrier des versions officiellement supportées](https://laravelversions.com/)) Il faudra finalement attendre janvier 2022, afin que cette nouvelle version LTS puisse reposer sur [Symfony 6.0, lui-même prévu pour novembre 2021](https://symfony.com/releases/6.0). Le calendrier des sorties suivantes (Laravel 10 et 11) est décalé en conséquence. Models Directory ---------------- Jusqu'à présent, les classes (héritant de) `Models` étaient rangées directement dans le répertoire _app_, contrairement à toutes les autres classes (`Controllers`, `Requests`, `Providers`, `Notifications`, `Rules`, ...) qui ont toutes leur (sous-)répertoire attribué. Désormais, le répertoire _app/Models_ existe. À noter que la commande `php artisan make:model XXX` (qui permet de générer un squelette de fichier PHP pour la classe-modèle _XXX_) tient compte de l'existence (ou non) de ce répertoire. Bien pratique si on veut conserver l'arborescence d'un projet déjà existant, sans avoir à déplacer les classes dans ce nouveau répertoire, au risque d'oublier de modifier le _namespace_, ou les imports de classes. Model Factory Classes --------------------- Dans Laravel, les _factories_ sont des générateurs d'objets (des instances de `Models`) avec des données de tests. Le but étant de pouvoir créer des objets reposant sur les classes-métiers du projet, afin de les utiliser au sein des tests unitaires. Désormais, les factories se basent sur une classe `Factory`, et suivent une structure beaucoup plus orientée POO. ```php // Factory en Laravel 6 LTS $factory->define(User::class, function (Faker $faker) { return [ 'firstname' => $faker->name, 'lastname' => $faker->name, 'email' => $faker->unique()->safeEmail, 'email_verified_at' => now(), 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 'remember_token' => Str::random(10), ]; }); ``` ```php // Factory en Laravel 8 class UserFactory extends Factory { /** * The name of the factory's corresponding model. * * @var string */ protected $model = User::class; /** * Define the model's default state. * * @return array */ public function definition() { return [ 'firstname' => $this->faker->name(), 'lastname' => $this->faker->name(), 'email' => $this->faker->unique()->safeEmail(), 'email_verified_at' => now(), 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 'remember_token' => Str::random(10), ]; } } ``` Laravel Jetstream ----------------- Jetstream est un kit de démarrage regroupant les fonctionnalités communes à tout nouveau projet : register/login, vérification d'email et authentification en deux étapes, gestion des sessions. Jetstream vient en remplacement de l'ancien système de register/login (templates HTML et contrôleurs) proposé par Laravel : `laravel-ui`. Migration Squashing ----------- Laravel propose, de base, un mécanisme de migration des schémas de bases de données, permettant de créer (ou de mettre à jour) les tables. Plutôt que d'écrire des requêtes SQL, dont la grammaire peut dépendre du SGBDR utilisé, ce mécanisme repose intégralement sur des classes et méthodes fournies par le framework Laravel. Charge à lui ensuite d'appeler la bonne implémentation, en fonction du SGBDR destinataire (MySQL, PostgreSQL, SQLite et SQL Server sont les seuls officiellement supportés, actuellement). Chaque migration se compose d'un fichier (dont le nom est horodaté, ce qui permet de définir leur ordre d'exécution) indiquant les modifications à effectuer sur la base de données. Exemple : ```php class CreateUsersTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('users', function (Blueprint $table) { $table->increments('id'); $table->unsignedInteger('gender_id') ->default(1); $table->string('firstname'); $table->string('lastname'); $table->string('email')->unique(); $table->timestamp('email_verified_at') ->nullable(); $table->string('password'); $table->timestamps(); // Ajoute les champs 'created_at' et 'updated_at' $table->softDeletes(); // Ajoute le champ 'deleted_at' // On ajoute une FK vers la table 'genders', qui doit avoir été créée dans un script précédent $table->foreign('gender_id') ->references('id') ->on('genders'); }); } /** * Reverse the migrations. * * @return void */ public function down() { if (Schema::hasTable('users')) { // On supprime la FK, avant de supprimer la table Schema::table('users', function (Blueprint $table) { $table->dropForeign('users_gender_id_foreign'); }); Schema::drop('users'); } } } ``` Ces scripts de migration sont donc l'équivalent d'un `versioning` de la structure de la base de données. Laravel permet d'ailleurs de revenir en arrière, grâce à la méthode `down` (avec le risque, évidemment, d'une perte de données si cette méthode supprime des champs/tables dans lesquels des données auront été insérées). Avec la nouvelle commande `php artisan schema:dump`, il est désormais possible de générer un seul (gros) fichier au format SQL, correspondant à la structure de la base de données. Désormais, si un tel fichier existe, Laravel l'exécutera en premier, puis exécutera les scripts de migration créés après (toujours en se basant sur l'horodatage dans les noms des fichiers). Utile lorsqu'on a un projet comptant plusieurs centaines de scripts de migration, et dont le temps d'exécution peut devenir rédhibitoire (ce qui peut vite avoir un impact non négligeable sur le temps d'exécution des tests, si la base de données est recréée avant chacun d'entre eux). À noter que cette commande ne fonctionne pour l'instant qu'avec MySQL et PostgreSQL. ### Sole() La méthode `sole()`, apparue dans [Laravel 8.23](https://laravel-news.com/laravel-8-23-0), permet de renvoyer le seul et unique objet correspondant au(x) critère(s). Si aucun ou plusieurs objets correspondent, une exception est levée. Cela est utile dans le cas où on est sûr de n'obtenir qu'un seul objet, quand on filtre une collection suivant un critère (ex : les informations d'une facture quand son statut est `INITIALIZED`). Avant cela, il fallait forcément filtrer à nouveau la collection, pour n'obtenir que le 1er élément, quand bien même il n'y en avait qu'un. Exemple : ```php // Laravel 6 LTS $book = Book::where('title', 'like', '%War%')->first(); // Il faut préciser qu'on veut le premier élément, sinon on obtient une liste (de 1 élément, potentiellement) // Laravel 8 $book = Book::where('title', 'like', '%War%')->sole(); // Renvoie forcément un seul élément, ou lève une exception ``` Autres nouveautés apparues dans les versions mineures ----------- La liste ci-dessous n'a pas vocation à être exhaustive, mais à montrer que le cycle désormais annuel n'a pas empêché l'apparition de nouvelles fonctionnalités. Elles sont listées dans l'ordre de leur apparition. ### Anonymous Migrations Comme dit précédemment, Laravel a un mécanisme de migration de la structure de la base de données, reposant sur des scripts horodatés. Jusqu'à présent, chaque script est une classe (héritant de la classe `Migration` fournie par Laravel) contenant les modifications à effectuer. Problème : cette classe doit avoir un nom unique (car tous les scripts de migration sont dans le même `namespace`). Ce qui n'est pas toujours pratique, quand le nombre de scripts est de plusieurs centaines. [Depuis Laravel 8.37](https://laravel-news.com/laravel-anonymous-migrations), il est possible d'avoir une classe anonyme. Exemple : ```php string('first_name')->nullable(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('people', function (Blueprint $table) { $table->dropColumn('first_name'); }); } }; ``` ### Vérification que le mot de passe n'a pas fuité [Laravel 8.39](https://laravel-news.com/password-validation-rule-object-in-laravel-8) a ajouté une nouvelle règle de validation (appelées `validation rules`) : `uncompromised()`. Elle permet de vérifier que le mot de passe saisi par l'utilisateur (lors de son inscription, par ex.) ne fait pas partie des mots de passe ayant fuité, suite au piratage de sites Web à grand public. Cette vérification repose sur [l'API du site HaveIBeenPwned.com](https://haveibeenpwned.com/API/v2) Pour rappel, les `validation rules` sont [un ensemble de règles de validation de champs de formulaires](https://laravel.com/docs/8.x/validation#available-validation-rules). [Laravel en fournit un grand nombre](https://laravel.com/docs/8.x/validation#available-validation-rules) (`required`, `exists`, `min`, `string`, ...), mais il est tout à fait possible d'en créer de nouvelles. ### One of Many Laravel permet le mapping ORM grâce à des méthodes (`hasOne`, `belongsToMany`, ...) dont héritent tous les modèles et qui permettent de renvoyer le ou les objets qui leur sont liés. Il suffit pour cela d'écrire une méthode dans la classe-modèle concernée. Exemple : ```php hasOne(Phone::class); } } ``` Par défaut, Laravel devine les clés primaires et étrangères, ainsi que les tables concernées. Mais on peut ajouter des paramètres à cette méthode, si les noms des champs ne respectent pas la structure par défaut de Laravel. Depuis [Laravel 8.42](https://laravel-news.com/one-of-many-eloquent-relationship), il est désormais possible de transformer une relation `one-to-many` en `one-to-one`, et ainsi de ne renvoyer qu'un élément. C'est notamment utile pour récupérer la première/dernière fois qu'un utilisateur s'est connecté, ou le prix actuel (le dernier enregistré en base) d'un produit. Exemple : ```php /** * Renvoie le dernier ordre passé par ce User. */ public function latestOrder() { return $this->hasOne(Order::class)->latestOfMany(); } ``` ### Parallel testing Le report de Laravel 9 a entraîné la parution, en "avance" d'une amélioration très attendue : [la possibilité de pouvoir enfin exécuter les tests en parallèle !](https://blog.laravel.com/laravel-parallel-testing-is-now-available) Pour générer et exécuter des tests, Laravel utilise la librairie [PHPUnit](https://phpunit.de/), enrichie d'un ensemble de classes et méthodes permettant de tester des fonctionnalités propres à Laravel (exécution des scripts de migration pour construire et remplir la base de données avant d'exécuter les tests, appels d'URL pour des tests _features_ de bout en bout, tests des retours JSON, ...). Pour pouvoir exécuter les tests en parallèle, il fallait jusque-là passer par la librairie [Paratest](https://github.com/paratestphp/paratest). Désormais, Laravel intègre directement une solution basée sur Paratest. ### Disable Lazy-loading Grâce à Eloquent (son ORM intégré), Laravel permet facilement d'accéder aux relations d'un objet. Par exemple : ```php // UserController.php $user = User::find(1); // topics.blade.php
{{ $comment->body }}
@endforeach @endforeach ``` Ce mécanisme, certes très pratique, a toutefois un inconvénient : mal utilisé, il provoque le problème dit "N+1 requêtes" (_N+1 queries problem_). En effet, pour que l'exemple ci-dessus s'exécute, il faut 1 requête pour récupérer l'objet `$user`, puis 1 requête pour chaque objet `$topic`. Et comme on le voit dans l'exemple, il est tout à fait possible d'invoquer les relations d'une relation, et ce sans limite. Ce qui augmente d'autant le nombre de requêtes SQL ! Pour éviter cela, il est possible, dès la récupération de l'objet `$user`, de demander à ce que soient en même temps chargées ses relations (et leurs relations, et...). Le début de l'exemple ci-dessus devient alors : ```php // UserController.php $user = User::where('id', 1)->with('topics', 'topics.comments')->first(); ``` Le nombre de requêtes chute alors, puisqu'il en faut toujours une seule pour charger `$user` (ça ne change pas), mais aussi une seule pour charger tous les `topics` appartenant à `$user`, et une autre pour charger tous les `comments` de tous les `topics` appartenant à `$user`. On passe donc de M*N + 1 requêtes, à 3 ! Problème résolu, alors ? Pas tout à fait. Si la méthode `with()` résout bien le problème, encore faut-il l'utiliser ! Et c'est là qu'intervient [la nouveauté en question](https://laravel-news.com/disable-eloquent-lazy-loading-during-development), consistant à désactiver le lazy-loading, pour forcer Laravel à lever une exception, et ainsi détecter les endroits dans le code ne recourant pas encore à la méthode `with()`. À noter que ce mécanisme de désactivation du lazy-loading est global. C'est donc la totalité du projet qui est susceptible de lever des exceptions. Il est donc préférable de faire ça en local d'abord (mais si vous êtes du genre à débugguer en production, allez-y !) Et maintenant ? --------------- ### Laravel 9 LTS Comme dit plus haut, Laravel 9 LTS a été reporté. Mais des premières infos sur ce que contiendra cette version ont déjà été annoncées : - Support de PHP 8.0 minimum ; - Utilisation de Symfony 6.0, pour les briques logicielles qui en proviennent ; - `Anonymous Stub Migrations` : désormais, ce comportement sera par défaut (cf. plus haut, pour plus d'infos sur ce qu'est cette fonctionnalité) ; - ... ### Laracon 2021 Laracon, la conférence annuelle autour de Laravel, [aura lieu le 1er septembre 2021](https://laracon.net/).