什么是服务容器
Laravel 的服务容器是用于管理类的依赖关系并进行依赖注入的机制。所谓依赖注入,是指将类所需的依赖通过构造函数(或在某些情况下的 setter 方法)“注入”到类中。 来看下面的例子。PodcastController 需要从 Apple Music 等数据源获取 Podcast。因此我们注入了一个可以获取 Podcast 的服务。通过注入服务,在测试时可以轻松将 AppleMusic 服务替换为 mock(伪实现)。
深入理解服务容器对构建大型 Laravel 应用不可或缺,也有助于对 Laravel 核心本身进行贡献。
零配置解析
如果类只依赖于其他具体类(而不是接口),则无需告诉容器如何解析。例如,将以下代码写入routes/web.php:
该示例在路由文件中定义了类,这仅用于演示。在实际应用中,请将服务类定义在
app/Services 目录下。Service 类并注入到路由处理器。不必准备配置文件即可享受依赖注入的好处。
在 Laravel 应用中编写的许多类(控制器、事件监听器、中间件等)都会通过容器自动注入依赖。
绑定
基本绑定
大多数绑定都在服务提供者中注册。在服务提供者内可通过$this->app 属性访问容器。
bind
通过bind 方法,传入类或接口名与闭包来注册绑定。
App 门面。
对于不依赖接口的类,无需绑定到容器。容器可以通过反射自动解析这些对象。
singleton
singleton 方法将类或接口绑定为只解析一次。一旦解析后的单例,之后对容器的调用都会返回同一实例。
singletonIf 方法,可以仅在指定类型尚未注册绑定时才注册单例绑定。
Singleton 属性
在类或接口上添加#[Singleton] 属性,也可以指示容器只解析一次。
绑定作用域单例
scoped 方法将类或接口绑定为在 Laravel 的请求/任务生命周期内只解析一次。它类似于 singleton,但通过 scoped 注册的实例会在 Laravel 应用开始新的”生命周期”时销毁,例如 Laravel Octane Worker 处理新请求时或队列 Worker 处理新任务时。
scopedIf 方法,可以仅在指定类型尚未注册绑定时才注册作用域绑定。
Scoped 属性
在类或接口上添加#[Scoped] 属性,也可以指示容器在请求/任务生命周期内只解析一次。
instance
也可以通过instance 方法将现有的对象实例绑定到容器。之后对容器的调用总是返回该实例。
将接口绑定到实现
服务容器的一个强大功能是可以将接口绑定到特定的实现。假设你有EventPusher 接口和 RedisEventPusher 实现。
RedisEventPusher 注入到需要 EventPusher 实现的类中。之后只需在构造函数中对 EventPusher 接口进行类型提示即可。
Bind 属性
Laravel 还提供了更便捷的Bind 属性。在接口上添加该属性,即可告诉 Laravel 当请求该接口时应自动注入哪个实现。使用 Bind 属性后,无需在服务提供者中进行额外的注册处理。
进一步地,还可以在接口上放置多个 Bind 属性,配置为在不同环境下注入不同的实现。
BindWhen 属性。可以向闭包传入容器,绑定应用时返回 true。Bind 与 BindWhen 属性按声明顺序被求值。
使用
BindWhen 属性需要 PHP 8.5 及以上。自动解析(基于类型提示的 DI)
在解析控制器、事件监听器、中间件等类时,服务容器会查看构造函数的类型提示并自动注入依赖。UserRepository 不依赖于接口,就无需注册到容器。仅需访问该路由,容器就会自动解析依赖并注入到控制器。
从容器解析
make 方法
使用make 方法可以从容器解析类实例。
makeWith 方法传入额外参数。
自动注入
实际上,你几乎不会直接调用make 方法。只需在容器所解析的类(控制器、事件监听器、中间件等)的构造函数上添加类型提示,容器就会自动注入。
门面与容器的关系
Laravel 的门面为容器中的对象提供了静态接口。例如,Cache::get() 在内部从容器获取 Cache 服务并调用。
构造函数注入的实践示例
来看在实际应用中的典型模式。1
定义接口
2
创建实现类
3
在服务提供者中绑定
4
在控制器中接收注入
Stripe 切换到其他提供商,也只需在一个地方修改绑定。
下一步
服务提供者
学习如何使用服务提供者注册绑定。