callAfterResolving() は、この2つを組み合わせるためのprotectedメソッドです。
このページではLaravel Framework v13.35.0 の実装を確認し、パッケージ開発の基礎にある boot() からの拡張を掘り下げます。アプリケーション全体の起動完了を待つフックではなく、指定したサービスの解決に対するフックです。
未解決なら待ち、解決済みなら今も実行する
ServiceProvider::callAfterResolving() は、次の2段階の処理を行います。
- コンテナの
afterResolving()にコールバックを登録する。 resolved()がtrueならmake()で取得し、その場でもコールバックを呼ぶ。
make() しません。通常の解決では、コンテナがオブジェクトを構築し、extenderを適用し、resolving コールバックを呼んだ後に afterResolving コールバックを呼びます。
通常のsingleton取得では、コンテナは保存済みインスタンスを早期returnするため、取得のたびに
afterResolving が発火するわけではありません。単に afterResolving() を登録するだけでは、登録前に作られたsingletonへの設定が抜ける点が重要です。
バリデーションFactoryにルールを追加する
パッケージ独自の文字列ルールを提供する例として、validator の解決後にルールを登録します。公式の ValidationServiceProvider はこのキーをsingletonとして登録しており、プロバイダー自体は遅延読み込みに対応しています。
extend() は、Illuminate\Validation\Factory のルール登録APIです。後述するコンテナの extend() とは別のメソッドです。通常のカスタムルールは空値などで実行されない場合があるため、必須性は required で指定します。ルール自体の詳しい設計はカスタムバリデーションルールを参照してください。
Factory::extend() は同じルール名の配列要素を上書きするので、この例のように同じ処理を再登録してもエントリーが増えません。ただし、別パッケージのルールを上書きしないよう、公開するルール名にはパッケージ固有の接頭辞を付けます。
コールバックが実行されるのはFactoryの解決時です。ルールの検証用クロージャが実行されるのは、後でValidatorが対象の値を検証するときです。フックの登録時に入力値を検証するわけではありません。
対象のキーと解決済み判定をそろえる
コンテナの通常の解決イベントでは、登録したキーとの一致だけでなく、取得したオブジェクトの型に一致するコールバックも選ばれます。一方、callAfterResolving() がその場で使う resolved($name) は、別名を正規化したキーについて、解決済みフラグまたは保存済みインスタンスがあるかを調べます。生成済みオブジェクトをすべて検索する処理ではありません。
そのため、あるインターフェースやクラスが解決済みだからといって、別のキーでの resolved() も必ずtrueになるとは限りません。対象サービスの実際のバインディングとaliasを確認し、解決済みケースも同じキーでテストします。上の例では、公式プロバイダーが登録する validator を指定しています。
また、resolved() は「過去に解決された」という判定も含みます。現在のインスタンスが必ず残っている、という意味ではありません。
「一度だけ実行」を保証するAPIではない
callAfterResolving() は、コールバックを登録したままにします。その場で実行された後も、将来オブジェクトが新しく解決されれば登録済みコールバックが実行されます。さらに、メソッドを再度呼べばコールバック自体が追加されます。
特に、通常の非共有バインディングをすでに解決した場合は注意が必要です。確認した実装では次のようになります。
- 新しいコールバックを登録する。
resolved()がtrueなのでmake()を呼ぶ。make()が新しいオブジェクトを作り、その解決イベントでコールバックが実行される。make()から戻った同じオブジェクトに、その場のコールバックも実行される。
オブジェクトの置換には別のAPIを使う
afterResolving のコールバックの戻り値は、コンテナが返すオブジェクトの置換には使われません。デコレーターを返してサービスそのものを差し替えたい場合は、コンテナの extend() を検討します。このAPIのクロージャは変更後のサービスを返す契約です。
同様に、callAfterResolving() は別オブジェクトにすでに保存された依存関係をすべて更新する仕組みでもありません。再バインド時の依存関係の更新が目的なら、公式ドキュメントの rebinding() と対象クラスの設計を確認します。
サービスを本当に遅延読み込みするプロバイダーの要件はDeferrableProviderを参照してください。フックを使うことと、そのフックを登録するプロバイダー自身を遅延読み込みにすることは別です。
更新時に確認する組み合わせ
パッケージのリリース前には、通常の起動順だけでなく、先に別プロバイダーがサービスを利用した場合も検証します。
アプリケーション全体で一度だけ使ったことを静的フラグで記録すると、新しいコンテナやテストで作り直されたサービスへの設定が抜ける可能性があります。設定の冪等性は、できるだけ対象オブジェクトや登録するキーの単位で確保します。
関連ページ
パッケージビューの上書きと更新
loadViewsFrom()が解決後フックを使ってビュー名前空間を登録する例を確認します。
パッケージ翻訳の上書きと更新
Translatorの名前空間登録と、読み込み済み翻訳の扱いを確認します。
参照した一次情報
- Laravel公式ドキュメント: パッケージ開発
- Laravel公式ドキュメント: Container events・Extending bindings・Rebinding
- Laravel公式ドキュメント: カスタムバリデーションルール
- ServiceProvider: callAfterResolving()とリソース登録での利用
- Container: resolved()・resolve()・afterResolving()
- ValidationServiceProvider: validatorのsingleton登録
- Validation Factory: extend()によるルール登録