メインコンテンツまでスキップ

コンテキスト

はじめに

Laravelの「コンテキスト」機能を使用すると、アプリケーション内で実行されるリクエスト、ジョブ、およびコマンドを介して情報をキャプチャ、取得、共有することができます。このキャプチャされた情報は、アプリケーションによって書き込まれるログにも含まれ、ログエントリが書き込まれる前に発生した周囲のコード実行履歴に深い洞察を提供し、分散システム全体で実行フローを追跡できるようにします。

動作方法

Laravelのコンテキスト機能を理解する最良の方法は、組み込みのログ機能を使用して実際に動作させることです。始めるには、Contextファサードを使用してコンテキストに情報を追加することができます。この例では、ミドルウェアを使用して、すべての着信リクエストにリクエストURLと一意のトレースIDをコンテキストに追加します:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Context;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;

class AddContext
{
/**
* Handle an incoming request.
*/
public function handle(Request $request, Closure $next): Response
{
Context::add('url', $request->url());
Context::add('trace_id', Str::uuid()->toString());

return $next($request);
}
}

コンテキストに追加された情報は、リクエスト全体で書き込まれるログエントリに自動的にメタデータとして追加されます。コンテキストをメタデータとして追加することで、個々のログエントリに渡される情報をContextを介して共有される情報と区別することができます。たとえば、次のログエントリを書き込むとします:

Log::info('User authenticated.', ['auth_id' => Auth::id()]);

書き込まれたログには、ログエントリに渡されたauth_idが含まれますが、コンテキストのurltrace_idもメタデータとして含まれます:

User authenticated. {"auth_id":27} {"url":"https://example.com/login","trace_id":"e04e1a11-e75c-4db3-b5b5-cfef4ef56697"}

コンテキストに追加された情報は、キューにディスパッチされるジョブにも利用可能です。たとえば、コンテキストにいくつかの情報を追加した後にProcessPodcastジョブをキューにディスパッチするとします:

// In our middleware...
Context::add('url', $request->url());
Context::add('trace_id', Str::uuid()->toString());

// In our controller...
ProcessPodcast::dispatch($podcast);

ジョブがディスパッチされると、現在のコンテキストに格納されている情報がキャプチャされ、ジョブと共有されます。キャプチャされた情報は、ジョブが実行されている間に現在のコンテキストに再度注入されます。したがって、もし私たちのジョブのハンドルメソッドがログに書き込む場合:

class ProcessPodcast implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

// ...

/**
* Execute the job.
*/
public function handle(): void
{
Log::info('Processing podcast.', [
'podcast_id' => $this->podcast->id,
]);

// ...
}
}

結果として得られるログエントリには、元々ジョブをディスパッチしたリクエスト中にコンテキストに追加された情報が含まれます:

Processing podcast. {"podcast_id":95} {"url":"https://example.com/login","trace_id":"e04e1a11-e75c-4db3-b5b5-cfef4ef56697"}

Laravelのコンテキストの組み込みのログ関連機能に焦点を当ててきましたが、以下のドキュメントでは、コンテキストがHTTPリクエスト/キューイングされたジョブの境界を越えて情報を共有する方法や、ログエントリとは別に書き込まれない非表示のコンテキストデータを追加する方法を説明します。

コンテキストのキャプチャ

Contextファサードのaddメソッドを使用して、現在のコンテキストに情報を格納することができます:

use Illuminate\Support\Facades\Context;

Context::add('key', 'value');

一度に複数のアイテムを追加するには、addメソッドに連想配列を渡すことができます:

Context::add([
'first_key' => 'value',
'second_key' => 'value',
]);

addメソッドは、同じキーを共有する既存の値を上書きします。キーがすでに存在しない場合にのみコンテキストに情報を追加したい場合は、addIfメソッドを使用できます:

Context::add('key', 'first');

Context::get('key');
// "first"

Context::addIf('key', 'second');

Context::get('key');
// "first"

条件付きコンテキスト

whenメソッドを使用して、与えられた条件に基づいてコンテキストにデータを追加することができます。whenメソッドに提供された最初のクロージャは、与えられた条件がtrueに評価される場合に呼び出され、2番目のクロージャは条件がfalseに評価される場合に呼び出されます:

use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Context;

Context::when(
Auth::user()->isAdmin(),
fn ($context) => $context->add('permissions', Auth::user()->permissions),
fn ($context) => $context->add('permissions', []),
);

スタック

コンテキストは、追加された順序で格納されたデータのリストである「スタック」を作成する機能を提供します。pushメソッドを呼び出すことで、スタックに情報を追加することができます:

use Illuminate\Support\Facades\Context;

Context::push('breadcrumbs', 'first_value');

Context::push('breadcrumbs', 'second_value', 'third_value');

Context::get('breadcrumbs');
// [
// 'first_value',
// 'second_value',
// 'third_value',
// ]

