> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# 延迟 Service Provider

> 讲解如何通过实现 DeferrableProvider 接口延迟加载服务。这是与包性能优化直接相关、看似朴素但十分重要的功能。

Laravel 启动时注册的 Service Provider，即使在请求中一次都没用到，也会每次都被加载。延迟 Service Provider（Deferred Service Provider）可以解决这一问题，让服务在真正被使用之前不加载。

<Info>
  本页假定你已阅读[包开发基础](/zh/advanced/package-development)。建议在理解 Service Provider 的基本机制后再阅读本页。
</Info>

## 为什么需要延迟 Provider

普通的 Service Provider 每个请求都会调用 `register()` 与 `boot()`。像邮件、队列、缓存等并非所有页面都使用的功能，每次都被初始化是浪费。

```php theme={null}
// 每个请求都会调用该 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()` 方法的简单接口。

```php theme={null}
namespace Illuminate\Contracts\Support;

interface DeferrableProvider
{
    public function provides();
}
```

只要实现这个接口，Provider 就会进入延迟模式。

***

## 基本实现

延迟 Provider 的实现分 3 步。

<Steps>
  <Step title="implements DeferrableProvider">
    ```php theme={null}
    use Illuminate\Contracts\Support\DeferrableProvider;
    use Illuminate\Support\ServiceProvider;

    class ReportServiceProvider extends ServiceProvider implements DeferrableProvider
    {
        // ...
    }
    ```
  </Step>

  <Step title="在 register() 中绑定服务">
    与普通 Provider 一样，在 `register()` 中写入绑定。

    ```php theme={null}
    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'));
        });
    }
    ```
  </Step>

  <Step title="在 provides() 中返回已注册的服务">
    `provides()` 必须返回 `register()` 中**所有**已绑定的服务。Laravel 会依据该列表判定「请求哪个服务时该加载这个 Provider」。

    ```php theme={null}
    public function provides(): array
    {
        return [
            ReportGenerator::class,
            ReportRepository::class,
        ];
    }
    ```
  </Step>
</Steps>

***

## Service Manifest 的机制

Laravel 会在启动时生成清单文件 `bootstrap/cache/services.php`。该文件保存了延迟 Provider 提供的服务列表。

```php theme={null}
// 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 只有在对应服务首次被解析时才会加载。

<Tip>
  添加或修改 Provider 后，请重新生成清单。

  ```shell theme={null}
  php artisan optimize:clear
  # 或
  php artisan clear-compiled
  ```
</Tip>

### 内部动作流程

```mermaid theme={null}
sequenceDiagram
    participant App as Laravel<br>应用
    participant Container as 服务<br>容器
    participant Manifest as services.php<br>清单
    participant Provider as ReportService<br>Provider

    App->>Manifest: 启动时读取清单
    Manifest-->>App: 返回 deferred 服务列表
    Note over App: 此时 Provider 尚未加载

    App->>Container: $app->make(ReportGenerator::class)
    Container->>Manifest: 检查是否为 deferred 服务
    Manifest-->>Container: 由 ReportServiceProvider 提供
    Container->>Provider: 调用 register() + boot()
    Provider-->>Container: 完成绑定注册
    Container-->>App: 返回 ReportGenerator 实例
```

***

## provides() 方法的重要性

如果 `provides()` 中**遗漏**某个服务，该服务将永远无法解析。

```php theme={null}
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()` 中列出。

```php theme={null}
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 指令    | 需在视图编译前注册         |

<Warning>
  延迟 Provider 可以写 `boot()` 方法，但其中的代码在服务被解析前不会执行。如果把路由、中间件之类「总是需要的处理」写在 `boot()` 中，会得到意料之外的行为。
</Warning>

***

## when() 方法 — 通过事件触发注册

使用 `when()` 方法可以在特定事件触发时注册 Provider。这适用于仅在特定上下文中需要的 Provider（如 Job 处理相关的 Provider）。

```php theme={null}
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 也能为用户应用的性能做贡献。

### 推荐模式

```php theme={null}
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',
        ];
    }
}
```

<Info>
  `mergeConfigFrom()` 会在内部检查配置是否已缓存，因此即使在延迟 Provider 的 `register()` 中调用也是安全的。但如果配置已被缓存，则不做任何处理。
</Info>

### 用 `runningInConsole()` 分离 Artisan 命令注册

命令注册只在 Artisan 启动时才需要，所以用 `runningInConsole()` 做条件判断。不过，如果要延迟一个提供命令的 Provider，请将命令类也加入 `provides()`，或专门准备一个只提供命令的 Provider。

```php theme={null}
public function boot(): void
{
    if ($this->app->runningInConsole()) {
        $this->commands([
            AnalyticsFlushCommand::class,
        ]);
    }
}
```

***

## Laravel 核心中的应用示例

Laravel 核心的很多 Provider 都是延迟 Provider。这是为了避免每个请求都立即加载所有服务。

| Provider                     | 提供的服务                                   |
| ---------------------------- | --------------------------------------- |
| `CacheServiceProvider`       | `cache`, `cache.store`, `RateLimiter`   |
| `QueueServiceProvider`       | `queue`, `queue.worker`, `queue.failer` |
| `MailServiceProvider`        | `mail.manager`, `mailer`                |
| `RedisServiceProvider`       | `redis`, `redis.connection`             |
| `HashServiceProvider`        | `hash`, `hash.driver`                   |
| `ValidationServiceProvider`  | `validator`, `validation.presence`      |
| `TranslationServiceProvider` | `translator`                            |
| `BroadcastServiceProvider`   | `Broadcast`                             |

在只有 API 端点的应用中，`MailServiceProvider` 或 `BroadcastServiceProvider` 可能在请求过程中一次都不会被加载。

***

## 是否应该延迟的判断标准

<AccordionGroup>
  <Accordion title="适合延迟的服务">
    * 并非所有请求都使用的服务（邮件、报表、外部 API 客户端等）
    * 初始化需要外部连接或读取文件的服务
    * 拥有庞大对象图的服务
    * 只在 CLI 中使用的命令 Provider
  </Accordion>

  <Accordion title="不适合延迟的服务">
    * 注册路由的 Provider（如 `loadRoutesFrom` 等）
    * 注册常时生效的中间件或异常处理器的 Provider
    * 注册 Eloquent 全局 Scope 或 Observer 的 Provider
    * 大多数请求都会用到的轻量服务（延迟带来的开销可能更大）
  </Accordion>
</AccordionGroup>

***

## 相关页面

<Columns cols={2}>
  <Card title="包开发基础" icon="box" href="/zh/advanced/package-development">
    以 Service Provider 为核心讲解 Laravel 包的开发方法。
  </Card>

  <Card title="包的版本兼容性管理" icon="layers" href="/zh/advanced/package-versioning">
    应对 Laravel 与 PHP 主版本升级的包维护策略。
  </Card>
</Columns>


## Related topics

- [服务提供者](/zh/service-providers.md)
- [Laravel 11 之后新应用结构 FAQ](/zh/advanced/app-structure-faq.md)
- [Laravel 11 之后的应用结构](/zh/advanced/app-structure.md)
- [GeneratorCommand — 实现自定义 make: 命令](/zh/advanced/generator-command.md)
- [从 laravel/ui 迁移到 Fortify 的指南](/zh/blog/ui-to-fortify.md)
