服务提供者

简介

服务提供者是整个 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 是默认的"杂物抽屉"——东西少就这么放着。当你发现抽屉塞不下、或者需要延迟加载时,再抽出来单独建。