Skip to main content
📝 Applications Laravel

Laravel 12.51 : 3 Fonctionnalités qui ont Changé ma Façon de Coder

Laravel 12.51 apporte trois fonctionnalités qui ont changé ma façon de construire des apps. Corrections de performance, améliorations de requêtes et la mise à jour à ne pas manquer.

22 min

Temps de lecture

4,317

Mots

Mar 01, 2026

Publié

Engr Mejba Ahmed

Écrit par

Engr Mejba Ahmed

Partager l'article

Laravel 12.51 : 3 Fonctionnalités qui ont Changé ma Façon de Coder

Laravel 12.51 : 3 Fonctionnalités Qui Ont Changé Ma Façon de Développer

Quatre secondes. C'est le temps que prenait le chargement d'une seule page d'administration sur un projet client que je gérais l'année dernière.

Le coupable n'était pas un index manquant. Ce n'était pas une requête N+1. C'était un appel à firstOrCreate — du code Laravel parfaitement banal, sans rien de remarquable — où le tableau d'attributs de création incluait un appel API vers un service de facturation tiers et une opération de hachage. Les deux étaient évalués à chaque requête. Même quand l'utilisateur existait déjà. Même quand rien n'était créé.

J'ai trouvé le bug après presque une heure d'appels dd() et de défilement lent dans la timeline du Laravel Debugbar. Quand j'ai enfin compris, j'ai ressenti ce mélange spécifique de soulagement et de gêne qui ne survient que lorsqu'on réalise que le bug était invisible non pas parce qu'il était astucieux — mais parce qu'on ne comprenait pas comment l'évaluation des arguments en PHP fonctionne réellement.

Le correctif que j'ai écrit à l'époque était un contournement : vérifier si l'enregistrement existait d'abord, puis appeler la méthode appropriée en fonction du résultat. Ça marchait. C'était lourd. Et à chaque fois que je tombais sur ce pattern dans d'autres projets, je pensais "il doit y avoir une façon plus propre."

Laravel 12.51 intègre cette façon plus propre nativement. Et ce n'est qu'une des trois nouveautés de cette version qui m'ont fait interrompre ce que je faisais pour lire le changelog deux fois.

Les deux autres — une méthode native de timeout pour les requêtes et des callbacks chaînables sur le validateur — répondent à des problèmes que je résous depuis des années avec du code personnalisé et des solutions de contournement. L'une concerne les performances qui dégradent silencieusement les applications en production. L'autre concerne la qualité du code qui dégrade silencieusement les équipes. Les deux comptent.

Voici ce qui a réellement changé, comment l'utiliser, et — parce que la plupart des changelogs omettent cette partie — quand chaque fonctionnalité vaut votre temps et quand elle ne le vaut pas.


Le Problème Caché Dans Vos Appels firstOrCreate

Avant d'expliquer le correctif, vous devez comprendre pourquoi le bug existe. Parce que si vous êtes comme la plupart des développeurs Laravel, vous écrivez ce code depuis des années et il n'a jamais rien cassé de manière évidente.

Le problème fondamental : PHP évalue tous les arguments d'une fonction avant de l'exécuter.

Cette phrase paraît simple. Ses implications ne le sont pas.

Quand vous écrivez ceci :

$user = User::firstOrCreate(
    ['email' => $email],
    [
        'name' => $name,
        'avatar' => $this->avatarService->generate($email),   // API call
        'api_key' => $this->keyService->issue($email),         // another API call
        'hash' => bcrypt(Str::random(64)),                     // CPU-bound
    ]
);

PHP construit le deuxième tableau en entier — en appelant generate(), issue() et bcrypt() — avant même que firstOrCreate ne démarre. Ensuite, firstOrCreate exécute sa requête. Si l'utilisateur existe, il retourne cet utilisateur et jette le tableau calculé. Tous les calculs que vous venez d'exécuter ? Gaspillés.

En développement, ça ne fait pas mal. Les bases de données de test ont peu d'enregistrements. Les appels API atteignent des sandboxes qui répondent en millisecondes. Vous exécutez le code, ça marche, vous déployez.

