Files
laravelDocScrappy/output/12.x/context.md
2025-09-02 15:19:23 +02:00

1009 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Context
* Introduction
* How it Works
* Capturing Context
* Stacks
* Retrieving Context
* Determining Item Existence
* Removing Context
* Hidden Context
* Events
* Dehydrating
* Hydrated
## Introduction
Laravel's "context" capabilities enable you to capture, retrieve, and share
information throughout requests, jobs, and commands executing within your
application. This captured information is also included in logs written by
your application, giving you deeper insight into the surrounding code
execution history that occurred before a log entry was written and allowing
you to trace execution flows throughout a distributed system.
### How it Works
The best way to understand Laravel's context capabilities is to see it in
action using the built-in logging features. To get started, you may add
information to the context using the `Context` facade. In this example, we
will use a [middleware](/docs/12.x/middleware) to add the request URL and a
unique trace ID to the context on every incoming request:
1<?php
2 
3namespace App\Http\Middleware;
4 
5use Closure;
6use Illuminate\Http\Request;
7use Illuminate\Support\Facades\Context;
8use Illuminate\Support\Str;
9use Symfony\Component\HttpFoundation\Response;
10 
11class AddContext
12{
13 /**
14 * Handle an incoming request.
15 */
16 public function handle(Request $request, Closure $next): Response
17 {
18 Context::add('url', $request->url());
19 Context::add('trace_id', Str::uuid()->toString());
20 
21 return $next($request);
22 }
23}
<?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);
}
}
Information added to the context is automatically appended as metadata to any
[log entries](/docs/12.x/logging) that are written throughout the request.
Appending context as metadata allows information passed to individual log
entries to be differentiated from the information shared via `Context`. For
example, imagine we write the following log entry:
1Log::info('User authenticated.', ['auth_id' => Auth::id()]);
Log::info('User authenticated.', ['auth_id' => Auth::id()]);
The written log will contain the `auth_id` passed to the log entry, but it
will also contain the context's `url` and `trace_id` as metadata:
1User authenticated. {"auth_id":27} {"url":"https://example.com/login","trace_id":"e04e1a11-e75c-4db3-b5b5-cfef4ef56697"}
User authenticated. {"auth_id":27} {"url":"https://example.com/login","trace_id":"e04e1a11-e75c-4db3-b5b5-cfef4ef56697"}
Information added to the context is also made available to jobs dispatched to
the queue. For example, imagine we dispatch a `ProcessPodcast` job to the
queue after adding some information to the context:
1// In our middleware...
2Context::add('url', $request->url());
3Context::add('trace_id', Str::uuid()->toString());
4 
5// In our controller...
6ProcessPodcast::dispatch($podcast);
// In our middleware...
Context::add('url', $request->url());
Context::add('trace_id', Str::uuid()->toString());
// In our controller...
ProcessPodcast::dispatch($podcast);
When the job is dispatched, any information currently stored in the context is
captured and shared with the job. The captured information is then hydrated
back into the current context while the job is executing. So, if our job's
handle method was to write to the log:
1class ProcessPodcast implements ShouldQueue
2{
3 use Queueable;
4 
5 // ...
6 
7 /**
8 * Execute the job.
9 */
10 public function handle(): void
11 {
12 Log::info('Processing podcast.', [
13 'podcast_id' => $this->podcast->id,
14 ]);
15 
16 // ...
17 }
18}
class ProcessPodcast implements ShouldQueue
{
use Queueable;
// ...
/**
* Execute the job.
*/
public function handle(): void
{
Log::info('Processing podcast.', [
'podcast_id' => $this->podcast->id,
]);
// ...
}
}
The resulting log entry would contain the information that was added to the
context during the request that originally dispatched the job:
1Processing podcast. {"podcast_id":95} {"url":"https://example.com/login","trace_id":"e04e1a11-e75c-4db3-b5b5-cfef4ef56697"}
Processing podcast. {"podcast_id":95} {"url":"https://example.com/login","trace_id":"e04e1a11-e75c-4db3-b5b5-cfef4ef56697"}
Although we have focused on the built-in logging related features of Laravel's
context, the following documentation will illustrate how context allows you to
share information across the HTTP request / queued job boundary and even how
to add hidden context data that is not written with log entries.
## Capturing Context
You may store information in the current context using the `Context` facade's
`add` method:
1use Illuminate\Support\Facades\Context;
2 
3Context::add('key', 'value');
use Illuminate\Support\Facades\Context;
Context::add('key', 'value');
To add multiple items at once, you may pass an associative array to the `add`
method:
1Context::add([
2 'first_key' => 'value',
3 'second_key' => 'value',
4]);
Context::add([
'first_key' => 'value',
'second_key' => 'value',
]);
The `add` method will override any existing value that shares the same key. If
you only wish to add information to the context if the key does not already
exist, you may use the `addIf` method:
1Context::add('key', 'first');
2 
3Context::get('key');
4// "first"
5 
6Context::addIf('key', 'second');
7 
8Context::get('key');
9// "first"
Context::add('key', 'first');
Context::get('key');
// "first"
Context::addIf('key', 'second');
Context::get('key');
// "first"
Context also provides convenient methods for incrementing or decrementing a
given key. Both of these methods accept at least one argument: the key to
track. A second argument may be provided to specify the amount by which the
key should be incremented or decremented:
1Context::increment('records_added');
2Context::increment('records_added', 5);
3 
4Context::decrement('records_added');
5Context::decrement('records_added', 5);
Context::increment('records_added');
Context::increment('records_added', 5);
Context::decrement('records_added');
Context::decrement('records_added', 5);
#### Conditional Context
The `when` method may be used to add data to the context based on a given
condition. The first closure provided to the `when` method will be invoked if
the given condition evaluates to `true`, while the second closure will be
invoked if the condition evaluates to `false`:
1use Illuminate\Support\Facades\Auth;
2use Illuminate\Support\Facades\Context;
3 
4Context::when(
5 Auth::user()->isAdmin(),
6 fn ($context) => $context->add('permissions', Auth::user()->permissions),
7 fn ($context) => $context->add('permissions', []),
8);
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', []),
);
#### Scoped Context
The `scope` method provides a way to temporarily modify the context during the
execution of a given callback and restore the context to its original state
when the callback finishes executing. Additionally, you can pass extra data
that should be merged into the context (as the second and third arguments)
while the closure executes.
1use Illuminate\Support\Facades\Context;
2use Illuminate\Support\Facades\Log;
3 
4Context::add('trace_id', 'abc-999');
5Context::addHidden('user_id', 123);
6 
7Context::scope(
8 function () {
9 Context::add('action', 'adding_friend');
10 
11 $userId = Context::getHidden('user_id');
12 
13 Log::debug("Adding user [{$userId}] to friends list.");
14 // Adding user [987] to friends list. {"trace_id":"abc-999","user_name":"taylor_otwell","action":"adding_friend"}
15 },
16 data: ['user_name' => 'taylor_otwell'],
17 hidden: ['user_id' => 987],
18);
19 
20Context::all();
21// [
22// 'trace_id' => 'abc-999',
23// ]
24 
25Context::allHidden();
26// [
27// 'user_id' => 123,
28// ]
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,
// ]
If an object within the context is modified inside the scoped closure, that
mutation will be reflected outside of the scope.
### Stacks
Context offers the ability to create "stacks", which are lists of data stored
in the order that they were added. You can add information to a stack by
invoking the `push` method:
1use Illuminate\Support\Facades\Context;
2 
3Context::push('breadcrumbs', 'first_value');
4 
5Context::push('breadcrumbs', 'second_value', 'third_value');
6 
7Context::get('breadcrumbs');
8// [
9// 'first_value',
10// 'second_value',
11// 'third_value',
12// ]
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',
// ]
Stacks can be useful to capture historical information about a request, such
as events that are happening throughout your application. For example, you
could create an event listener to push to a stack every time a query is
executed, capturing the query SQL and duration as a tuple:
1use Illuminate\Support\Facades\Context;
2use Illuminate\Support\Facades\DB;
3 
4// In AppServiceProvider.php...
5DB::listen(function ($event) {
6 Context::push('queries', [$event->time, $event->sql]);
7});
use Illuminate\Support\Facades\Context;
use Illuminate\Support\Facades\DB;
// In AppServiceProvider.php...
DB::listen(function ($event) {
Context::push('queries', [$event->time, $event->sql]);
});
You may determine if a value is in a stack using the `stackContains` and
`hiddenStackContains` methods:
1if (Context::stackContains('breadcrumbs', 'first_value')) {
2 //
3}
4 
5if (Context::hiddenStackContains('secrets', 'first_value')) {
6 //
7}
if (Context::stackContains('breadcrumbs', 'first_value')) {
//
}
if (Context::hiddenStackContains('secrets', 'first_value')) {
//
}
The `stackContains` and `hiddenStackContains` methods also accept a closure as
their second argument, allowing more control over the value comparison
operation:
1use Illuminate\Support\Facades\Context;
2use Illuminate\Support\Str;
3 
4return Context::stackContains('breadcrumbs', function ($value) {
5 return Str::startsWith($value, 'query_');
6});
use Illuminate\Support\Facades\Context;
use Illuminate\Support\Str;
return Context::stackContains('breadcrumbs', function ($value) {
return Str::startsWith($value, 'query_');
});
## Retrieving Context
You may retrieve information from the context using the `Context` facade's
`get` method:
1use Illuminate\Support\Facades\Context;
2 
3$value = Context::get('key');
use Illuminate\Support\Facades\Context;
$value = Context::get('key');
The `only` and `except` methods may be used to retrieve a subset of the
information in the context:
1$data = Context::only(['first_key', 'second_key']);
2 
3$data = Context::except(['first_key']);
$data = Context::only(['first_key', 'second_key']);
$data = Context::except(['first_key']);
The `pull` method may be used to retrieve information from the context and
immediately remove it from the context:
1$value = Context::pull('key');
$value = Context::pull('key');
If context data is stored in a stack, you may pop items from the stack using
the `pop` method:
1Context::push('breadcrumbs', 'first_value', 'second_value');
2 
3Context::pop('breadcrumbs');
4// second_value
5 
6Context::get('breadcrumbs');
7// ['first_value']
Context::push('breadcrumbs', 'first_value', 'second_value');
Context::pop('breadcrumbs');
// second_value
Context::get('breadcrumbs');
// ['first_value']
The `remember` and `rememberHidden` methods may be used to retrieve
information from the context, while setting the context value to the value
returned by the given closure if the requested information doesn't exist:
1$permissions = Context::remember(
2 'user-permissions',
3 fn () => $user->permissions,
4);
$permissions = Context::remember(
'user-permissions',
fn () => $user->permissions,
);
If you would like to retrieve all of the information stored in the context,
you may invoke the `all` method:
1$data = Context::all();
$data = Context::all();
### Determining Item Existence
You may use the `has` and `missing` methods to determine if the context has
any value stored for the given key:
1use Illuminate\Support\Facades\Context;
2 
3if (Context::has('key')) {
4 // ...
5}
6 
7if (Context::missing('key')) {
8 // ...
9}
use Illuminate\Support\Facades\Context;
if (Context::has('key')) {
// ...
}
if (Context::missing('key')) {
// ...
}
The `has` method will return `true` regardless of the value stored. So, for
example, a key with a `null` value will be considered present:
1Context::add('key', null);
2 
3Context::has('key');
4// true
Context::add('key', null);
Context::has('key');
// true
## Removing Context
The `forget` method may be used to remove a key and its value from the current
context:
1use Illuminate\Support\Facades\Context;
2 
3Context::add(['first_key' => 1, 'second_key' => 2]);
4 
5Context::forget('first_key');
6 
7Context::all();
8 
9// ['second_key' => 2]
use Illuminate\Support\Facades\Context;
Context::add(['first_key' => 1, 'second_key' => 2]);
Context::forget('first_key');
Context::all();
// ['second_key' => 2]
You may forget several keys at once by providing an array to the `forget`
method:
1Context::forget(['first_key', 'second_key']);
Context::forget(['first_key', 'second_key']);
## Hidden Context
Context offers the ability to store "hidden" data. This hidden information is
not appended to logs, and is not accessible via the data retrieval methods
documented above. Context provides a different set of methods to interact with
hidden context information:
1use Illuminate\Support\Facades\Context;
2 
3Context::addHidden('key', 'value');
4 
5Context::getHidden('key');
6// 'value'
7 
8Context::get('key');
9// null
use Illuminate\Support\Facades\Context;
Context::addHidden('key', 'value');
Context::getHidden('key');
// 'value'
Context::get('key');
// null
The "hidden" methods mirror the functionality of the non-hidden methods
documented above:
1Context::addHidden(/* ... */);
2Context::addHiddenIf(/* ... */);
3Context::pushHidden(/* ... */);
4Context::getHidden(/* ... */);
5Context::pullHidden(/* ... */);
6Context::popHidden(/* ... */);
7Context::onlyHidden(/* ... */);
8Context::exceptHidden(/* ... */);
9Context::allHidden(/* ... */);
10Context::hasHidden(/* ... */);
11Context::missingHidden(/* ... */);
12Context::forgetHidden(/* ... */);
Context::addHidden(/* ... */);
Context::addHiddenIf(/* ... */);
Context::pushHidden(/* ... */);
Context::getHidden(/* ... */);
Context::pullHidden(/* ... */);
Context::popHidden(/* ... */);
Context::onlyHidden(/* ... */);
Context::exceptHidden(/* ... */);
Context::allHidden(/* ... */);
Context::hasHidden(/* ... */);
Context::missingHidden(/* ... */);
Context::forgetHidden(/* ... */);
## Events
Context dispatches two events that allow you to hook into the hydration and
dehydration process of the context.
To illustrate how these events may be used, imagine that in a middleware of
your application you set the `app.locale` configuration value based on the
incoming HTTP request's `Accept-Language` header. Context's events allow you
to capture this value during the request and restore it on the queue, ensuring
notifications sent on the queue have the correct `app.locale` value. We can
use context's events and hidden data to achieve this, which the following
documentation will illustrate.
### Dehydrating
Whenever a job is dispatched to the queue the data in the context is
"dehydrated" and captured alongside the job's payload. The
`Context::dehydrating` method allows you to register a closure that will be
invoked during the dehydration process. Within this closure, you may make
changes to the data that will be shared with the queued job.
Typically, you should register `dehydrating` callbacks within the `boot`
method of your application's `AppServiceProvider` class:
1use Illuminate\Log\Context\Repository;
2use Illuminate\Support\Facades\Config;
3use Illuminate\Support\Facades\Context;
4 
5/**
6 * Bootstrap any application services.
7 */
8public function boot(): void
9{
10 Context::dehydrating(function (Repository $context) {
11 $context->addHidden('locale', Config::get('app.locale'));
12 });
13}
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'));
});
}
You should not use the `Context` facade within the `dehydrating` callback, as
that will change the context of the current process. Ensure you only make
changes to the repository passed to the callback.
### Hydrated
Whenever a queued job begins executing on the queue, any context that was
shared with the job will be "hydrated" back into the current context. The
`Context::hydrated` method allows you to register a closure that will be
invoked during the hydration process.
Typically, you should register `hydrated` callbacks within the `boot` method
of your application's `AppServiceProvider` class:
1use Illuminate\Log\Context\Repository;
2use Illuminate\Support\Facades\Config;
3use Illuminate\Support\Facades\Context;
4 
5/**
6 * Bootstrap any application services.
7 */
8public function boot(): void
9{
10 Context::hydrated(function (Repository $context) {
11 if ($context->hasHidden('locale')) {
12 Config::set('app.locale', $context->getHidden('locale'));
13 }
14 });
15}
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'));
}
});
}
You should not use the `Context` facade within the `hydrated` callback and
instead ensure you only make changes to the repository passed to the callback.