コンテキスト
イントロダクション
Laravelの「コンテキスト」機能を使用すると、アプリケーション内で実行されるリクエスト、ジョブ、コマンド全体で情報を取得、取得、共有できます。この取得された情報は、アプリケーションによって書き込まれるログにも含まれるため、ログエントリが書き込まれる前に発生した周辺のコード実行履歴をより深く洞察でき、分散システム全体で実行フローを追跡できます。
仕組み
Laravelのコンテキスト機能を理解する最良の方法は、組み込みのログ機能を使用して実際に動作させることです。まず、Contextファサードを使用してコンテキストに情報を追加できます。この例では、ミドルウェアを使用して、受信リクエストごとにリクエストURLと一意のトレースIDをコンテキストに追加します。
1<?php 2 3namespace App\Http\Middleware; 4 5use Closure; 6use Illuminate\Http\Request; 7use Illuminate\Support\Facades\Context; 8use Illuminate\Support\Str; 9use Symfony\Component\HttpFoundation\Response;10 11class AddContext12{13 /**14 * Handle an incoming request.15 */16 public function handle(Request $request, Closure $next): Response17 {18 Context::add('url', $request->url());19 Context::add('trace_id', Str::uuid()->toString());20 21 return $next($request);22 }23}
コンテキストに追加された情報は、リクエスト全体で書き込まれるログエントリにメタデータとして自動的に追加されます。コンテキストをメタデータとして追加すると、個々のログエントリに渡される情報とContextを介して共有される情報を区別できます。たとえば、次のログエントリを書き込むとします。
1Log::info('User authenticated.', ['auth_id' => Auth::id()]);
書き込まれたログには、ログエントリに渡されたauth_idが含まれますが、メタデータとしてコンテキストのurlとtrace_idも含まれます。
1User authenticated. {"auth_id":27} {"url":"https://example.com/login","trace_id":"e04e1a11-e75c-4db3-b5b5-cfef4ef56697"}
コンテキストに追加された情報は、キューにディスパッチされたジョブでも利用できます。たとえば、コンテキストに情報を追加した後、ProcessPodcastジョブをキューにディスパッチしたとします。
1// In our middleware...2Context::add('url', $request->url());3Context::add('trace_id', Str::uuid()->toString());4 5// In our controller...6ProcessPodcast::dispatch($podcast);
ジョブがディスパッチされると、現在コンテキストに保存されているすべての情報がキャプチャされ、ジョブと共有されます。キャプチャされた情報は、ジョブの実行中に現在のコンテキストにハイドレート(復元)されます。したがって、ジョブのhandleメソッドがログに書き込む場合、
1class ProcessPodcast implements ShouldQueue 2{ 3 use Queueable; 4 5 // ... 6 7 /** 8 * Execute the job. 9 */10 public function handle(): void11 {12 Log::info('Processing podcast.', [13 'podcast_id' => $this->podcast->id,14 ]);15 16 // ...17 }18}
結果のログエントリには、最初にジョブをディスパッチしたリクエスト中にコンテキストに追加された情報が含まれます。
1Processing podcast. {"podcast_id":95} {"url":"https://example.com/login","trace_id":"e04e1a11-e75c-4db3-b5b5-cfef4ef56697"}
ここまでLaravelのコンテキストの組み込みのログ関連機能に焦点を当ててきましたが、以下のドキュメントでは、コンテキストがHTTPリクエストとキュー投入されたジョブの境界を越えて情報を共有する方法や、ログエントリに書き込まれない隠しコンテキストデータを追加する方法についても説明します。
コンテキストの取得
Contextファサードのaddメソッドを使用して、現在のコンテキストに情報を保存できます。
1use Illuminate\Support\Facades\Context;2 3Context::add('key', 'value');
一度に複数のアイテムを追加するには、addメソッドに連想配列を渡します。
1Context::add([2 'first_key' => 'value',3 'second_key' => 'value',4]);
addメソッドは、同じキーを共有する既存の値を上書きします。キーがまだ存在しない場合にのみコンテキストに情報を追加したい場合は、addIfメソッドを使用できます。
1Context::add('key', 'first');2 3Context::get('key');4// "first"5 6Context::addIf('key', 'second');7 8Context::get('key');9// "first"
条件付きコンテキスト
whenメソッドを使用して、特定の条件に基づいてコンテキストにデータを追加できます。whenメソッドに指定した最初のクロージャは、指定した条件がtrueと評価された場合に呼び出され、2番目のクロージャは条件がfalseと評価された場合に呼び出されます。
1use Illuminate\Support\Facades\Auth;2use Illuminate\Support\Facades\Context;3 4Context::when(5 Auth::user()->isAdmin(),6 fn ($context) => $context->add('permissions', Auth::user()->permissions),7 fn ($context) => $context->add('permissions', []),8);
スタック
コンテキストは、「スタック」を作成する機能を提供します。スタックは、追加された順序で保存されるデータのリストです。pushメソッドを呼び出して、スタックに情報を追加できます。
1use Illuminate\Support\Facades\Context; 2 3Context::push('breadcrumbs', 'first_value'); 4 5Context::push('breadcrumbs', 'second_value', 'third_value'); 6 7Context::get('breadcrumbs'); 8// [ 9// 'first_value',10// 'second_value',11// 'third_value',12// ]
スタックは、アプリケーション全体で発生しているイベントなど、リクエストに関する履歴情報を取得するのに役立ちます。たとえば、クエリが実行されるたびにスタックにプッシュするイベントリスナを作成し、クエリSQLと実行時間をタプルとして取得できます。
1use Illuminate\Support\Facades\Context;2use Illuminate\Support\Facades\DB;3 4DB::listen(function ($event) {5 Context::push('queries', [$event->time, $event->sql]);6});
stackContainsメソッドとhiddenStackContainsメソッドを使用して、値がスタック内にあるかどうかを判断できます。
1if (Context::stackContains('breadcrumbs', 'first_value')) {2 //3}4 5if (Context::hiddenStackContains('secrets', 'first_value')) {6 //7}
stackContainsメソッドとhiddenStackContainsメソッドは、2番目の引数としてクロージャも受け入れ、値の比較操作をより細かく制御できます。
1use Illuminate\Support\Facades\Context;2use Illuminate\Support\Str;3 4return Context::stackContains('breadcrumbs', function ($value) {5 return Str::startsWith($value, 'query_');6});
コンテキストの取得
Contextファサードのgetメソッドを使用して、コンテキストから情報を取得できます。
1use Illuminate\Support\Facades\Context;2 3$value = Context::get('key');
onlyメソッドを使用して、コンテキスト内の情報の一部を取得できます。
1$data = Context::only(['first_key', 'second_key']);
pullメソッドを使用して、コンテキストから情報を取得し、すぐにコンテキストから削除できます。
1$value = Context::pull('key');
コンテキストデータがスタックに保存されている場合は、popメソッドを使用してスタックからアイテムをポップできます。
1Context::push('breadcrumbs', 'first_value', 'second_value');2 3Context::pop('breadcrumbs')4// second_value5 6Context::get('breadcrumbs');7// ['first_value']
コンテキストに保存されているすべての情報を取得したい場合は、allメソッドを呼び出せます。
1$data = Context::all();
アイテムの存在判定
hasメソッドとmissingメソッドを使用して、コンテキストに特定のキーの値が保存されているかどうかを判断できます。
1use Illuminate\Support\Facades\Context;2 3if (Context::has('key')) {4 // ...5}6 7if (Context::missing('key')) {8 // ...9}
hasメソッドは、保存されている値に関係なくtrueを返します。したがって、たとえば、null値を持つキーは存在すると見なされます。
1Context::add('key', null);2 3Context::has('key');4// true
コンテキストの削除
forgetメソッドを使用して、現在のコンテキストからキーとその値を削除できます。
1use Illuminate\Support\Facades\Context;2 3Context::add(['first_key' => 1, 'second_key' => 2]);4 5Context::forget('first_key');6 7Context::all();8 9// ['second_key' => 2]
forgetメソッドに配列を指定することで、一度に複数のキーを削除できます。
1Context::forget(['first_key', 'second_key']);
隠しコンテキスト
コンテキストは、「隠し」データを保存する機能を提供します。この隠し情報はログに追加されず、上記で説明したデータ取得メソッドを介してアクセスできません。コンテキストは、隠しコンテキスト情報を操作するための別のメソッドセットを提供します。
1use Illuminate\Support\Facades\Context;2 3Context::addHidden('key', 'value');4 5Context::getHidden('key');6// 'value'7 8Context::get('key');9// null
「隠し」メソッドは、上記で説明した非隠しメソッドの機能を反映しています。
1Context::addHidden(/* ... */); 2Context::addHiddenIf(/* ... */); 3Context::pushHidden(/* ... */); 4Context::getHidden(/* ... */); 5Context::pullHidden(/* ... */); 6Context::popHidden(/* ... */); 7Context::onlyHidden(/* ... */); 8Context::allHidden(/* ... */); 9Context::hasHidden(/* ... */);10Context::forgetHidden(/* ... */);
イベント
コンテキストは、コンテキストのハイドレートおよびデハイドレートプロセスにフックできる2つのイベントをディスパッチします。
これらのイベントの使用方法を説明するために、アプリケーションのミドルウェアで、受信HTTPリクエストのAccept-Languageヘッダに基づいてapp.locale設定値を設定するとします。コンテキストのイベントを使用すると、リクエスト中にこの値をキャプチャし、キュー上で復元できるため、キューで送信される通知が正しいapp.locale値を持つようになります。コンテキストのイベントと隠しデータを使用してこれを実現でき、以下のドキュメントで説明します。
デハイドレート
ジョブがキューにディスパッチされるたびに、コンテキスト内のデータは「デハイドレート」され、ジョブのペイロードとともにキャプチャされます。Context::dehydratingメソッドを使用すると、デハイドレートプロセス中に呼び出されるクロージャを登録できます。このクロージャ内で、キュー投入されたジョブと共有されるデータに変更を加えられます。
通常、アプリケーションのAppServiceProviderクラスのbootメソッド内でdehydratingコールバックを登録する必要があります。
1use Illuminate\Log\Context\Repository; 2use Illuminate\Support\Facades\Config; 3use Illuminate\Support\Facades\Context; 4 5/** 6 * Bootstrap any application services. 7 */ 8public function boot(): void 9{10 Context::dehydrating(function (Repository $context) {11 $context->addHidden('locale', Config::get('app.locale'));12 });13}
現在のプロセスのコンテキストが変更されてしまうため、dehydratingコールバック内でContextファサードを使用しないでください。コールバックに渡されたリポジトリにのみ変更を加えるようにしてください。
ハイドレート
キュー投入されたジョブがキューで実行を開始するたびに、ジョブと共有されていたすべてのコンテキストが現在のコンテキストに「ハイドレート」されます。Context::hydratedメソッドを使用すると、ハイドレートプロセス中に呼び出されるクロージャを登録できます。
通常、アプリケーションのAppServiceProviderクラスのbootメソッド内でhydratedコールバックを登録する必要があります。
1use Illuminate\Log\Context\Repository; 2use Illuminate\Support\Facades\Config; 3use Illuminate\Support\Facades\Context; 4 5/** 6 * Bootstrap any application services. 7 */ 8public function boot(): void 9{10 Context::hydrated(function (Repository $context) {11 if ($context->hasHidden('locale')) {12 Config::set('app.locale', $context->getHidden('locale'));13 }14 });15}
hydratedコールバック内でContextファサードを使用せず、代わりにコールバックに渡されたリポジトリにのみ変更を加えるようにしてください。