En production, cela devient un impôt que vous payez sur chaque requête qui touche un enregistrement existant — ce qui, dans une application mature, représente la quasi-totalité des requêtes. Si votre service d'avatars prend 300ms et votre service de clés prend 200ms, vous ajoutez 500ms de pur gaspillage à ces requêtes. Pour toujours. Jusqu'à ce que quelqu'un trace l'endpoint lent et découvre pourquoi.

Le correctif introduit par Laravel 12.51 est élégant : passer le deuxième argument sous forme de closure.

$user = User::firstOrCreate(
    ['email' => $email],
    function () use ($name, $email) {
        return [
            'name' => $name,
            'avatar' => $this->avatarService->generate($email),
            'api_key' => $this->keyService->issue($email),
            'hash' => bcrypt(Str::random(64)),
        ];
    }
);

L'implémentation de Laravel 12.51 vérifie si le deuxième argument est un callable. Si c'est le cas, le closure ne s'exécute que lorsque l'enregistrement n'existe pas. Les enregistrements existants sont retournés immédiatement. Le travail de création n'est jamais exécuté.

C'est de l'évaluation paresseuse (lazy evaluation) — un pattern présent dans la boîte à outils de PHP depuis des années via les closures et les generators, mais qui n'était pas disponible dans les méthodes de création d'enregistrements du query builder jusqu'à maintenant.

La méthode createOrFirst bénéficie du même traitement. Cette méthode fonctionne dans l'ordre inverse : elle tente d'abord une insertion puis fait un fallback vers un select en cas de violation de contrainte unique. La même logique d'évaluation paresseuse s'applique. Passez un closure, et les attributs ne sont calculés que si une insertion est réellement nécessaire.

Quand cela compte réellement pour votre code

Soyons clairs : si vos attributs de création sont des valeurs statiques — chaînes, entiers, flags booléens — vous n'avez pas besoin du closure. PHP les évalue en microsecondes. L'optimisation n'a de sens que lorsque le calcul est coûteux. Posez-vous la question : est-ce que quelque chose dans ce deuxième tableau effectue un appel réseau, exécute une opération cryptographique, interroge la base de données, ou exécute du code qui prend plus de quelques millisecondes ?

Si oui, encapsulez-le. Chaque appel firstOrCreate et createOrFirst avec une logique de création coûteuse est un candidat.

Trouvez-les dans votre projet maintenant :

grep -rn "firstOrCreate\|createOrFirst" app/ --include="*.php"

Ouvrez chaque fichier. Regardez le deuxième argument. S'il fait du travail réel, vous payez l'impôt de l'évaluation anticipée.

Les chiffres de mes propres tests — en utilisant un sleep artificiel de deux secondes pour simuler deux appels API — étaient sans appel : la recherche d'enregistrements existants est passée de 2 003ms à 8ms. La création de nouveaux enregistrements est restée à 2 003ms car le travail est réellement nécessaire. C'est le comportement souhaité. Ne calculer que lorsque c'est indispensable.

Mais c'est ici que les choses deviennent intéressantes, et c'est là que la plupart des articles sur cette fonctionnalité s'arrêtent avant la partie importante.


Tuer les Requêtes Longues Avant Qu'elles Ne Tuent Votre Base de Données

La deuxième fonctionnalité est opérationnellement différente de l'évaluation paresseuse. L'évaluation paresseuse vise à éviter le travail inutile. Le timeout de requête vise à limiter le travail nécessaire qui a dérapé.

Si vous avez travaillé avec Laravel sur de grosses tables MySQL — je parle de centaines de milliers à des millions de lignes — vous avez probablement observé ce schéma : une requête qui fonctionnait parfaitement il y a six mois prend maintenant 30, 40, 60 secondes. La table a grossi. Aucun nouvel index n'a été ajouté. Un rapport analytique qui s'exécutait en moins d'une seconde maintient désormais une connexion base de données ouverte assez longtemps pour que plusieurs requêtes HTTP expirent.

MySQL a une solution pour ça depuis des années : le hint d'optimisation MAX_EXECUTION_TIME. Vous pouvez l'intégrer directement dans une instruction SELECT pour définir un plafond en millisecondes sur la durée de la requête. Si la requête dépasse ce plafond, MySQL la termine et retourne une erreur au lieu de la laisser s'exécuter indéfiniment.

Le problème était que l'utiliser dans Laravel nécessitait de sortir du query builder :

