本页假定你已阅读包开发基础。建议在理解 Service Provider 的基本机制后再阅读本页。
为什么需要延迟 Provider
普通的 Service Provider 每个请求都会调用register() 与 boot()。像邮件、队列、缓存等并非所有页面都使用的功能,每次都被初始化是浪费。
DeferrableProvider 接口
Illuminate\Contracts\Support\DeferrableProvider 是仅有 provides() 方法的简单接口。
基本实现
延迟 Provider 的实现分 3 步。1
implements DeferrableProvider
2
在 register() 中绑定服务
与普通 Provider 一样,在
register() 中写入绑定。3
在 provides() 中返回已注册的服务
provides() 必须返回 register() 中所有已绑定的服务。Laravel 会依据该列表判定「请求哪个服务时该加载这个 Provider」。Service Manifest 的机制
Laravel 会在启动时生成清单文件bootstrap/cache/services.php。该文件保存了延迟 Provider 提供的服务列表。
内部动作流程
provides() 方法的重要性
如果provides() 中遗漏某个服务,该服务将永远无法解析。
$bindings / $singletons 属性时也一样,需要在 provides() 中列出。
延迟 Provider 的限制
延迟 Provider 适用于仅进行容器绑定注册的 Provider。如果boot() 中包含以下事项,就不能延迟。
when() 方法 — 通过事件触发注册
使用when() 方法可以在特定事件触发时注册 Provider。这适用于仅在特定上下文中需要的 Provider(如 Job 处理相关的 Provider)。
when() 中返回的事件触发,即使服务没有被直接解析,Provider 也会加载。
在包开发中的应用
作为第三方包分发时,延迟 Provider 也能为用户应用的性能做贡献。推荐模式
mergeConfigFrom() 会在内部检查配置是否已缓存,因此即使在延迟 Provider 的 register() 中调用也是安全的。但如果配置已被缓存,则不做任何处理。用 runningInConsole() 分离 Artisan 命令注册
命令注册只在 Artisan 启动时才需要,所以用 runningInConsole() 做条件判断。不过,如果要延迟一个提供命令的 Provider,请将命令类也加入 provides(),或专门准备一个只提供命令的 Provider。
Laravel 核心中的应用示例
Laravel 核心的很多 Provider 都是延迟 Provider。这是为了避免每个请求都立即加载所有服务。
在只有 API 端点的应用中,
MailServiceProvider 或 BroadcastServiceProvider 可能在请求过程中一次都不会被加载。
是否应该延迟的判断标准
适合延迟的服务
适合延迟的服务
- 并非所有请求都使用的服务(邮件、报表、外部 API 客户端等)
- 初始化需要外部连接或读取文件的服务
- 拥有庞大对象图的服务
- 只在 CLI 中使用的命令 Provider
不适合延迟的服务
不适合延迟的服务
- 注册路由的 Provider(如
loadRoutesFrom等) - 注册常时生效的中间件或异常处理器的 Provider
- 注册 Eloquent 全局 Scope 或 Observer 的 Provider
- 大多数请求都会用到的轻量服务(延迟带来的开销可能更大)
相关页面
包开发基础
以 Service Provider 为核心讲解 Laravel 包的开发方法。
包的版本兼容性管理
应对 Laravel 与 PHP 主版本升级的包维护策略。