Skip to main content
パッケージを通常のHTTPリクエストだけでなく、Octaneやキューワーカーでも使えるようにするには、サービスがどの期間、何を保持してよいかを決める必要があります。リクエストごとのデータをsingletonに保存すると、別の処理でも古いデータを参照する可能性があります。 このページではLaravel Framework v13.36.0 のコンテナとキューワーカー、およびOctaneの実装を確認し、サービスコンテナの基本的な登録方法をパッケージの長期保守に結び付けます。scoped() を選ぶだけで、オブジェクト間の参照や静的状態がすべてリセットされるわけではありません。

登録方法はサービスの保持期間で選ぶ

これは通常の解決を比較した表です。パラメーターを明示した解決やcontextual bindingなどの特殊な経路は別に確認してください。 bind() でも、取得したサービスを別のsingletonのプロパティに保存すれば、その参照は残ります。登録方法はコンテナの生成・再利用規則であって、利用者が保持するオブジェクトまで強制的に作り直す規則ではありません。

scopedは共有登録と除去対象の記録を組み合わせる

確認した Container::scoped() は、次の2つを行います。
  1. 登録キーを scopedInstances に記録する。
  2. singleton() を呼び、共有バインディングとして登録する。
インスタンス自体はsingletonと同じ instances に保存されます。forgetScopedInstances() は、記録されたキーについて保存済みインスタンスを unset() します。バインディングの生成処理は残るため、次の通常の解決で新しいオブジェクトを作れます。 この処理は、古いオブジェクトのプロパティを初期化したり、別オブジェクトが持つ参照を書き換えたりしません。参照が残っている古いオブジェクトは、引き続き利用できてしまいます。

境界を作るのは実行環境

コンテナ自身がHTTPリクエストやジョブの開始を監視しているわけではありません。
  • キューワーカー: QueueServiceProvider がスコープをリセットするコールバックをWorkerに渡します。Worker::daemon() は次のジョブを取得する前のループでこれを呼び、コールバック内で forgetScopedInstances() とFacadeの解決済みインスタンスのクリアなどを行います。
  • Octane: 公式の既定設定では OperationTerminated に FlushTemporaryContainerInstances を登録します。このリスナーが forgetScopedInstances() を呼びます。
  • 独自の常駐コマンドやループ: 単に make() を繰り返しても境界にはなりません。実行基盤側でライフサイクルを設計する必要があります。
通常のアプリケーション処理やパッケージの boot() から、独自判断で forgetScopedInstances() を呼ばないでください。コンテナ全体のscopedサービスに影響します。また、同期実行を含むすべてのジョブ経路が、常駐Workerと同じリセット経路を通るとは限りません。

処理内で共有する状態をscopedにする

配送パッケージが、1つのリクエスト・ジョブ内で配送先の識別子を共有したい例を考えます。HTTPのRequestを直接保持せず、必要な値だけを保存します。
パッケージのサービスプロバイダーでは、生成時に利用者の状態を読み取らず、空のcontextを登録します。
利用アプリケーションのコントローラーやジョブの handle() で、入力の検証や権限の確認を済ませた識別子を設定します。同じライフサイクルでこの型を解決する他のサービスは、同じcontextを参照できます。
このcontextは認可機能ではありません。識別子を設定できることと、その配送先へのアクセスが許可されていることは別です。また、scoped() はデータベースや外部キャッシュへの永続化でもありません。次のジョブに渡す値はジョブのデータとして明示的に渡します。
Octaneではサービスプロバイダーの register() と boot() はワーカーの初期起動時に実行されます。現在のユーザーや配送先を boot() でcontextに保存する設計にしないでください。処理ごとの値は、処理が始まってから設定します。

singletonからscopedへの参照は自動更新されない

次の登録は、contextだけをscopedにしても意図通りにはなりません。
DeliveryService がコンストラクターでcontextをプロパティに保存するなら、最初に生成したcontextを持ち続けます。コンテナからcontextの保存を除去しても、すでに生成済みの DeliveryService のプロパティは変わりません。 対策は、サービスの責務に合わせて選びます。
  1. 処理ごとに必要な値をメソッド引数で渡す。状態を共有する必要がなければ、これが単純です。
  2. 保持する側もscopedにする。DeliveryService とcontextの両方を処理単位で作り直します。
  3. 利用時に解決するresolverを渡す。singletonを維持する必要がある場合は、現在のコンテナから必要なサービスをその都度取得します。
3番目の例では、古いcontextをクロージャーに取り込むのではなく、呼び出す時点で解決します。
この例の DeliveryService は、コンストラクターで Closure を受け取り、操作のたびに呼び出すAPIへ変更する必要があります。コンストラクター内でresolverを一度だけ呼び、その結果を保存しては解決になりません。これは公式Octaneドキュメントのcontainer resolverを、パッケージ固有のcontextに応用した設計例です。

scopedで消えない状態も確認する

scoped() は、次の状態を自動的には除去しません。
  • パッケージ独自の静的プロパティに追加した値
  • 別のsingletonやグローバル変数に保存したcontextへの参照
  • singletonの内部に保持した「現在のユーザー」「現在の配送先」
  • 独自のFacade・キャッシュ層が保持する参照
標準の実行基盤によるリセットと、パッケージ独自の状態のリセットは区別します。処理ごとの状態を保持する場所を減らす方が、すべての実行環境で独自のクリア処理を維持するより単純です。

同じコンテナで境界をまたぐテストを書く

HTTPテストが毎回新しいアプリケーションを作るだけでは、常駐プロセスで同じコンテナを使い続ける問題を見逃すことがあります。まずはコンテナ単体で、同じ処理内の共有と境界後の再生成を検証します。 次のテストはPHPUnit形式です。最初のテストは公式のContainerテストと同様に forgetScopedInstances() を使い、Worker全体を起動せず境界の作用を確認します。
2番目は、参照が残る仕様を記録するテストです。実際のパッケージには、採用した対策で新しいcontextや値が使われることを検証するテストも追加します。 コンテナ単体テストは、Octaneのsandboxやキューワーカーの実際の実行経路を検証するものではありません。対応を表明する実行環境で連続処理を行う結合テストも必要です。登録方法やサービス実装を変更したデプロイでは、常駐プロセスを再読み込みして新しいコードを適用します。

関連ページ

パッケージのサービス解決後フック

afterResolvingと共有インスタンスの取得、再解決時の設定の違いを確認します。

Laravel Octane

Requestやコンテナの保持、常駐プロセスの運用上の注意点を確認します。

参照した一次情報

最終更新日 2026年10月14日