本文へ移動
Laravel Tips

コンテキスト

リクエストやジョブをまたいで情報を持ち回り、ログにも自動で添えるコンテキストの使い方を、追加・取得・削除・隠しデータ・イベントまで説明します。

コンテキストは、リクエスト・ジョブ(あとで動かす仕事)・コマンドをまたいで、情報をしまったり、取り出したり、分け合ったりできるしくみです。荷物に付ける「付箋」のように、ある情報を、処理の流れの先まで持っていけます。記録した情報は、アプリが書くログにも自動で入ります。ログが書かれる前に、どんな流れで動いてきたのかが分かるので、複数のサーバーにまたがる処理も追いやすくなります。

しくみ#

コンテキストの働きは、最初から入っているログの機能で見るのが分かりやすいです。Context ファサード(Context::add() のように、クラス名と :: で機能を呼べる窓口)で、コンテキストに情報を足せます。この例では、ミドルウェア(リクエストが処理に届く前に、間に入って確かめる処理)を使って、リクエストごとに、リクエストの URL と、重ならない追跡用の ID(trace ID)を、コンテキストに足します。

php
<?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 で共有した情報を、見分けられます。たとえば、次のログを書いたとします。

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

書かれたログには、ログに渡した auth_id に加えて、コンテキストの url と trace_id も、メタデータとして入ります。

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

コンテキストに足した情報は、キュー(時間のかかる仕事の順番待ちの列)に出したジョブにも渡ります。たとえば、コンテキストに情報を足したあと、ProcessPodcast というジョブをキューに出すとします。

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

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

ジョブを出すとき、いまコンテキストに入っている情報が、ジョブと一緒に持ち出されます。そして、ジョブが動いている間、その情報が、いまのコンテキストに戻されます。そのため、ジョブの handle メソッドがログを書くと、次のようになります。

php
class ProcessPodcast implements ShouldQueue
{
    use Queueable;

    // ...

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

        // ...
    }
}

できたログには、そのジョブを出したリクエストの間に、コンテキストへ足された情報が入っています。

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

ここまでは、ログの機能を中心に見てきました。この先では、ほかの使い方も説明します。HTTP のリクエストとキューのジョブのあいだで情報を分け合う方法や、ログには書かれない「隠しデータ」の足し方などです。

コンテキストに足す#

Context ファサードの add で、いまのコンテキストに情報をしまえます。

php
use Illuminate\Support\Facades\Context;

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

連想配列(名前と値の組の並び)を渡すと、いくつもの項目を、まとめて足せます。

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

add は、同じキーがあると、値を上書きします。キーがまだないときだけ足したいなら、addIf を使います。

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

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

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

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

コンテキストには、キーの値を増やしたり減らしたりする、便利なメソッドもあります。どちらも、1つ目の引数に、数えるキーを渡します。2つ目の引数で、増減の量も決められます。

php
Context::increment('records_added');
Context::increment('records_added', 5);

Context::decrement('records_added');
Context::decrement('records_added', 5);

条件でコンテキストに足す#

when を使うと、条件に応じて、コンテキストにデータを足せます。条件が true のときは1つ目のクロージャ(名前のない関数)が、false のときは2つ目のクロージャが動きます。

php
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つ目の引数で渡せます。

php
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 を呼ぶと、スタックに情報を足せます。

php
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 とかかった時間の組を記録できます。

php
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 で調べられます。

php
if (Context::stackContains('breadcrumbs', 'first_value')) {
    //
}

if (Context::hiddenStackContains('secrets', 'first_value')) {
    //
}

stackContains と hiddenStackContains は、2つ目の引数にクロージャも渡せます。値の比べ方を、細かく決められます。

php
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 で、コンテキストから情報を取り出せます。

php
use Illuminate\Support\Facades\Context;

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

only と except を使うと、コンテキストの情報の一部だけを取り出せます。

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

$data = Context::except(['first_key']);

pull は、コンテキストから情報を取り出して、すぐにコンテキストから消します。

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

コンテキストのデータがスタックに入っているときは、pop で、スタックから項目を取り出せます。

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

Context::pop('breadcrumbs');
// second_value

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

remember と rememberHidden は、コンテキストから情報を取り出します。ほしい情報がなければ、渡したクロージャが返す値を、コンテキストに入れます。

php
$permissions = Context::remember(
    'user-permissions',
    fn () => $user->permissions,
);

コンテキストにある情報を、全部取り出したいときは、all を呼びます。

php
$data = Context::all();

項目があるか調べる#

has と missing で、キーに値が入っているかを調べられます。

php
use Illuminate\Support\Facades\Context;

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

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

has は、入っている値が何でも true を返します。たとえば、値が null のキーも、「ある」とみなされます。

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

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

取り出すためのメソッドは、次のとおりです。

メソッド 働き
get キーの値を取り出す
only 決めたキーだけを取り出す
except 決めたキー以外を取り出す
pull 取り出して、コンテキストから消す
pop スタックの最後の項目を取り出す
remember なければ、クロージャの値を入れて、その値を返す
rememberHidden 隠しデータで、remember と同じことをする
all 全部の情報を取り出す
has キーがあるか調べる(値が null でも「ある」)
missing キーがないか調べる

コンテキストを消す#

forget は、いまのコンテキストから、キーとその値を消します。

php
use Illuminate\Support\Facades\Context;

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

Context::forget('first_key');

Context::all();

// ['second_key' => 2]

配列を渡すと、いくつものキーを、まとめて消せます。

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

隠しデータ#

コンテキストには、「隠し」のデータをしまう機能があります。隠しの情報は、ログに付かず、上で説明した取り出しのメソッドでも取り出せません。隠しの情報を扱うには、別のメソッドを使います。

php
use Illuminate\Support\Facades\Context;

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

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

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

「隠し」のメソッドは、上で説明した隠しでないメソッドと、同じ働きをします。

php
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 メソッドで登録します。

php
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 メソッドで登録します。

php
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日時点の内容をもとに、日本語でまとめています。

ページの一覧