Laravel 启动时注册的 Service Provider,即使在请求中一次都没用到,也会每次都被加载。延迟 Service Provider(Deferred Service Provider)可以解决这一问题,让服务在真正被使用之前不加载。
本页假定你已阅读包开发基础 。建议在理解 Service Provider 的基本机制后再阅读本页。
为什么需要延迟 Provider
普通的 Service Provider 每个请求都会调用 register() 与 boot()。像邮件、队列、缓存等并非所有页面都使用的功能,每次都被初始化是浪费。
// 每个请求都会调用该 Provider 的 register()
class ReportServiceProvider extends ServiceProvider
{
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 ),
);
});
}
}
将该 Provider 改为延迟后,只有在实际生成报表的请求中才会初始化。
DeferrableProvider 接口
Illuminate\Contracts\Support\DeferrableProvider 是仅有 provides() 方法的简单接口。
namespace Illuminate\Contracts\Support ;
interface DeferrableProvider
{
public function provides ();
}
只要实现这个接口,Provider 就会进入延迟模式。
基本实现
延迟 Provider 的实现分 3 步。
implements DeferrableProvider
use Illuminate\Contracts\Support\ DeferrableProvider ;
use Illuminate\Support\ ServiceProvider ;
class ReportServiceProvider extends ServiceProvider implements DeferrableProvider
{
// ...
}
在 register() 中绑定服务
与普通 Provider 一样,在 register() 中写入绑定。 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' ));
});
}
在 provides() 中返回已注册的服务
provides() 必须返回 register() 中所有 已绑定的服务。Laravel 会依据该列表判定「请求哪个服务时该加载这个 Provider」。public function provides () : array
{
return [
ReportGenerator :: class ,
ReportRepository :: class ,
];
}
Service Manifest 的机制
Laravel 会在启动时生成清单文件 bootstrap/cache/services.php。该文件保存了延迟 Provider 提供的服务列表。
// bootstrap/cache/services.php 示例(自动生成)
return [
'providers' => [
// 与 bootstrap/providers.php 相同的列表
],
'eager' => [
// 立即加载的 Provider
App\Providers\ AppServiceProvider :: class ,
],
'deferred' => [
// 服务名 => 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' => [],
];
有了这份清单,Laravel 无需加载文件就能知道「哪个 Provider 提供了哪个服务」。实际的 Provider 只有在对应服务首次被解析时才会加载。
添加或修改 Provider 后,请重新生成清单。 php artisan optimize:clear
# 或
php artisan clear-compiled
内部动作流程
provides() 方法的重要性
如果 provides() 中遗漏 某个服务,该服务将永远无法解析。
public function register () : void
{
$this -> app -> singleton ( ReportGenerator :: class , fn ( $app ) => new ReportGenerator ());
// 追加的绑定
$this -> app -> singleton ( 'report' , fn ( $app ) => $app -> make ( ReportGenerator :: class ));
}
public function provides () : array
{
return [
ReportGenerator :: class ,
'report' , // ← 以字符串键绑定的服务也不要遗漏
];
}
使用 $bindings / $singletons 属性时也一样,需要在 provides() 中列出。
class AnalyticsServiceProvider extends ServiceProvider implements DeferrableProvider
{
public $singletons = [
AnalyticsClient :: class => DefaultAnalyticsClient :: class ,
];
public function provides () : array
{
// 返回 $singletons 中列出的全部键
return [
AnalyticsClient :: class ,
];
}
}
延迟 Provider 的限制
延迟 Provider 适用于仅进行容器绑定注册 的 Provider。如果 boot() 中包含以下事项,就不能延迟。
限制 原因 路由注册 路由在应用启动时解析,延迟无法生效 全局中间件注册 需在处理请求之前注册 事件监听器注册(常需生效者) 必须在事件触发之前注册 添加 Blade 指令 需在视图编译前注册
延迟 Provider 可以写 boot() 方法,但其中的代码在服务被解析前不会执行。如果把路由、中间件之类「总是需要的处理」写在 boot() 中,会得到意料之外的行为。
when() 方法 — 通过事件触发注册
使用 when() 方法可以在特定事件触发时注册 Provider。这适用于仅在特定上下文中需要的 Provider(如 Job 处理相关的 Provider)。
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 ];
}
/**
* 不仅在服务绑定解析时加载 Provider,
* 在指定事件触发时也会加载。
*/
public function when () : array
{
return [
\App\Events\ ReportRequested :: class ,
];
}
}
一旦 when() 中返回的事件触发,即使服务没有被直接解析,Provider 也会加载。
在包开发中的应用
作为第三方包分发时,延迟 Provider 也能为用户应用的性能做贡献。
推荐模式
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
{
// boot() 只用于发布注册
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() 会在内部检查配置是否已缓存,因此即使在延迟 Provider 的 register() 中调用也是安全的。但如果配置已被缓存,则不做任何处理。
用 runningInConsole() 分离 Artisan 命令注册
命令注册只在 Artisan 启动时才需要,所以用 runningInConsole() 做条件判断。不过,如果要延迟一个提供命令的 Provider,请将命令类也加入 provides(),或专门准备一个只提供命令的 Provider。
public function boot () : void
{
if ( $this -> app -> runningInConsole ()) {
$this -> commands ([
AnalyticsFlushCommand :: class ,
]);
}
}
Laravel 核心中的应用示例
Laravel 核心的很多 Provider 都是延迟 Provider。这是为了避免每个请求都立即加载所有服务。
Provider 提供的服务 CacheServiceProvidercache, cache.store, RateLimiterQueueServiceProviderqueue, queue.worker, queue.failerMailServiceProvidermail.manager, mailerRedisServiceProviderredis, redis.connectionHashServiceProviderhash, hash.driverValidationServiceProvidervalidator, validation.presenceTranslationServiceProvidertranslatorBroadcastServiceProviderBroadcast
在只有 API 端点的应用中,MailServiceProvider 或 BroadcastServiceProvider 可能在请求过程中一次都不会被加载。
是否应该延迟的判断标准
并非所有请求都使用的服务(邮件、报表、外部 API 客户端等)
初始化需要外部连接或读取文件的服务
拥有庞大对象图的服务
只在 CLI 中使用的命令 Provider
注册路由的 Provider(如 loadRoutesFrom 等)
注册常时生效的中间件或异常处理器的 Provider
注册 Eloquent 全局 Scope 或 Observer 的 Provider
大多数请求都会用到的轻量服务(延迟带来的开销可能更大)
相关页面
包开发基础 以 Service Provider 为核心讲解 Laravel 包的开发方法。
包的版本兼容性管理 应对 Laravel 与 PHP 主版本升级的包维护策略。