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

# 지연 서비스 프로바이더

> DeferrableProvider 인터페이스를 구현하여 서비스의 지연 로딩을 하는 방법을 해설합니다. 패키지의 성능 최적화에 직결되는 수수하지만 중요한 기능입니다.

Laravel 기동 시에 등록되는 서비스 프로바이더는, 설령 그 기능을 요청 중에 한 번도 사용하지 않아도 매번 로드됩니다. 지연 서비스 프로바이더(Deferred Service Provider)는 이 문제를 해결하고, 서비스가 실제로 사용될 때까지 로드를 뒤로 미룰 수 있습니다.

<Info>
  이 페이지는 [패키지 개발의 기초](/ko/advanced/package-development)의 지식을 전제로 하고 있습니다. 서비스 프로바이더의 기본적인 구조를 이해한 뒤 읽는 것을 권장합니다.
</Info>

## 왜 지연 프로바이더가 필요한가

일반적인 서비스 프로바이더는 모든 요청에서 `register()`와 `boot()`가 호출됩니다. 메일 송신, 큐, 캐시 등, 모든 페이지에서 사용하지 않는 기능까지 매번 초기화하는 것은 낭비입니다.

```php theme={null}
// このプロバイダーはすべてのリクエストで 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),
            );
        });
    }
}
```

이 프로바이더를 지연화하면, 리포트를 실제로 생성하는 요청에서만 초기화됩니다.

***

## DeferrableProvider 인터페이스

`Illuminate\Contracts\Support\DeferrableProvider`는 `provides()` 메서드 하나를 갖는 심플한 인터페이스입니다.

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

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

이 인터페이스를 구현하는 것만으로, 프로바이더는 지연 모드가 됩니다.

***

## 기본적인 구현

