コンテキスト
リクエストやジョブをまたいで情報を持ち回り、ログにも自動で添えるコンテキストの使い方を、追加・取得・削除・隠しデータ・イベントまで説明します。
コンテキストは、リクエスト・ジョブ(あとで動かす仕事)・コマンドをまたいで、情報をしまったり、取り出したり、分け合ったりできるしくみです。荷物に付ける「付箋」のように、ある情報を、処理の流れの先まで持っていけます。記録した情報は、アプリが書くログにも自動で入ります。ログが書かれる前に、どんな流れで動いてきたのかが分かるので、複数のサーバーにまたがる処理も追いやすくなります。
しくみ#
コンテキストの働きは、最初から入っているログの機能で見るのが分かりやすいです。Context ファサード(Context::add() のように、クラス名と :: で機能を呼べる窓口)で、コンテキストに情報を足せます。この例では、ミドルウェア(リクエストが処理に届く前に、間に入って確かめる処理)を使って、リクエストごとに、リクエストの URL と、重ならない追跡用の ID(trace 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 に加えて、コンテキストの url と trace_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);
ジョブを出すとき、いまコンテキストに入っている情報が、ジョブと一緒に持ち出されます。そして、ジョブが動いている間、その情報が、いまのコンテキストに戻されます。そのため、ジョブの handle メソッドがログを書くと、次のようになります。
class ProcessPodcast implements ShouldQueue
{
use Queueable;
// ...
/**
* 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"}
ここまでは、ログの機能を中心に見てきました。この先では、ほかの使い方も説明します。HTTP のリクエストとキューのジョブのあいだで情報を分け合う方法や、ログには書かれない「隠しデータ」の足し方などです。
コンテキストに足す#
Context ファサードの add で、いまのコンテキストに情報をしまえます。
use Illuminate\Support\Facades\Context;
Context::add('key', 'value');
連想配列(名前と値の組の並び)を渡すと、いくつもの項目を、まとめて足せます。
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"
コンテキストには、キーの値を増やしたり減らしたりする、便利なメソッドもあります。どちらも、1つ目の引数に、数えるキーを渡します。2つ目の引数で、増減の量も決められます。
Context::increment('records_added');
Context::increment('records_added', 5);
Context::decrement('records_added');
Context::decrement('records_added', 5);
条件でコンテキストに足す#
when を使うと、条件に応じて、コンテキストにデータを足せます。条件が true のときは1つ目のクロージャ(名前のない関数)が、false のときは2つ目のクロージャが動きます。
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', []),
);
期間を決めてコンテキストを変える(scope)#
scope を使うと、渡したコールバック(ここではクロージャ)が動いている間だけ、コンテキストを一時的に変えられます。コールバックが終わると、コンテキストは元に戻ります。クロージャが動いている間だけコンテキストに足したいデータは、2つ目と3つ目の引数で渡せます。
use Illuminate\Support\Facades\Context;
use Illuminate\Support\Facades\Log;
Context::add('trace_id', 'abc-999');
Context::addHidden('user_id', 123);
Context::scope(
function () {
Context::add('action', 'adding_friend');
$userId = Context::getHidden('user_id');
Log::debug("Adding user [{$userId}] to friends list.");
// Adding user [987] to friends list. {"trace_id":"abc-999","user_name":"taylor_otwell","action":"adding_friend"}
},
data: ['user_name' => 'taylor_otwell'],
hidden: ['user_id' => 987],
);
Context::all();
// [
// 'trace_id' => 'abc-999',
// ]
Context::allHidden();
// [
// 'user_id' => 123,
// ]
注意
scope のクロージャの中で、コンテキストにあるオブジェクトを書き換えると、その変更は、scope の外にも残ります。
スタック#
コンテキストでは、「スタック」を作れます。スタックは、足した順に並べて入れておく、データのリストです。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 が動くたびにスタックに足して、SQL とかかった時間の組を記録できます。
use Illuminate\Support\Facades\Context;
use Illuminate\Support\Facades\DB;
// In AppServiceProvider.php...
DB::listen(function ($event) {
Context::push('queries', [$event->time, $event->sql]);
});
スタックに値が入っているかは、stackContains と hiddenStackContains で調べられます。
if (Context::stackContains('breadcrumbs', 'first_value')) {
//
}
if (Context::hiddenStackContains('secrets', 'first_value')) {
//
}
stackContains と hiddenStackContains は、2つ目の引数にクロージャも渡せます。値の比べ方を、細かく決められます。
use Illuminate\Support\Facades\Context;
use Illuminate\Support\Str;
return Context::stackContains('breadcrumbs', function ($value) {
return Str::startsWith($value, 'query_');
});
足すためのメソッドは、次のとおりです。
| メソッド | 働き |
|---|---|
add |
情報を足す(同じキーは上書き) |
addIf |
キーがまだないときだけ足す |
increment |
キーの値を増やす |
decrement |
キーの値を減らす |
when |
条件に応じて、足す内容を変える |
scope |
コールバックが動いている間だけ、コンテキストを変える |
push |
スタックに値を足す |
stackContains |
スタックに値が入っているか調べる |
hiddenStackContains |
隠しデータのスタックに値が入っているか調べる |
コンテキストを取り出す#
Context ファサードの get で、コンテキストから情報を取り出せます。
use Illuminate\Support\Facades\Context;
$value = Context::get('key');
only と except を使うと、コンテキストの情報の一部だけを取り出せます。
$data = Context::only(['first_key', 'second_key']);
$data = Context::except(['first_key']);
pull は、コンテキストから情報を取り出して、すぐにコンテキストから消します。
$value = Context::pull('key');
コンテキストのデータがスタックに入っているときは、pop で、スタックから項目を取り出せます。
Context::push('breadcrumbs', 'first_value', 'second_value');
Context::pop('breadcrumbs');
// second_value
Context::get('breadcrumbs');
// ['first_value']
remember と rememberHidden は、コンテキストから情報を取り出します。ほしい情報がなければ、渡したクロージャが返す値を、コンテキストに入れます。
$permissions = Context::remember(
'user-permissions',
fn () => $user->permissions,
);
コンテキストにある情報を、全部取り出したいときは、all を呼びます。
$data = Context::all();
項目があるか調べる#
has と missing で、キーに値が入っているかを調べられます。
use Illuminate\Support\Facades\Context;
if (Context::has('key')) {
// ...
}
if (Context::missing('key')) {
// ...
}
has は、入っている値が何でも true を返します。たとえば、値が null のキーも、「ある」とみなされます。
Context::add('key', null);
Context::has('key');
// true
取り出すためのメソッドは、次のとおりです。
| メソッド | 働き |
|---|---|
get |
キーの値を取り出す |
only |
決めたキーだけを取り出す |
except |
決めたキー以外を取り出す |
pull |
取り出して、コンテキストから消す |
pop |
スタックの最後の項目を取り出す |
remember |
なければ、クロージャの値を入れて、その値を返す |
rememberHidden |
隠しデータで、remember と同じことをする |
all |
全部の情報を取り出す |
has |
キーがあるか調べる(値が null でも「ある」) |
missing |
キーがないか調べる |
コンテキストを消す#
forget は、いまのコンテキストから、キーとその値を消します。
use Illuminate\Support\Facades\Context;
Context::add(['first_key' => 1, 'second_key' => 2]);
Context::forget('first_key');
Context::all();
// ['second_key' => 2]
配列を渡すと、いくつものキーを、まとめて消せます。
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::popHidden(/* ... */);
Context::onlyHidden(/* ... */);
Context::exceptHidden(/* ... */);
Context::allHidden(/* ... */);
Context::hasHidden(/* ... */);
Context::missingHidden(/* ... */);
Context::forgetHidden(/* ... */);
| 隠しのメソッド | 対応する隠しでないメソッド |
|---|---|
addHidden |
add |
addHiddenIf |
addIf |
pushHidden |
push |
getHidden |
get |
pullHidden |
pull |
popHidden |
pop |
onlyHidden |
only |
exceptHidden |
except |
allHidden |
all |
hasHidden |
has |
missingHidden |
missing |
forgetHidden |
forget |
イベント#
コンテキストは、2つのイベント(「起きた」という知らせ)を出します。このイベントを使うと、コンテキストを持ち出すとき(デハイドレート)と、戻すとき(ハイドレート)に、自分の処理を差し込めます。
使い方の例を考えてみます。アプリのミドルウェアで、届いたリクエストの Accept-Language ヘッダー(相手の言語の希望)を見て、app.locale という設定値を決めているとします。コンテキストのイベントと隠しデータを使えば、この値をリクエストの間に記録しておき、キューのジョブの中で元に戻せます。そうすれば、キューから送る通知でも、正しい app.locale の値が使われます。
デハイドレート(持ち出すとき)#
ジョブをキューに出すたびに、コンテキストのデータは「デハイドレート」(持ち出しの形に整えること)されて、ジョブの中身と一緒に持ち出されます。Context::dehydrating で、そのときに動くクロージャを登録できます。このクロージャの中で、キューのジョブに共有するデータを変えられます。
dehydrating のコールバックは、ふつう、アプリの AppServiceProvider クラスの boot メソッドで登録します。
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 で、そのときに動くクロージャを登録できます。
hydrated のコールバックも、ふつう、アプリの AppServiceProvider クラスの boot メソッドで登録します。
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 ファサードは使わず、コールバックに渡されるリポジトリだけを変えてください。
| メソッド | 働き |
|---|---|
Context::dehydrating |
ジョブをキューに出すとき(持ち出すとき)に動く処理を登録する |
Context::hydrated |
ジョブが動き始めるとき(戻すとき)に動く処理を登録する |
関連するページ#
公式ドキュメント(英語)
2026年10月5日時点の内容をもとに、日本語でまとめています。