// Option 1: Raw SQL. Breaks the fluent interface.
$results = DB::select('SELECT /*+ MAX_EXECUTION_TIME(5000) */ * FROM orders WHERE status = "pending"');

// Option 2: Session-level setting. Affects more than just this query.
DB::statement('SET SESSION MAX_EXECUTION_TIME = 5000');
$orders = Order::where('status', 'pending')->get();
DB::statement('SET SESSION MAX_EXECUTION_TIME = 0'); // must reset or everything is capped

// Option 3: Custom macro. Works, but requires maintenance and project-specific setup.
Builder::macro('maxExecutionTime', function (int $ms) {
    return $this->beforeQuery(function ($q) use ($ms) {
        $q->addSelectExpression("/*+ MAX_EXECUTION_TIME($ms) */");
    });
});

J'utilisais l'Option 3 sur plusieurs projets. Ça fonctionne, mais vous traînez du code personnalisé que les nouveaux membres de l'équipe ne connaissent pas, qui n'apparaît pas dans l'autocomplétion de l'IDE, et que vous devez penser à ajouter à chaque nouveau projet Laravel.

Laravel 12.51 intègre cela nativement :

$orders = Order::where('status', 'pending')
    ->timeout(5)
    ->get();

La méthode timeout() accepte des secondes (pas des millisecondes — notez la différence avec le hint raw MySQL, qui utilise des millisecondes). En coulisses, Laravel convertit et injecte le hint d'optimisation dans la requête. Si la limite est atteinte, vous obtenez une QueryException avec le message de requête interrompue de MySQL.

Voici le pattern prêt pour la production pour encapsuler les requêtes avec timeout :

use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\Log;

public function generateSalesReport(array $filters): array
{
    try {
        $results = Order::query()
            ->where('created_at', '>=', $filters['start_date'])
            ->where('created_at', '<=', $filters['end_date'])
            ->with(['items', 'customer', 'discounts'])
            ->when($filters['status'] ?? null, fn($q, $s) => $q->where('status', $s))
            ->timeout(15)
            ->get();

        return $this->formatReport($results);

    } catch (QueryException $e) {
        if (str_contains($e->getMessage(), 'Query execution was interrupted')) {
            Log::warning('Sales report query timed out', [
                'filters' => $filters,
                'timeout_seconds' => 15,
            ]);

            // Graceful degradation: return cached result or a reduced dataset
            return $this->getCachedReport($filters) ?? [];
        }

        throw $e; // Re-throw unexpected database errors
    }
}

Ce pattern — timeout plus gestion explicite des exceptions — est ce qui sépare "j'ai ajouté un timeout" de "j'ai correctement géré le timeout." Le timeout seul ne fait que remplacer un blocage infini par une exception. La gestion de l'exception est là où vous protégez réellement vos utilisateurs.

La partie que la plupart des tutoriels omettent : c'est réservé à MySQL

Le comportement de la méthode timeout() dépend du driver. Pour MySQL et MariaDB, elle utilise le hint d'optimisation MAX_EXECUTION_TIME. Pour PostgreSQL, l'implémentation est différente — PostgreSQL utilise des timeouts au niveau instruction configurés différemment, et le comportement entre drivers peut ne pas être celui que vous attendez.

Si vous construisez un SaaS multi-tenant où différents clients peuvent utiliser différents drivers de base de données, ou si vous développez en SQLite localement mais déployez sur MySQL en production (une configuration étonnamment courante), testez le comportement du timeout sur le driver réel de production avant de vous y fier.

À savoir également : MAX_EXECUTION_TIME s'applique aux instructions SELECT dans MySQL. Il ne s'applique pas aux opérations INSERT, UPDATE ou DELETE — celles-ci nécessitent des techniques différentes pour le timeout. La méthode timeout() dans Laravel est destinée aux requêtes de lecture.

Utilisez-la sur vos endpoints de rapports. Utilisez-la dans les queue jobs qui effectuent du traitement lourd en base de données. Utilisez-la partout où une requête peut légitimement avoir besoin de s'exécuter un certain temps mais ne devrait jamais s'exécuter indéfiniment.