지연 프로바이더의 구현은 3단계입니다.

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

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

  <Step title="register()에서 서비스를 바인드한다">
    일반적인 프로바이더와 마찬가지로 `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은 이 목록을 근거로 "어느 서비스를 요구했을 때 이 프로바이더를 로드할지"를 판단합니다.

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

***

## 서비스 매니페스트의 구조

Laravel은 기동 시에 `bootstrap/cache/services.php`라는 매니페스트 파일을 생성합니다. 이 파일에 지연 프로바이더가 제공하는 서비스의 목록이 보관되어 있습니다.

```php theme={null}
// bootstrap/cache/services.php の例（自動生成）
return [
    'providers' => [
        // bootstrap/providers.php と同じリスト
    ],
    'eager' => [
        // 即時読み込みのプロバイダー
        App\Providers\AppServiceProvider::class,
    ],
    'deferred' => [
        // サービス名 => プロバイダークラス のマッピング
        '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은 파일을 로드하지 않고 "이 서비스는 어느 프로바이더가 제공하는가"를 파악할 수 있습니다. 실제 프로바이더는 대상 서비스가 처음으로 해결될 때만 로드됩니다.

<Tip>
  프로바이더를 추가·변경한 경우에는 매니페스트를 재생성해 주세요.

  ```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: 이 시점에서는 프로바이더는 로드되어 있지 않음

    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,
        ];
    }
}
```

***

## 지연 프로바이더의 제약

지연 프로바이더는 **컨테이너에의 바인딩 등록만**을 목적으로 한 프로바이더에 적합합니다. 이하와 같은 것을 `boot()`에서 하는 프로바이더는 지연화할 수 없습니다.

| 제약                   | 이유                                |
| -------------------- | --------------------------------- |
| 라우트 등록               | 라우트는 앱 기동 시에 해결되므로, 지연에서는 기능하지 않음 |
| 글로벌 미들웨어 등록          | 요청 처리 전에 등록이 필요                   |
| 이벤트 리스너 등록(상시 필요한 것) | 이벤트가 발화되기 전에 등록되어 있지 않으면 기능하지 않음  |
| Blade 디렉티브의 추가       | 뷰의 컴파일 전에 등록이 필요                  |

<Warning>
  지연 프로바이더에 `boot()` 메서드를 쓰는 것은 가능하지만, 그 내용은 서비스가 해결될 때까지 실행되지 않습니다. 라우트나 미들웨어와 같은 "항상 필요한 처리"를 `boot()`에 쓰면, 예기치 않은 동작이 됩니다.
</Warning>

***

## when() 메서드 — 이벤트 트리거에 의한 등록

`when()` 메서드를 사용하면, 특정 이벤트가 발화되었을 때 프로바이더를 등록할 수 있습니다. 이것은, 잡 처리계 등 특정 컨텍스트에서만 필요한 프로바이더에 사용할 수 있습니다.

```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];
    }

    /**
     * サービスのバインディング解決ではなく、
     * 指定したイベントが発火したときにもプロバイダーを読み込む。
     */
    public function when(): array
    {
        return [
            \App\Events\ReportRequested::class,
        ];
    }
}
```

`when()`에 반환한 이벤트가 발화되면, 서비스가 직접 해결되지 않아도 프로바이더가 로드됩니다.

***

## 패키지 개발에서의 활용

서드파티 패키지로서 배포할 때도, 지연 프로바이더는 사용자의 애플리케이션의 성능에 공헌합니다.

### 권장 패턴

```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()`은 설정의 캐시 유무를 내부에서 체크하고 있으므로, 지연 프로바이더의 `register()` 안에서 호출해도 안전합니다. 다만 설정이 캐시된 경우에는 아무것도 하지 않습니다.
</Info>

### `runningInConsole()`로 Artisan 커맨드 등록을 분리한다

커맨드 등록은 Artisan 기동 시에만 필요하므로, `runningInConsole()`로 조건 분기합니다. 다만 커맨드를 제공하는 프로바이더를 지연으로 하는 경우에는, 커맨드 클래스도 `provides()`에 포함하거나, 커맨드 전용의 프로바이더를 별도로 준비해 주세요.

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

***

## Laravel 코어에서의 사용 예

Laravel의 코어 프로바이더의 다수는 지연 프로바이더입니다. 매 요청에서 사용하지 않는 서비스를 모두 즉시 로드하지 않기 위한 설계입니다.

| 프로바이더                        | 제공하는 서비스                                |
| ---------------------------- | --------------------------------------- |
| `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에서만 사용하는 커맨드를 제공하는 프로바이더
  </Accordion>

  <Accordion title="지연화에 적합하지 않은 서비스">
    * 라우트를 등록하는 프로바이더(`loadRoutesFrom` 등)
    * 상시 기능하는 미들웨어나 예외 핸들러를 등록하는 프로바이더
    * Eloquent의 글로벌 스코프나 옵저버를 등록하는 프로바이더
    * 요청의 대부분에서 사용되는 경량 서비스(지연의 오버헤드 쪽이 커지는 경우)
  </Accordion>
</AccordionGroup>

***

## 관련 페이지

<Columns cols={2}>
  <Card title="패키지 개발의 기초" icon="box" href="/ko/advanced/package-development">
    서비스 프로바이더를 핵심으로 한 Laravel 패키지의 개발 방법을 해설합니다.
  </Card>

  <Card title="패키지의 버전 호환성 관리" icon="layers" href="/ko/advanced/package-versioning">
    Laravel과 PHP의 메이저 버전 업그레이드에 대응하는 패키지의 유지보수 전략을 해설합니다.
  </Card>
</Columns>


## Related topics

- [서비스 프로바이더](/ko/service-providers.md)
- [요청 라이프사이클](/ko/lifecycle.md)
- [AI SDK의 커스텀 프로바이더 만들기](/ko/advanced/ai-sdk-custom-provider.md)
- [GeneratorCommand — 커스텀 make: 명령어 구현](/ko/advanced/generator-command.md)
- [Lottery 클래스](/ko/advanced/lottery.md)
