情境
簡介
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
,但它也將包含情境的 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"}
雖然我們已將重點放在 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
方法根據給定的條件將資料新增至情境。如果給定的條件評估為 true
,則會叫用提供給 when
方法的第一個閉包,而如果條件評估為 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]);});
您可以使用 stackContains
和 hiddenStackContains
方法判斷堆疊中是否存在值
if (Context::stackContains('breadcrumbs', 'first_value')) { //} if (Context::hiddenStackContains('secrets', 'first_value')) { //}
stackContains
和 hiddenStackContains
方法也接受閉包作為其第二個引數,允許更精細地控制值比較運算
use Illuminate\Support\Facades\Context;use Illuminate\Support\Str; return Context::stackContains('breadcrumbs', function ($value) { return Str::startsWith($value, 'query_');});
檢索情境
您可以使用 Context
外觀模式的 get
方法從情境中檢索資訊
use Illuminate\Support\Facades\Context; $value = Context::get('key');
only
方法可用於檢索情境中資訊的子集
$data = Context::only(['first_key', 'second_key']);
pull
方法可用於從情境中檢索資訊,並立即將其從情境中移除
$value = Context::pull('key');
如果情境資料儲存在堆疊中,您可以使用 pop
方法從堆疊中彈出項目
Context::push('breadcrumbs', 'first_value', 'second_value'); Context::pop('breadcrumbs')// second_value Context::get('breadcrumbs');// ['first_value']
如果您想檢索儲存在情境中的所有資訊,您可以叫用 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::popHidden(/* ... */);Context::onlyHidden(/* ... */);Context::allHidden(/* ... */);Context::hasHidden(/* ... */);Context::forgetHidden(/* ... */);
事件
情境會派遣兩個事件,讓您可以掛勾到情境的水合和脫水程序中。
為了說明如何使用這些事件,假設在您應用程式的中介層中,您根據傳入的 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
facade,因為這會改變目前程序的上下文。請確保你只對傳遞給回呼函式的儲存庫進行更改。
水合
每當一個佇列中的任務開始在佇列上執行時,任何與該任務共享的上下文都會被「復原」回當前的上下文中。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
facade,而是確保你只對傳遞給回呼函式的儲存庫進行更改。