> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Context（脈絡）

> 說明如何使用 Laravel 的 Context 功能，在請求、Job 與指令之間共享資訊，並自動附加到日誌中。

## Context 是什麼

Laravel 的 Context 功能是一種在請求、佇列 Job 與指令執行之間記錄、共享資訊的機制。
透過 `Illuminate\Support\Facades\Context` Facade 加入的資訊，會自動附加到應用程式所寫出的每筆日誌條目上。

如此一來，就能清楚區分傳給個別日誌呼叫的資訊，以及 Context 中所保有的共享資訊。
特別適合用於分散式系統或使用佇列的架構中做追蹤（tracing）。

### 脈絡的傳遞流程

```mermaid theme={null}
sequenceDiagram
    participant Client
    participant Middleware
    participant Controller
    participant Queue
    participant Job

    Client->>Middleware: HTTP 請求
    Middleware->>Middleware: Context::add('trace_id', uuid)
    Middleware->>Controller: next($request)
    Controller->>Controller: Log::info(...) — 自動附加 trace_id
    Controller->>Queue: ProcessPodcast::dispatch()
    Queue->>Job: 序列化脈絡並傳送
    Job->>Job: 還原脈絡 (Hydrate)
    Job->>Job: Log::info(...) — 自動附加 trace_id
```

## 基本用法

最典型的用法就是在中介軟體中設定 `trace_id`。之後所有的日誌條目都會自動包含它。

<Steps>
  <Step title="建立中介軟體">
    ```shell theme={null}
    php artisan make:middleware AddContext
    ```
  </Step>

  <Step title="在 Context 中加入追蹤 ID">
    ```php theme={null}
    <?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
    {
        public function handle(Request $request, Closure $next): Response
        {
            Context::add('url', $request->url());
            Context::add('trace_id', Str::uuid()->toString());

            return $next($request);
        }
    }
    ```
  </Step>

  <Step title="註冊中介軟體">
    在 `bootstrap/app.php` 中以全域中介軟體的形式註冊。

    ```php theme={null}
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->append(\App\Http\Middleware\AddContext::class);
    })
    ```
  </Step>
</Steps>

完成上述設定後，在控制器或服務中寫入的日誌會自動附加 `url` 與 `trace_id`。

```php theme={null}
Log::info('User authenticated.', ['auth_id' => Auth::id()]);
```

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

## 寫入脈絡

### add — 加入值

```php theme={null}
use Illuminate\Support\Facades\Context;

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

// 一次加入多筆
Context::add([
    'first_key'  => 'value',
    'second_key' => 'value',
]);
```

`add` 會覆蓋既有的 key。若只想在 key 不存在時才加入，可使用 `addIf`。

```php theme={null}
Context::add('key', 'first');
Context::addIf('key', 'second');

Context::get('key');
// "first" — 不會被覆蓋
```

### increment / decrement — 管理計數器

專門用於增減數值的方法。第 2 個參數可指定變化量。

```php theme={null}
Context::increment('records_added');
Context::increment('records_added', 5);

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

### when — 有條件地加入

`when` 方法可在條件為 `true` 或 `false` 時分別加入不同的資料。

```php theme={null}
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 — 加入到堆疊

Context 支援以 list 形式保存的「堆疊」。
使用 `push` 時資料會按照加入順序堆疊起來。

```php theme={null}
Context::push('breadcrumbs', 'first_value');
Context::push('breadcrumbs', 'second_value', 'third_value');

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

以下範例是用堆疊記錄查詢的執行紀錄。

```php theme={null}
use Illuminate\Support\Facades\Context;
use Illuminate\Support\Facades\DB;

// 於 AppServiceProvider.php 的 boot 方法註冊
DB::listen(function ($event) {
    Context::push('queries', [$event->time, $event->sql]);
});
```

## 讀取脈絡

### get / all

```php theme={null}
$value = Context::get('key');

// 取得全部
$data = Context::all();
```

### only / except — 只取部分

```php theme={null}
$data = Context::only(['first_key', 'second_key']);

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

### pull / pop — 取得並移除

`pull` 會在讀取 key 的值後，同時從脈絡中移除。

```php theme={null}
$value = Context::pull('key');
```

要從堆疊取出最後一個值請使用 `pop`。

```php theme={null}
Context::push('breadcrumbs', 'first_value', 'second_value');

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

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

### remember — 不存在時設定並回傳

```php theme={null}
$permissions = Context::remember(
    'user-permissions',
    fn () => $user->permissions,
);
```

### has / missing — 檢查 key 是否存在

```php theme={null}
if (Context::has('key')) {
    // ...
}

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

<Info>
  `has` 即使值為 `null` 也會回傳 `true`。它只確認 key 是否已註冊。
</Info>

## 移除脈絡

以 `forget` 移除 key。

