服务提供者
简介
服务提供者是整个 Laravel 应用启动流程的中心。你自己的应用、以及 Laravel 所有的核心服务,都是通过服务提供者来启动的。
"启动"具体指什么?说白了就是注册各种东西——注册服务容器绑定、事件监听器、中间件,甚至路由。服务提供者就是你配置应用的中心位置。
Laravel 内部用了几十个服务提供者来启动它的核心服务,比如邮件、队列、缓存等等。其中很多是"延迟"提供者——不会每次请求都加载,只有在真正用到它们提供的服务时才会被加载。
所有用户自定义的服务提供者都注册在 bootstrap/providers.php 文件里。接下来的文档会教你如何编写自己的服务提供者并注册到 Laravel 应用中。
编写服务提供者
所有服务提供者都继承 Illuminate\Support\ServiceProvider 类。大部分服务提供者包含 register 和 boot 两个方法。在 register 方法里,你只能往服务容器里绑东西。绝对不要在 register 里注册事件监听器、路由或者任何其他功能。
Artisan CLI 可以通过 make:provider 命令生成新的提供者。Laravel 会自动把它注册到你应用的 bootstrap/providers.php 文件里:
php artisan make:provider RiakServiceProvider
Register 方法
前面说过,register 方法里只能往服务容器里绑东西。绝对不要在这里注册事件监听器、路由等任何其他功能。否则你可能不小心用到了一个还没加载的服务提供者提供的服务。
来看一个基础的服务提供者示例。在服务提供者的任何方法里,你始终能通过 $app 属性访问服务容器:
<?php
namespace App\Providers;
use App\Services\Riak\Connection;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\ServiceProvider;
class RiakServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
$this->app->singleton(Connection::class, function (Application $app) {
return new Connection(config('riak'));
});
}
}
这个服务提供者只定义了 register 方法,用它在服务容器里注册了 App\Services\Riak\Connection 的实现。如果你还不太熟悉服务容器,先去看看它的文档。
bindings 和 singletons 属性
如果你的服务提供者要注册很多简单的绑定,可以用 bindings 和 singletons 属性,不用每次手动调用方法。框架加载服务提供者时会自动检查这两个属性并注册绑定:
<?php
namespace App\Providers;
use App\Contracts\DowntimeNotifier;
use App\Contracts\ServerProvider;
use App\Services\DigitalOceanServerProvider;
use App\Services\PingdomDowntimeNotifier;
use App\Services\ServerToolsProvider;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* All of the container bindings that should be registered.
*
* @var array
*/
public $bindings = [
ServerProvider::class => DigitalOceanServerProvider::class,
];
/**
* All of the container singletons that should be registered.
*
* @var array
*/
public $singletons = [
DowntimeNotifier::class => PingdomDowntimeNotifier::class,
ServerProvider::class => ServerToolsProvider::class,
];
}
Boot 方法
那如果要在服务提供者里注册一个视图合成器呢?这应该放在 boot 方法里。这个方法在所有其他服务提供者都注册完之后才调用,也就是说你在这里能拿到框架已注册的所有其他服务:
<?php
namespace App\Providers;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
class ComposerServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*/
public function boot(): void
{
View::composer('view', function () {
// ...
});
}
}
Boot 方法的依赖注入
你可以在服务提供者的 boot 方法里类型提示依赖。服务容器会自动注入你需要的任何依赖:
use Illuminate\Contracts\Routing\ResponseFactory;
/**
* Bootstrap any application services.
*/
public function boot(ResponseFactory $response): void
{
$response->macro('serialized', function (mixed $value) {
// ...
});
}
注册服务提供者
所有服务提供者都注册在 bootstrap/providers.php 配置文件里。这个文件返回一个数组,包含你应用中所有服务提供者的类名:
<?php
return [
App\Providers\AppServiceProvider::class,
];
当你执行 make:provider Artisan 命令时,Laravel 会自动把生成的提供者加到 bootstrap/providers.php 文件里。但如果你是手动创建的提供者类,就得自己手动把它加到数组中:
<?php
return [
App\Providers\AppServiceProvider::class,
App\Providers\ComposerServiceProvider::class, // [tl! add]
];
延迟提供者
如果你的提供者只在服务容器里注册绑定,那就可以选择延迟加载——等到真正用到这些绑定时才加载。延迟加载能提升应用性能,因为不是每次请求都从文件系统加载它。
Laravel 会编译并存储一个所有延迟提供者提供的服务列表,以及对应的提供者类名。然后,只有当你真的去解析这些服务时,Laravel 才会加载那个服务提供者。
要延迟加载一个提供者,实现 \Illuminate\Contracts\Support\DeferrableProvider 接口并定义一个 provides 方法。provides 方法应该返回这个提供者所注册的服务容器绑定:
<?php
namespace App\Providers;
use App\Services\Riak\Connection;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;
class RiakServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* Register any application services.
*/
public function register(): void
{
$this->app->singleton(Connection::class, function (Application $app) {
return new Connection($app['config']['riak']);
});
}
/**
* Get the services provided by the provider.
*
* @return array<int, string>
*/
public function provides(): array
{
return [Connection::class];
}
}
为什么要单独创建Provider
代码全写在 AppServiceProvider 里还是单独建?
全写 AppServiceProvider 里完全没问题。 Laravel 默认就只带一个 AppServiceProvider,大部分中小项目一个就够用。
什么时候继续用 AppServiceProvider 就行:
- 绑定数量少,
register()里不超过三四十行 - 项目没有性能瓶颈,不在乎每次请求多加载几个绑定
- 就你一个人在维护,不需要模块化拆分
什么时候该自己新建一个:
| 场景 | 为什么 |
|---|---|
| 某个功能模块绑定很多 | 比如支付模块有十几个绑定,全塞 AppServiceProvider 里 register() 变成几百行,找东西费劲 |
| 想用延迟加载提升性能 | 大量只做绑定、不做别的事的提供者,实现 DeferrableProvider 后只有用到才加载——这个只有独立文件才能做 |
| 按环境条件注册 | 比如只在 local 环境加载 TelescopeServiceProvider,独立文件好管理 |
| 写扩展包 | 包必须自带提供者,不能指望用户帮你写到 AppServiceProvider 里 |
简单说:AppServiceProvider 是默认的"杂物抽屉"——东西少就这么放着。当你发现抽屉塞不下、或者需要延迟加载时,再抽出来单独建。