Laravel 8 est sorti

Posté par  . Édité par yPhil, Benoît Sibaud, yal et palm123. Modéré par Pierre Jarillon. Licence CC By‑SA.
Étiquettes :
28
14
août
2021
PHP

Laravel 8, la dernière version du framework PHP est sortie le 8 septembre 2020.

Sommaire

Laravel est un framework PHP, qui existe depuis 2011 (il a fêté ses 10 ans en juin). Parfois comparé à Symfony (qui date de 2005), avec qui il partage certaines briques de code 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.

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)

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

    // 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),
        ];
    });
    // 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 :

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

    // 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, il est possible d'avoir une classe anonyme.

Exemple :

<?php

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

return new class extends Migration {
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('people', function (Blueprint $table) {
            $table->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 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

Pour rappel, les validation rules sont un ensemble de règles de validation de champs de formulaires.
Laravel en fournit un grand nombre (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

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Un utilisateur peut avoir un téléphone (de type `Phone`) associé
     */
    public function phone()
    {
        return $this->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, 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 :

/**
 * 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 !
Pour générer et exécuter des tests, Laravel utilise la librairie PHPUnit, 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. 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 :

// UserController.php
$user = User::find(1);

// topics.blade.php
<h2>Topics</h2>
@foreach($user->topics as $topic)
    <h3>{{ $topic->title }}</h3>
    @foreach($topic->comments as $comment)
        <p>
            {{ $comment->body }}
        </p>
    @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 :

// 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, 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.

  • # Génial le chapeau

    Posté par  . Évalué à 1.

    J'hésite à en dire plus…

    • [^] # Re: Génial le chapeau

      Posté par  . Évalué à 3.

      Étant l'auteur de cette dépêche, j'aimerais bien que tu en dises plus, justement…

      • [^] # Re: Génial le chapeau

        Posté par  . Évalué à 3.

        Je crois que le chapeau (que l'on voit dans liste des articles) et un peu bref, il pourrait indiquer un résumé de quelques éléments majeurs de l'article afin d'avoir une idée de ce qu'on va y lire et peut être avoir l'information concise des changements majeurs ? En l'état, ça ressemble à un Twitt, un texte vide de sens.

        C'est dommage(able) parce que l'article est bien écrit et contient plein d'affirmation pertinentes. Au moins on ne peut pas accuser le chapeau d'être un texte trop aguicheur. Merci en tout cas pour toutes ces informations.

      • [^] # Re: Génial le chapeau

        Posté par  (site Web personnel) . Évalué à 4.

        En fait, on aurait pu faire remonter les deux premiers paragraphes au-dessus de Calendrier de sortie dans l'intro. C'est ce que j'aurais fait, mais je n'ai pas touché à cette dépêche en modération :-)

        « Tak ne veut pas quʼon pense à lui, il veut quʼon pense », Terry Pratchett, Déraillé.

      • [^] # Re: Génial le chapeau

        Posté par  . Évalué à 3. Dernière modification le 15/08/21 à 14:35.

        tao popus a très bien expliqué. Ma remarque c'était pour rire, à l'intention de l'équipe de modération qui aurait pu remonter les 2 premiers paragraphes en chapeau pour introduire Laravel et rendre la dépêche plus visible — vu le travail qu'elle représente, et puis c'est dommage qu'une dépêche de qualité ne saute pas aux yeux. Désolé si ça t'a blessé je n'y ai pas pensé sur le coup.

        • [^] # Re: Génial le chapeau

          Posté par  . Évalué à 4.

          Merci pour l'explication, pertinente.
          Je n'ai pas été blessé : je trouvais juste que ton commentaire n'en disait pas assez (comme mon chapeau, donc).

        • [^] # Re: Génial le chapeau

          Posté par  (site Web personnel) . Évalué à 5.

          tao popus a très bien expliqué

          Ysabeau a hésité, je comprends tout a fait. Il y a peu, un article présenté en modération avait un chapeau à rallonges d'une longueur excessive. J'avais réorganisé l'article en en mettant une grosse partie en 2e partie.
          L'auteur était furieux et Ysabeau a remis la grosse image dans le chapeau qui est devenu 2 fois trop long au lieu de 3 fois !

          Quand on rédige un article, une paire de ciseaux apparait dans la marge. Ce n'est qu'une indication de la taille maximale recommandée. Devrions nous aussi mettre une limite minimale ?

          • [^] # Re: Génial le chapeau

            Posté par  . Évalué à 3.

            Quand on rédige un article, une paire de ciseaux apparait dans la marge. Ce n'est qu'une indication de la taille maximale recommandée

            Oh mais je n'avais jamais fait gaffe !!! Merci pour cette info !

  • # Vérification que le mot de passe n'a pas fuité

    Posté par  . Évalué à 6.

    Je me demandais ce qu'envoie le serveur à l'API https://haveibeenpwned.com/API/v2
    Je suppose qu'il n'envoie pas le mot de passe en clair mais en empreinte surement SHA1.

    En fait, c'est mieux que çà. Haveibeenpwned met à disposition une requête avec les 5 premier caractères SHA1. Il retourne alors la liste des hash commençant par ces 5 caractères. Il ne reçoive donc pas l'empreinte complète.

    https://haveibeenpwned.com/API/v2#SearchingPwnedPasswordsByRange

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.