Image de couverture : Réduisez le temps de boot de Laravel de 40% grâce au Service Container
tech

Réduisez le temps de boot de Laravel de 40% grâce au Service Container

25 May 2026
5 min de lecture
3 vues
Sébastien Muler

Réduisez le temps de boot de Laravel de 40% grâce au Service Container

Vous avez déjà remarqué qu'une application Laravel minimaliste — quelques endpoints JSON, une migration, un job en queue — peut prendre plusieurs centaines de millisecondes avant même d'exécuter votre code ? Ce n'est pas votre logique métier qui ralentit tout : c'est le processus de boot du framework, et plus précisément le Service Container.

Un article récent de Gabriel Anhaia sur dev.to illustre parfaitement le problème : une équipe travaillant sur 40 microservices constatait que php artisan migrate seul consommait 2,4 secondes par run, soit une minute et demie de pipeline CI/CD gaspillée à chaque déploiement, uniquement pour initialiser le framework.

Bonne nouvelle : deux patterns natifs de Laravel permettent de corriger l'essentiel du problème sans réécrire une seule ligne de votre logique applicative.


Ce que fait réellement le boot de Laravel

Avant que votre code ne s'exécute, Laravel enchaîne plusieurs étapes systématiques :

  1. L'autoloader Composer se charge.
  2. bootstrap/app.php instancie l'application.
  3. Le kernel enregistre tous les Service Providers listés dans config/app.php, plus ceux auto-découverts par les packages installés.
  4. La méthode register() de chaque provider s'exécute et lie des classes dans le container.
  5. La méthode boot() de chaque provider s'exécute à son tour.

Le problème se situe aux étapes 3 à 5. Laravel charge et initialise l'intégralité de vos providers, qu'ils soient nécessaires ou non pour la requête en cours. Sur une application avec de nombreux packages, cela représente un travail considérable accompli systématiquement, même pour un simple health check.


Pattern 1 : le Deferred Service Provider

Le premier levier est le Deferred Provider. Par défaut, un Service Provider est chargé au boot, même si aucune des classes qu'il enregistre n't est jamais demandée lors de la requête courante.

En implémentant l'interface Illuminate\Contracts\Support\DeferrableProvider et en définissant une méthode provides(), vous indiquez à Laravel de ne résoudre ce provider que lorsqu'une des classes listées est effectivement demandée au container.

use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Support\DeferrableProvider;

class ReportingServiceProvider extends ServiceProvider implements DeferrableProvider
{
    public function register(): void
    {
        $this->app->singleton(ReportingService::class, function ($app) {
            return new ReportingService(
                $app->make(ReportingRepository::class)
            );
        });
    }

    public function provides(): array
    {
        return [ReportingService::class];
    }
}

Avec ce pattern, si votre commande artisan ou votre endpoint n'utilise pas ReportingService, le provider n'est tout simplement pas initialisé. Sur des applications avec des dizaines de services métier, l'économie au boot devient significative.

⚠️ Attention : un provider différé ne peut pas écouter les events dans boot(). Si votre provider fait les deux, séparez les responsabilités en deux providers distincts.


Pattern 2 : la liaison lazy avec bindIf et les closures

Le second levier concerne la manière dont vous effectuez vos liaisons dans le container. Comparez ces deux approches :

// ❌ Instanciation immédiate — coût payé au boot, même si jamais utilisé
$this->app->singleton(HeavyService::class, new HeavyService(new Config()));

// ✅ Instanciation différée — coût payé uniquement à la première résolution
$this->app->singleton(HeavyService::class, function ($app) {
    return new HeavyService($app->make(Config::class));
});

La première forme instancie HeavyService au moment où register() s'exécute. La seconde retarde l'instanciation au moment où le container reçoit une demande réelle pour cette classe. Sur des services qui initialisent des connexions, chargent des fichiers de configuration ou préparent des structures de données complexes, la différence est mesurable.

Combinez cela avec bindIf pour éviter d'écraser des liaisons déjà définies (utile en contexte de test) :

$this->app->bindIf(MailerInterface::class, function ($app) {
    return new SmtpMailer($app->make('config')->get('mail'));
});

Appliquer ces patterns dans un contexte CI/CD

L'impact est particulièrement visible dans les pipelines d'intégration continue. Chaque commande artisan — migrate, queue:work, cache:clear — déclenche un boot complet de Laravel. Dans une architecture microservices, ce coût se multiplie.

Quelques actions concrètes à mener :

  • Auditez vos providers : listez ceux dans config/app.php et identifiez lesquels enregistrent des services rarement utilisés. Ce sont les candidats prioritaires pour le pattern DeferrableProvider.
  • Vérifiez vos liaisons : remplacez les instanciations directes dans register() par des closures.
  • Mesurez avant et après : utilisez php artisan route:list ou time php artisan migrate comme benchmark rapide. Les gains sont visibles immédiatement.
  • Pensez aux packages tiers : certains packages populaires enregistrent des providers lourds. Vérifiez s'ils supportent le deferred loading ou si vous pouvez les déclarer manuellement plutôt que par auto-découverte.

Conclusion

La lenteur du boot de Laravel n'est pas une fatalité. Le Service Container offre nativement les outils pour charger uniquement ce qui est nécessaire, au moment où c'est nécessaire. Les patterns DeferrableProvider et les liaisons via closures existent depuis les premières versions du framework, mais restent sous-utilisés dans la majorité des projets.

Sur une application standard, ces ajustements peuvent réduire le temps de boot de 30 à 40 %. Sur une architecture avec de nombreux services ou des pipelines CI/CD intensifs, le gain en temps cumulé — et donc en coût d'infrastructure — devient rapidement substantiel.

Commencez petit : choisissez un provider non critique, appliquez DeferrableProvider, mesurez. Vous serez surpris du résultat.

Partager cet article