```php theme={null}
Context::add(['first_key' => 1, 'second_key' => 2]);

Context::forget('first_key');

Context::all();
// ['second_key' => 2]

// 一次移除多個
Context::forget(['first_key', 'second_key']);
```

## 有作用域的脈絡

透過 `scope` 方法，可以只在閉包執行期間暫時變更脈絡，並在執行完自動還原原本狀態。
在測試或局部處理中希望暫時加入補充資訊到日誌時非常好用。

```php theme={null}
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]
```

<Warning>
  若在作用域內修改了物件，該變更會反映到作用域之外。使用基本型別（primitive）則沒有問題。
</Warning>

## Hidden Context

不希望被輸出至日誌的資料（密碼、API 金鑰、個人識別資訊等）應存入 Hidden Context。
無法透過一般的 `get` 方法取得，只能透過 `getHidden` 等專用方法存取。

```php theme={null}
use Illuminate\Support\Facades\Context;

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

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

Context::get('key');
// null — 一般 get 無法取得
```

Hidden Context 提供與一般脈絡對應的一整組方法。

```php theme={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(/* ... */);
```

## 傳遞給佇列 Job

當你將 Job 派發到佇列時，目前的脈絡會自動被序列化並包含在 Job payload 中。
Job 執行時原始脈絡會被還原，因此於請求中設定的 `trace_id` 也會自動延續到佇列中的日誌。

```php theme={null}
// 中介軟體中設定
Context::add('trace_id', Str::uuid()->toString());

// 於控制器派發 Job
ProcessPodcast::dispatch($podcast);
```

```php theme={null}
class ProcessPodcast implements ShouldQueue
{
    use Queueable;

    public function handle(): void
    {
        Log::info('Processing podcast.', ['podcast_id' => $this->podcast->id]);
    }
}
```

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

可看出請求時的 `trace_id` 也包含在佇列的日誌中。

### Dehydrating — Job 送出時的自訂

透過 `Context::dehydrating` 可以在 Job 送出前加工脈絡。
例如，若想將依 `Accept-Language` 標頭決定的 locale 傳遞給佇列時可以使用。

```php theme={null}
// AppServiceProvider.php

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

public function boot(): void
{
    Context::dehydrating(function (Repository $context) {
        $context->addHidden('locale', Config::get('app.locale'));
    });
}
```

<Warning>
  在 `dehydrating` 回呼中，請不要使用 `Context` Facade，只能操作傳入回呼的 `$context` repository。
  若使用 Facade，會變更當前程序的脈絡。
</Warning>

### Hydrated — Job 執行時的還原

透過 `Context::hydrated` 可以在 Job 執行前、脈絡剛被還原的時機加入處理。
例如將先前保存的 locale 套用到設定檔中。

```php theme={null}
// AppServiceProvider.php

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

public function boot(): void
{
    Context::hydrated(function (Repository $context) {
        if ($context->hasHidden('locale')) {
            Config::set('app.locale', $context->getHidden('locale'));
        }
    });
}
```

<Warning>
  同樣地，在 `hydrated` 回呼中請勿使用 `Context` Facade，只操作傳入的 `$context` repository。
</Warning>

## 總結

<AccordionGroup>
  <Accordion title="Context vs Log::withContext 的差異">
    |           | `Context`             | `Log::withContext` |
    | --------- | --------------------- | ------------------ |
    | 對象        | 所有日誌通道                | 僅特定通道              |
    | 傳遞給佇列 Job | 自動（Dehydrate/Hydrate） | 無                  |
    | Hidden 資料 | 支援                    | 無                  |
    | 用途        | 追蹤 / 分散式系統            | 通道專屬中介資料           |
  </Accordion>

  <Accordion title="適合放入 Hidden Context 的資料">
    Hidden Context 不會輸出到日誌，可安全地儲存以下類型資料。

    * session ID 或使用者 ID（不想留在日誌中時）
    * API 金鑰或認證 token
    * locale 或設定值（想傳遞給佇列但不需要出現在日誌）
    * 內部旗標或狀態
  </Accordion>

  <Accordion title="Dehydrate / Hydrate 常見用途">
    1. **傳遞 locale**：`dehydrating` 將 `app.locale` 存入 Hidden Context，`hydrated` 中透過 `Config::set` 還原。
    2. **傳遞認證資訊**：讓佇列 Job 也能取得請求中已認證使用者的資訊。
    3. **租戶 ID**：在多租戶應用中將租戶識別碼跨佇列分享。
  </Accordion>
</AccordionGroup>


## Related topics

- [日誌](/zh-TW/logging.md)
- [錯誤處理](/zh-TW/error-handling.md)
- [Session Hook](/zh-TW/packages/laravel-copilot-sdk/hooks.md)
- [Laravel MCP](/zh-TW/mcp.md)
- [Laravel Boost](/zh-TW/boost.md)