À ce stade, vous avez l'évaluation paresseuse et les timeouts de requête en place. Le troisième changement dans 12.51 est de nature différente — moins axé sur les performances brutes, davantage sur le type de qualité de code qui s'accumule au sein d'une équipe entière au fil du temps.


Callbacks du Validator : Redonner à la Validation Manuelle le Goût de Laravel

La validation des requêtes HTTP dans Laravel est excellente. Vous définissez une classe Form Request, vous l'injectez dans votre contrôleur, et le framework gère tout automatiquement. La validation passe, le contrôleur s'exécute. La validation échoue, l'utilisateur reçoit les erreurs. Propre, automatique, zéro code superflu.

Mais toute la validation ne se fait pas dans les requêtes HTTP.

Les commandes Artisan doivent valider des arguments. Les classes de service doivent valider les entrées provenant de queue jobs. Les classes métier doivent valider des données venant d'API externes. Dans tous ces scénarios, vous faites de la validation manuelle — en créant une instance de Validator à la main et en vérifiant son résultat vous-même.

Le pattern classique :

public function handle(): int
{
    $validator = Validator::make($this->arguments(), [
        'email' => 'required|email|max:255',
        'role'  => 'required|in:admin,editor,viewer',
    ]);

    if ($validator->fails()) {
        $this->error('Validation failed:');
        foreach ($validator->errors()->all() as $error) {
            $this->line("  — {$error}");
        }
        return self::FAILURE;
    }

    $this->info('Creating user...');
    $this->userService->create($validator->validated());
    return self::SUCCESS;
}

Ce code est correct. Mais relisez-le et remarquez le rythme : vous construisez le validateur, puis vous cassez le flux pour vérifier fails(), gérer le chemin d'erreur, et ensuite — seulement ensuite — poursuivre avec le chemin de succès. Deux flux de contrôle distincts pour une seule opération de validation.

Laravel 12.51 ajoute whenFails() et whenPasses() directement sur l'instance de Validator :

public function handle(): int
{
    return Validator::make($this->arguments(), [
        'email' => 'required|email|max:255',
        'role'  => 'required|in:admin,editor,viewer',
    ])
    ->whenFails(function (Validator $validator) {
        $this->error('Validation failed:');
        foreach ($validator->errors()->all() as $error) {
            $this->line("  — {$error}");
        }
        return self::FAILURE;
    })
    ->whenPasses(function (Validator $validator) {
        $this->info('Creating user...');
        $this->userService->create($validator->validated());
        return self::SUCCESS;
    });
}

Les deux callbacks reçoivent l'instance du validateur en paramètre. La valeur de retour du callback qui s'exécute devient la valeur de retour de la chaîne. Si la validation échoue, whenFails s'exécute et sa valeur de retour est renvoyée. Si la validation passe, whenPasses s'exécute.

Vous n'êtes pas obligé d'utiliser les deux. Dans une classe de service qui doit lever une exception en cas d'échec :

public function processWebhook(array $payload): void
{
    Validator::make($payload, [
        'event'   => 'required|string',
        'data'    => 'required|array',
        'user_id' => 'required|integer|exists:users,id',
    ])
    ->whenFails(fn($v) => throw new InvalidPayloadException($v->errors()->toJson()))
    ->whenPasses(function ($v) {
        $this->dispatchWebhookEvent($v->validated());
    });
}

Ou vous pouvez utiliser uniquement whenFails pour gérer le cas d'erreur et laisser l'exécution se poursuivre naturellement en cas de succès :

Validator::make($data, $rules)
    ->whenFails(function ($validator) {
        Log::error('Data validation failed', ['errors' => $validator->errors()->toArray()]);
        return false;
    });

// code here runs whether validation passed or failed (if whenFails didn't return/throw)

L'API est suffisamment flexible pour couvrir la plupart des scénarios réels sans vous imposer un pattern spécifique.


Appliquer les Trois Fonctionnalités : Un Exemple Réaliste

Laissez-moi vous montrer à quoi ressemblent ces trois fonctionnalités ensemble dans un vrai morceau de code — une commande qui importe des utilisateurs depuis un fichier CSV.

Avant 12.51 :

<?php

namespace App\Console\Commands;

use App\Models\User;
use App\Services\BillingService;
use App\Services\AvatarService;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;