スタックは、リクエストに関する履歴情報をキャプチャするのに役立ちます。たとえば、クエリが実行されるたびにスタックにプッシュするイベントリスナーを作成して、クエリSQLと期間をタプルとしてキャプチャすることができます。

use Illuminate\Support\Facades\Context;
use Illuminate\Support\Facades\DB;

DB::listen(function ($event) {
Context::push('queries', [$event->time, $event->sql]);
});

コンテキストの取得

Contextファサードのgetメソッドを使用して、コンテキストから情報を取得できます:

use Illuminate\Support\Facades\Context;

$value = Context::get('key');

onlyメソッドを使用して、コンテキスト内の情報のサブセットを取得できます:

$data = Context::only(['first_key', 'second_key']);

pullメソッドを使用して、コンテキストから情報を取得してすぐにコンテキストから削除できます:

$value = Context::pull('key');

コンテキストに格納されているすべての情報を取得したい場合は、allメソッドを呼び出すことができます:

$data = Context::all();

アイテムの存在を判定する

指定されたキーに対してコンテキストに値が格納されているかどうかを判定するには、hasメソッドを使用できます:

use Illuminate\Support\Facades\Context;

if (Context::has('key')) {
// ...
}

hasメソッドは、格納されている値に関係なくtrueを返します。たとえば、null値が格納されているキーも存在すると見なされます:

Context::add('key', null);

Context::has('key');
// true

コンテキストの削除

forgetメソッドを使用して、現在のコンテキストからキーとその値を削除できます:

use Illuminate\Support\Facades\Context;

Context::add(['first_key' => 1, 'second_key' => 2]);

Context::forget('first_key');

Context::all();

// ['second_key' => 2]

forgetメソッドに配列を提供することで、複数のキーを一度に削除できます:

Context::forget(['first_key', 'second_key']);

非表示コンテキスト

コンテキストは「非表示」データを保存する機能を提供します。この非表示情報はログに追加されず、上記で説明したデータ取得メソッドを介してアクセスできません。コンテキストは非表示コンテキスト情報とやり取りするための異なるセットのメソッドを提供します:

use Illuminate\Support\Facades\Context;

Context::addHidden('key', 'value');

Context::getHidden('key');
// 'value'

Context::get('key');
// null

「非表示」メソッドは、上記で説明した非非表示メソッドの機能と同じです:

Context::addHidden(/* ... */);
Context::addHiddenIf(/* ... */);
Context::pushHidden(/* ... */);
Context::getHidden(/* ... */);
Context::pullHidden(/* ... */);
Context::onlyHidden(/* ... */);
Context::allHidden(/* ... */);
Context::hasHidden(/* ... */);
Context::forgetHidden(/* ... */);

イベント

コンテキストは、コンテキストの水分補給および脱水化プロセスにフックするための2つのイベントをディスパッチします。

これらのイベントがどのように使用されるかを示すために、アプリケーションのミドルウェアで、着信HTTPリクエストのAccept-Languageヘッダーに基づいてapp.locale構成値を設定するとします。コンテキストのイベントを使用すると、リクエスト中にこの値をキャプチャし、キューで正しいapp.locale値を持つ通知が送信されることを保証できます。これを達成するために、非表示データを使用してコンテキストのイベントと非表示データを使用する方法を以下のドキュメントで説明します。

脱水化

ジョブがキューにディスパッチされるたびに、コンテキスト内のデータは「脱水化」され、ジョブのペイロードと共にキャプチャされます。 Context::dehydrating メソッドを使用すると、脱水化プロセス中に呼び出されるクロージャを登録できます。このクロージャ内で、キューに渡されるデータを変更することができます。

通常、AppServiceProvider クラスの boot メソッド内で dehydrating コールバックを登録する必要があります:

use Illuminate\Log\Context\Repository;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Context;

/**
* Bootstrap any application services.
*/
public function boot(): void
{
Context::dehydrating(function (Repository $context) {
$context->addHidden('locale', Config::get('app.locale'));
});
}
注記

dehydrating コールバック内で Context ファサードを使用しないでください。それにより、現在のプロセスのコンテキストが変更されます。コールバックに渡されたリポジトリにのみ変更を加えるようにしてください。

水分補給

キューに入れられたジョブがキューで実行を開始するたびに、ジョブと共有されたコンテキストが現在のコンテキストに「水分補給」されます。 Context::hydrated メソッドを使用すると、水分補給プロセス中に呼び出されるクロージャを登録できます。

通常、AppServiceProvider クラスの boot メソッド内で hydrated コールバックを登録する必要があります:

use Illuminate\Log\Context\Repository;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Context;

/**
* Bootstrap any application services.
*/
public function boot(): void
{
Context::hydrated(function (Repository $context) {
if ($context->hasHidden('locale')) {
Config::set('app.locale', $context->getHidden('locale'));
}
});
}
注記

hydrated コールバック内で Context ファサードを使用しないでください。代わりに、コールバックに渡されたリポジトリにのみ変更を加えるようにしてください。