Découvrez comment charger vos services en différé en implémentant l’interface DeferrableProvider. Une fonctionnalité discrète mais cruciale pour l’optimisation des performances des packages.
Au démarrage de Laravel, les service providers enregistrés sont chargés à chaque requête, même si leur fonctionnalité n’est jamais utilisée durant celle-ci. Les service providers différés (Deferred Service Provider) résolvent ce problème en retardant le chargement jusqu’à ce que le service soit réellement utilisé.
Cette page suppose la connaissance des bases du développement de packages. Il est recommandé de comprendre le fonctionnement de base des service providers avant de la lire.
Les service providers ordinaires voient leurs register() et boot() appelés à chaque requête. Initialiser à chaque fois des fonctionnalités que toutes les pages n’utilisent pas — envoi d’e-mails, queues, cache — est un gaspillage.
// Le register() de ce provider est appelé à chaque requêteclass ReportServiceProvider extends ServiceProvider{ public function register(): void { // Lier un service de rapport lourd à chaque fois $this->app->singleton(ReportGenerator::class, function ($app) { return new ReportGenerator( $app->make(PdfRenderer::class), $app->make(ChartRenderer::class), $app->make(DataExporter::class), ); }); }}
Le rendre différé fait que l’initialisation n’a lieu que pour les requêtes qui génèrent réellement un rapport.
L’implémentation d’un provider différé se fait en trois étapes.
1
Implémenter DeferrableProvider
use Illuminate\Contracts\Support\DeferrableProvider;use Illuminate\Support\ServiceProvider;class ReportServiceProvider extends ServiceProvider implements DeferrableProvider{ // ...}
2
Lier les services dans register()
Écrivez les bindings dans register() comme dans un provider ordinaire.
public function register(): void{ $this->app->singleton(ReportGenerator::class, function ($app) { return new ReportGenerator( $app->make(PdfRenderer::class), $app->make(ChartRenderer::class), $app->make(DataExporter::class), ); }); $this->app->singleton(ReportRepository::class, function ($app) { return new ReportRepository($app->make('db')); });}
3
Retourner les services enregistrés dans provides()
provides() doit retourner tous les services liés dans register(). Laravel se base sur cette liste pour déterminer « quel provider charger lorsque tel service est demandé ».
public function provides(): array{ return [ ReportGenerator::class, ReportRepository::class, ];}
Au démarrage, Laravel génère un fichier manifeste bootstrap/cache/services.php. Ce fichier contient la liste des services fournis par les providers différés.
// Exemple de bootstrap/cache/services.php (généré automatiquement)return [ 'providers' => [ // Même liste que bootstrap/providers.php ], 'eager' => [ // Providers à chargement immédiat App\Providers\AppServiceProvider::class, ], 'deferred' => [ // Mapping nom de service => classe de provider 'App\Services\ReportGenerator' => ReportServiceProvider::class, 'App\Repositories\ReportRepository' => ReportServiceProvider::class, 'cache' => Illuminate\Cache\CacheServiceProvider::class, 'cache.store' => Illuminate\Cache\CacheServiceProvider::class, 'queue' => Illuminate\Queue\QueueServiceProvider::class, ], 'when' => [],];
Grâce à ce manifeste, Laravel sait « quel provider fournit ce service » sans avoir à charger de fichiers. Le provider réel n’est chargé que lorsque le service correspondant est résolu pour la première fois.
Après avoir ajouté ou modifié un provider, régénérez le manifeste.
Si provides() contient un oubli, le service correspondant ne sera jamais résolu.
public function register(): void{ $this->app->singleton(ReportGenerator::class, fn ($app) => new ReportGenerator()); // Binding supplémentaire $this->app->singleton('report', fn ($app) => $app->make(ReportGenerator::class));}public function provides(): array{ return [ ReportGenerator::class, 'report', // ← N'oubliez pas de mentionner aussi les bindings à clé chaîne ];}
Idem lorsque vous utilisez les propriétés $bindings / $singletons : incluez-les dans provides().
class AnalyticsServiceProvider extends ServiceProvider implements DeferrableProvider{ public $singletons = [ AnalyticsClient::class => DefaultAnalyticsClient::class, ]; public function provides(): array { // Retourner toutes les clés listées dans $singletons return [ AnalyticsClient::class, ]; }}
Les providers différés conviennent aux providers dont le seul objectif est l’enregistrement de bindings dans le conteneur. Les providers qui effectuent les opérations ci-dessous dans boot() ne peuvent pas être différés.
Contrainte
Raison
Enregistrement de routes
Les routes sont résolues au démarrage de l’app, le différé ne fonctionne pas
Enregistrement de middlewares globaux
Doit se faire avant le traitement de la requête
Enregistrement d’event listeners (permanents)
Doit être enregistré avant que l’événement soit émis
Ajout de directives Blade
Doit être enregistré avant la compilation des vues
Écrire une méthode boot() dans un provider différé est possible, mais son contenu ne s’exécutera que lorsqu’un service sera résolu. Écrire des « traitements toujours nécessaires » comme des routes ou des middlewares dans boot() produit des comportements inattendus.
La méthode when() — enregistrement déclenché par un événement
La méthode when() permet d’enregistrer un provider lorsqu’un événement particulier est émis. Utile pour des providers nécessaires uniquement dans certains contextes (traitement de jobs, etc.).
class ReportServiceProvider extends ServiceProvider implements DeferrableProvider{ public function register(): void { $this->app->singleton(ReportGenerator::class, fn () => new ReportGenerator()); } public function provides(): array { return [ReportGenerator::class]; } /** * Charger le provider aussi lorsque les événements spécifiés * sont émis, en plus de la résolution d'un binding de service. */ public function when(): array { return [ \App\Events\ReportRequested::class, ]; }}
Lorsque l’un des événements retournés par when() est émis, le provider est chargé même si aucun service n’a été directement résolu.
namespace Acme\Analytics;use Illuminate\Contracts\Support\DeferrableProvider;use Illuminate\Support\ServiceProvider;class AnalyticsServiceProvider extends ServiceProvider implements DeferrableProvider{ public function register(): void { $this->mergeConfigFrom(__DIR__.'/../config/analytics.php', 'analytics'); $this->app->singleton(AnalyticsManager::class, function ($app) { return new AnalyticsManager($app->make('config')->get('analytics')); }); $this->app->singleton('analytics', fn ($app) => $app->make(AnalyticsManager::class)); } public function boot(): void { // Utiliser boot() uniquement pour l'enregistrement des publications if ($this->app->runningInConsole()) { $this->publishes([ __DIR__.'/../config/analytics.php' => config_path('analytics.php'), ], 'analytics-config'); } } public function provides(): array { return [ AnalyticsManager::class, 'analytics', ]; }}
mergeConfigFrom() vérifie en interne si la configuration est en cache, donc l’appeler dans le register() d’un provider différé est sûr. Si la configuration est déjà en cache, cela ne fait rien.
Séparer l’enregistrement des commandes Artisan avec runningInConsole()
L’enregistrement de commandes n’est nécessaire qu’au démarrage d’Artisan ; branchez donc avec runningInConsole(). Toutefois, si vous différez un provider qui fournit des commandes, incluez la classe de commande dans provides() ou séparez les commandes dans un provider dédié.
public function boot(): void{ if ($this->app->runningInConsole()) { $this->commands([ AnalyticsFlushCommand::class, ]); }}
Beaucoup des providers du cœur de Laravel sont des providers différés. C’est une conception qui évite de charger immédiatement tous les services non utilisés à chaque requête.
Provider
Services fournis
CacheServiceProvider
cache, cache.store, RateLimiter
QueueServiceProvider
queue, queue.worker, queue.failer
MailServiceProvider
mail.manager, mailer
RedisServiceProvider
redis, redis.connection
HashServiceProvider
hash, hash.driver
ValidationServiceProvider
validator, validation.presence
TranslationServiceProvider
translator
BroadcastServiceProvider
Broadcast
Dans une application composée uniquement d’endpoints API, MailServiceProvider ou BroadcastServiceProvider peuvent ne jamais être chargés durant une requête.