class ImportUsers extends Command
{
    protected $signature = 'users:import {file}';

    public function handle(BillingService $billing, AvatarService $avatars): int
    {
        $validator = Validator::make(['file' => $this->argument('file')], [
            'file' => 'required|string',
        ]);

        if ($validator->fails()) {
            $this->error($validator->errors()->first());
            return self::FAILURE;
        }

        $rows = array_map('str_getcsv', file($this->argument('file')));

        foreach ($rows as $row) {
            [$email, $name, $plan] = $row;

            // Eager evaluation: avatar and plan are computed even for existing users
            $user = User::firstOrCreate(
                ['email' => $email],
                [
                    'name'    => $name,
                    'avatar'  => $avatars->generate($email),      // API call, always runs
                    'plan_id' => $billing->getDefaultPlan()->id,  // DB query, always runs
                ]
            );

            // Long-running query with no timeout
            $exists = DB::table('user_activities')
                ->where('user_id', $user->id)
                ->where('created_at', '>=', now()->subYear())
                ->count();

            $this->line("Processed: {$email} (activities: {$exists})");
        }

        return self::SUCCESS;
    }
}

Après 12.51 :

<?php

namespace App\Console\Commands;

use App\Models\User;
use App\Services\BillingService;
use App\Services\AvatarService;
use Illuminate\Console\Command;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;

class ImportUsers extends Command
{
    protected $signature = 'users:import {file}';

    public function handle(BillingService $billing, AvatarService $avatars): int
    {
        return Validator::make(['file' => $this->argument('file')], [
            'file' => 'required|string',
        ])
        ->whenFails(function ($v) {
            $this->error($v->errors()->first());
            return self::FAILURE;
        })
        ->whenPasses(function () use ($billing, $avatars) {
            $rows = array_map('str_getcsv', file($this->argument('file')));

            foreach ($rows as $row) {
                [$email, $name] = $row;

                // Lazy evaluation: closure only runs if user doesn't exist
                $user = User::firstOrCreate(
                    ['email' => $email],
                    function () use ($email, $name, $billing, $avatars) {
                        return [
                            'name'    => $name,
                            'avatar'  => $avatars->generate($email),
                            'plan_id' => $billing->getDefaultPlan()->id,
                        ];
                    }
                );

                // Timeout: protect against long-running activity queries
                try {
                    $activityCount = DB::table('user_activities')
                        ->where('user_id', $user->id)
                        ->where('created_at', '>=', now()->subYear())
                        ->timeout(3)
                        ->count();

                    $this->line("Processed: {$email} (activities: {$activityCount})");

                } catch (QueryException $e) {
                    if (str_contains($e->getMessage(), 'Query execution was interrupted')) {
                        Log::warning("Activity query timed out for user {$email}");
                        $this->line("Processed: {$email} (activity count unavailable)");
                        continue;
                    }
                    throw $e;
                }
            }

            return self::SUCCESS;
        });
    }
}

La version d'après est légèrement plus longue car elle gère les cas d'échec explicitement — mais l'intention de chaque section est plus claire. La logique de validation vit dans un bloc chaîné. La logique de création est séparée de la logique de recherche. Les requêtes longues ont des limites.

C'est ainsi que la mise à niveau vers 12.51 devrait réellement se présenter en pratique : des améliorations ciblées, pas des réécritures.


Ce Que Je Pense Vraiment de Ces Changements

Bon — parlons franchement, parce que la plupart des articles ne disent pas ceci.

La fonctionnalité d'évaluation paresseuse répond à un choix de conception que je pense erroné depuis le départ. "Passer les attributs de création sous forme de tableau" est l'API intuitive, et c'est ce que chaque tutoriel et exemple de documentation montre. Mais cela prépare silencieusement les développeurs à un piège de performance. L'hypothèse que la plupart des gens font est que PHP est assez intelligent pour ne pas faire de travail inutile. Cette hypothèse est fausse pour les arguments de fonctions.

Je suis content que ce soit corrigé. Mais je pense aussi qu'il est honnête de dire que c'est une catégorie de bug que Laravel aurait pu rendre impossible il y a des années en documentant plus visiblement le comportement d'évaluation anticipée dans la documentation de firstOrCreate. J'ai vu exactement ce bug dans trois bases de code client différentes. Je l'ai probablement dans certains de mes propres projets qui n'ont pas encore suffisamment grossi pour en souffrir.

