Laravel 기동 시에 등록되는 서비스 프로바이더는, 설령 그 기능을 요청 중에 한 번도 사용하지 않아도 매번 로드됩니다. 지연 서비스 프로바이더(Deferred Service Provider)는 이 문제를 해결하고, 서비스가 실제로 사용될 때까지 로드를 뒤로 미룰 수 있습니다.
이 페이지는 패키지 개발의 기초 의 지식을 전제로 하고 있습니다. 서비스 프로바이더의 기본적인 구조를 이해한 뒤 읽는 것을 권장합니다.
왜 지연 프로바이더가 필요한가
일반적인 서비스 프로바이더는 모든 요청에서 register()와 boot()가 호출됩니다. 메일 송신, 큐, 캐시 등, 모든 페이지에서 사용하지 않는 기능까지 매번 초기화하는 것은 낭비입니다.
// このプロバイダーはすべてのリクエストで 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() 메서드 하나를 갖는 심플한 인터페이스입니다.
namespace Illuminate\Contracts\Support ;
interface DeferrableProvider
{
public function provides ();
}
이 인터페이스를 구현하는 것만으로, 프로바이더는 지연 모드가 됩니다.
기본적인 구현
지연 프로바이더의 구현은 3단계입니다.
DeferrableProvider를 implements한다
use Illuminate\Contracts\Support\ DeferrableProvider ;
use Illuminate\Support\ ServiceProvider ;
class ReportServiceProvider extends ServiceProvider implements DeferrableProvider
{
// ...
}
register()에서 서비스를 바인드한다
일반적인 프로바이더와 마찬가지로 register()에 바인드를 기술합니다. 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' ));
});
}
provides()에서 등록한 서비스를 반환한다
provides()는 register()에서 바인드한 모든 서비스 를 반환해야 합니다. Laravel은 이 목록을 근거로 “어느 서비스를 요구했을 때 이 프로바이더를 로드할지”를 판단합니다.public function provides () : array
{
return [
ReportGenerator :: class ,
ReportRepository :: class ,
];
}
서비스 매니페스트의 구조
Laravel은 기동 시에 bootstrap/cache/services.php라는 매니페스트 파일을 생성합니다. 이 파일에 지연 프로바이더가 제공하는 서비스의 목록이 보관되어 있습니다.
// 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은 파일을 로드하지 않고 “이 서비스는 어느 프로바이더가 제공하는가”를 파악할 수 있습니다. 실제 프로바이더는 대상 서비스가 처음으로 해결될 때만 로드됩니다.
프로바이더를 추가·변경한 경우에는 매니페스트를 재생성해 주세요. php artisan optimize:clear
# または
php artisan clear-compiled
내부 동작의 흐름
provides() 메서드의 중요성
provides()에 등록 누락 이 있으면, 그 서비스는 영원히 해결되지 않습니다.
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()에 포함합니다.
class AnalyticsServiceProvider extends ServiceProvider implements DeferrableProvider
{
public $singletons = [
AnalyticsClient :: class => DefaultAnalyticsClient :: class ,
];
public function provides () : array
{
// $singletons に列挙したキーをすべて返す
return [
AnalyticsClient :: class ,
];
}
}
지연 프로바이더의 제약
지연 프로바이더는 컨테이너에의 바인딩 등록만 을 목적으로 한 프로바이더에 적합합니다. 이하와 같은 것을 boot()에서 하는 프로바이더는 지연화할 수 없습니다.
제약 이유 라우트 등록 라우트는 앱 기동 시에 해결되므로, 지연에서는 기능하지 않음 글로벌 미들웨어 등록 요청 처리 전에 등록이 필요 이벤트 리스너 등록(상시 필요한 것) 이벤트가 발화되기 전에 등록되어 있지 않으면 기능하지 않음 Blade 디렉티브의 추가 뷰의 컴파일 전에 등록이 필요
지연 프로바이더에 boot() 메서드를 쓰는 것은 가능하지만, 그 내용은 서비스가 해결될 때까지 실행되지 않습니다. 라우트나 미들웨어와 같은 “항상 필요한 처리”를 boot()에 쓰면, 예기치 않은 동작이 됩니다.
when() 메서드 — 이벤트 트리거에 의한 등록
when() 메서드를 사용하면, 특정 이벤트가 발화되었을 때 프로바이더를 등록할 수 있습니다. 이것은, 잡 처리계 등 특정 컨텍스트에서만 필요한 프로바이더에 사용할 수 있습니다.
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()에 반환한 이벤트가 발화되면, 서비스가 직접 해결되지 않아도 프로바이더가 로드됩니다.
패키지 개발에서의 활용
서드파티 패키지로서 배포할 때도, 지연 프로바이더는 사용자의 애플리케이션의 성능에 공헌합니다.
권장 패턴
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' ,
];
}
}
mergeConfigFrom()은 설정의 캐시 유무를 내부에서 체크하고 있으므로, 지연 프로바이더의 register() 안에서 호출해도 안전합니다. 다만 설정이 캐시된 경우에는 아무것도 하지 않습니다.
runningInConsole()로 Artisan 커맨드 등록을 분리한다
커맨드 등록은 Artisan 기동 시에만 필요하므로, runningInConsole()로 조건 분기합니다. 다만 커맨드를 제공하는 프로바이더를 지연으로 하는 경우에는, 커맨드 클래스도 provides()에 포함하거나, 커맨드 전용의 프로바이더를 별도로 준비해 주세요.
public function boot () : void
{
if ( $this -> app -> runningInConsole ()) {
$this -> commands ([
AnalyticsFlushCommand :: class ,
]);
}
}
Laravel 코어에서의 사용 예
Laravel의 코어 프로바이더의 다수는 지연 프로바이더입니다. 매 요청에서 사용하지 않는 서비스를 모두 즉시 로드하지 않기 위한 설계입니다.
프로바이더 제공하는 서비스 CacheServiceProvidercache, cache.store, RateLimiterQueueServiceProviderqueue, queue.worker, queue.failerMailServiceProvidermail.manager, mailerRedisServiceProviderredis, redis.connectionHashServiceProviderhash, hash.driverValidationServiceProvidervalidator, validation.presenceTranslationServiceProvidertranslatorBroadcastServiceProviderBroadcast
API 엔드포인트만의 애플리케이션에서는, MailServiceProvider나 BroadcastServiceProvider가 요청 중에 한 번도 로드되지 않는 경우도 있습니다.
지연화해야 할지의 판단 기준
모든 요청에서 사용되지는 않는 서비스(메일, 리포트, 외부 API 클라이언트 등)
초기화에 외부 접속이나 파일 로드가 필요한 서비스
무거운 오브젝트 그래프를 갖는 서비스
CLI에서만 사용하는 커맨드를 제공하는 프로바이더
라우트를 등록하는 프로바이더(loadRoutesFrom 등)
상시 기능하는 미들웨어나 예외 핸들러를 등록하는 프로바이더
Eloquent의 글로벌 스코프나 옵저버를 등록하는 프로바이더
요청의 대부분에서 사용되는 경량 서비스(지연의 오버헤드 쪽이 커지는 경우)
관련 페이지
패키지 개발의 기초 서비스 프로바이더를 핵심으로 한 Laravel 패키지의 개발 방법을 해설합니다.
패키지의 버전 호환성 관리 Laravel과 PHP의 메이저 버전 업그레이드에 대응하는 패키지의 유지보수 전략을 해설합니다.