Vérifiez votre code.

La fonctionnalité de timeout de requête est excellente et je vais l'utiliser immédiatement — mais avec une réserve que je tiens à souligner : le nom de méthode timeout() ne donne aucune indication qu'il est spécifique à MySQL. Si vous travaillez dans une équipe où tout le monde ne connaît pas les détails internes, quelqu'un ajoutera timeout() à une requête PostgreSQL en s'attendant au comportement MySQL et sera perplexe quand les choses ne fonctionneront pas comme prévu. Une meilleure documentation et éventuellement un avertissement spécifique au driver dans l'exception seraient les bienvenus.

Les méthodes whenFails et whenPasses sont agréables. Je les apprécie. Mais j'ai déjà vu des gens les utiliser dans des contextes où elles rendent le code moins clair, pas plus — notamment quand la logique du callback est assez complexe pour qu'un simple if/else aurait été plus lisible. Ces méthodes brillent pour la gestion de validation courte et ciblée : lever une exception en cas d'échec, dispatcher un job en cas de succès. Si vos callbacks font 15 lignes chacun, demandez-vous si un bloc if traditionnel ne serait pas en fait plus clair.

Le modèle de releases incrémentales de Laravel fait que les améliorations de "version mineure" comme celles-ci tendent à passer inaperçues. C'est une erreur. Rien que le correctif d'évaluation paresseuse dans firstOrCreate pourrait améliorer les temps de réponse de plusieurs secondes pour des applications qui vivent avec ce problème sans le savoir. Ce n'est pas mineur en pratique — c'est une victoire significative déguisée en entrée ennuyeuse de changelog.


Les Chiffres de Performance : Ce Que Vous Pouvez Réellement Attendre

Laissez-moi être précis sur ce que ces changements feront et ne feront pas pour les métriques de votre application.

Lazy firstOrCreate — les chiffres :

L'amélioration dépend entièrement de ce qui se trouve dans votre closure de création. Voici un cadre approximatif :

Coût de l'Attribut de Création Requêtes Touchant des Enregistrements Existants Économie Attendue par Requête
Valeurs statiques uniquement Quelconque ~0ms (pas besoin du closure)
Un seul bcrypt / hash 80%+ 50–200ms
Un appel API externe 80%+ 200–1 500ms
Deux appels API + requête DB 80%+ 500–3 000ms

Si 80% de vos requêtes touchent des enregistrements existants (typique pour les sessions d'utilisateurs connectés), et que vos attributs de création incluent deux appels API de 500ms chacun en moyenne, vous envisagez une réduction de 1 000ms par requête au 80e percentile. Pour les endpoints à fort trafic, c'est transformateur.

Pour le projet client mentionné au début de cet article — celui avec les pages d'administration de quatre secondes — l'amélioration mesurée après le passage aux attributs de création basés sur closure était de 3,8 secondes par requête au niveau p95. Ce n'est pas théorique. C'est une application qui est passée de lente et frustrante à réactive en un seul commit.

Query timeout — la perspective opérationnelle :

Le timeout ne rend pas les requêtes plus rapides. Il rend le mode de défaillance contrôlé plutôt qu'illimité. La valeur réside dans votre budget d'erreurs et la fiabilité de votre système :

  • Avant le timeout : requête longue monopolise les connexions base de données → le pool de connexions s'épuise → les autres requêtes s'empilent → défaillance en cascade
  • Après le timeout : requête longue atteint le plafond → QueryException → vous gérez élégamment → les autres requêtes se poursuivent normalement

Pour une application en production gérant du trafic réel, cette différence est la frontière entre une page lente et une panne complète.

Callbacks chaînables du Validator — l'angle expérience développeur :

Aucun impact sur les performances à l'exécution. Le retour sur investissement se mesure en temps de revue de code, en vitesse d'onboarding et en coût de maintenance sur des mois et des années. Les équipes qui écrivent du code plus propre font moins d'erreurs. Moins d'erreurs signifie moins d'incidents. L'effet cumulé est réel même s'il n'est pas mesurable au chronomètre.


Mettez à Jour et Utilisez Réellement Ces Fonctionnalités

Voici la marche à suivre concrète.

Mettez à jour d'abord. Si vous êtes sur Laravel 12.x, c'est une mise à jour Composer :

composer require laravel/framework:^12.51

Lancez votre suite de tests. Ce sont des changements rétrocompatibles — si vos tests passent, tout va bien.

Ensuite, auditez vos appels firstOrCreate et createOrFirst. Lancez le grep mentionné plus haut :

grep -rn "firstOrCreate\|createOrFirst" app/ --include="*.php"

Pour chaque résultat, examinez le deuxième argument. S'il effectue du travail réel, convertissez-le en closure et mesurez l'avant/après dans votre environnement de staging.

Puis trouvez vos requêtes les plus lourdes — endpoints analytiques, routes de rapports, queue jobs qui font des agrégations complexes. Ajoutez ->timeout() avec un plafond raisonnable. Encapsulez dans un try/catch avec dégradation élégante. Déployez en staging, testez le chemin du timeout explicitement (vous pouvez temporairement réduire le timeout pour le déclencher), et confirmez que votre gestion d'erreurs fonctionne comme attendu.

Enfin, prenez une commande Artisan ou une classe de service qui utilise la validation manuelle et refactorisez-la pour utiliser whenFails/whenPasses. Voyez comment ça se lit. Si c'est plus propre, appliquez le pattern ailleurs. Si votre équipe préfère la structure traditionnelle if/else, c'est très bien aussi — utilisez l'outil qui rend votre code plus clair.

Je maintiens une liste de fonctionnalités que j'ai voulu avoir nativement dans Laravel depuis un moment. Lazy firstOrCreate était sur cette liste. Query timeout était sur cette liste. Et chaque fois qu'une release livre quelque chose de cette liste — sans rien casser d'autre — c'est un bon rappel que le framework est construit par des gens qui l'utilisent réellement en production.

Ce chargement de page de quatre secondes sur le panneau d'administration du client ? C'est 90ms maintenant. Même base de données. Même infrastructure. Un seul changement dans la façon dont les attributs de création sont passés à un seul appel de méthode.

Allez vérifier votre code.


🤝 Travaillons Ensemble

Vous cherchez à construire des systèmes d'IA, automatiser des workflows ou faire évoluer votre infrastructure technologique ? Je serais ravi de vous aider.

Coffee cup

Vous avez apprécié cet article ?

Votre soutien m'aide à créer davantage de contenu technique approfondi, d'outils open source et de ressources gratuites pour la communauté des développeurs.

Sujets connexes

Engr Mejba Ahmed

À propos de l'auteur

Engr Mejba Ahmed

Engr. Mejba Ahmed builds AI-powered applications and secure cloud systems for businesses worldwide. With 10+ years shipping production software in Laravel, Python, and AWS, he's helped companies automate workflows, reduce infrastructure costs, and scale without security headaches. He writes about practical AI integration, cloud architecture, and developer productivity.

Discussion

Comments

0

No comments yet

Be the first to share your thoughts

Leave a Comment

Your email won't be published

13  -  12  =  ?

Continuer l'apprentissage

Articles connexes

Tout parcourir

Comments

Leave a Comment

Comments are moderated before appearing.

Learning Resources

Expand Your Knowledge

Accelerate your growth with structured courses, verified certificates, interactive flashcards, and production-ready AI agent skills.

Sample Certificate of Completion

Sample certificate — complete any course to earn yours

Engr Mejba Ahmed

Engr Mejba Ahmed

Claude Code Expert · Online

👋

Hey there!

Quick Actions

WhatsApp Instant reply

Chat on WhatsApp

+880 1723 741224 · Instant reply

Popular Questions

Engr Mejba Ahmed is connected
Engr Mejba Ahmed is typing...
Engr Mejba Ahmed avatar

✉ Want me to follow up? Drop your email

Engr Mejba Ahmed avatar

📞 Connect Directly

Choose how you'd like to reach me

WhatsApp

+880 1723 741224

Email

[email protected]

✓ Details sent! I'll get back to you shortly.

Powered by OpenAI

335+

Blog Posts

25

AI Courses

63

Projects

Services & Expertise

Pricing & Process

Learning & Resources

Connect & Support