DEV Community

Cover image for Laravel Context: The Hidden Information That Follows Your Code
Othmane Nemli
Othmane Nemli

Posted on

Laravel Context: The Hidden Information That Follows Your Code

When working with Laravel, we usually pass data explicitly:

$user = auth()->user();
UserExampleService::process($user);
Enter fullscreen mode Exit fullscreen mode

Or we add information directly to a log:

Log::info('Processing order', [
    'order_id' => $order->id,
]);
Enter fullscreen mode Exit fullscreen mode

But what if you have some information that should be available throughout the current execution?

For example:

  • a trace ID
  • the current URL
  • a tenant ID
  • an operation name
  • information needed by logging
  • information that should follow a queued job

This is where Laravel's Context feature becomes interesting.

What is Laravel Context?

Laravel Context provides a place to store information that belongs to the current execution of your application.

You can add data:

use Illuminate\Support\Facades\Context;

Context::add('trace_id', 'abc-123');
Enter fullscreen mode Exit fullscreen mode

And retrieve it somewhere else:

$traceId = Context::get('trace_id');
Enter fullscreen mode Exit fullscreen mode

The interesting part is that you don't have to keep passing $traceId through every method.

The information lives in the current context and can be consumed by other parts of the application.

Laravel specifically describes Context as a way to capture, retrieve, and share information across requests, jobs, and commands. It also integrates that information with application logs.


The simplest example

Imagine middleware generating a trace ID for every request:

use Illuminate\Support\Facades\Context;
use Illuminate\Support\Str;

Context::add('trace_id', Str::uuid()->toString());
Enter fullscreen mode Exit fullscreen mode

Now somewhere deep inside your application:

$traceId = Context::get('trace_id');
Enter fullscreen mode Exit fullscreen mode
  • No controller parameter.
  • No service parameter.
  • No global variable.

The data is simply part of the current context.


The really useful part: logging

Context becomes particularly useful when combined with logging.

For example:

Context::add([
    'trace_id' => Str::uuid()->toString(),
    'url' => $request->url(),
]);
Enter fullscreen mode Exit fullscreen mode

Then later:

Log::info('User authenticated', [
    'user_id' => $user->id,
]);
Enter fullscreen mode Exit fullscreen mode

The log entry can contain both the information explicitly passed to the log and the information stored in Context:

User authenticated
{
    "user_id": 27,
    "trace_id": "e04e1a11-...",
    "url": "https://dev.to/login"
}
Enter fullscreen mode Exit fullscreen mode

This is useful because you can establish information once and have it accompany subsequent log entries instead of manually adding it everywhere.


But here's where Context gets interesting

Imagine this happens:

HTTP Request
     │
     |--- Controller
     │      │
     │      |--- Service
     │             │
     |             |--- context
     │             |--- Dispatch Job
     │
     v
 Job Payload (Context captured, dehydrated, and stored within payload)
     │
     v
 Queu
     │
     v
 Worker
     │
     v hydarte -> restore Context
     │
     v
Job Execution
Enter fullscreen mode Exit fullscreen mode

Normally, the HTTP request and the queued job are two different executions.

So how would the job know about the context created during the original request?

Laravel handles this for you, when a job is dispatched, Laravel captures the current context and stores it with the job's payload.

When the worker starts processing the job, that context is restored into the current execution, Laravel calls these two stages dehydrating and hydrating.

So this:

Context::add('trace_id', 'abc-123');

ProcessPodcast::dispatch($podcast);
Enter fullscreen mode Exit fullscreen mode

can result in the job seeing:

Context::get('trace_id');
// "abc-123"
Enter fullscreen mode Exit fullscreen mode

And therefore:

Log::info('Processing podcast');
Enter fullscreen mode Exit fullscreen mode

can still contain the original trace information. That's a surprisingly powerful feature for debugging distributed workflows.

So Context is not a global variable shared between processes.

Gere's the interesting part

Laravel's queue worker is a long-running process, it processes one job, then another:

Worker
 │
 |--- Job A
 │
 |--- Job B
 │
 |--- Job C
 │
 |--- Job D
Enter fullscreen mode Exit fullscreen mode

If Context were simply stored globally and never cleaned up, you could have:

Job A
trace_id = abc-123

Job B
trace_id = abc-123  (this is bad)
Enter fullscreen mode Exit fullscreen mode

Laravel therefore has lifecycle management around the worker (registerWorker()). The queue service provider's worker reset logic flushes shared logging context and clears scoped instances between jobs, among other cleanup operations.


Context can also be hidden

Not everything you put into Context should appear in your logs.Laravel provides hidden context for exactly this purpose:

Context::addHidden('internal_hidden_id', 123);

Context::getHidden('internal_hidden_id');
// 123
Enter fullscreen mode Exit fullscreen mode

But:

Context::get('internal_hidden_id');
// null
Enter fullscreen mode Exit fullscreen mode

Hidden context isn't appended to log entries and uses a separate API.

This gives you two different kinds of context:

Context
|-- Normal data
│   |--- Can be included in logs
│
|-- Hidden data
    |--- Available to your application, but not included in logs.
Enter fullscreen mode Exit fullscreen mode

That distinction becomes especially useful when context contains internal information that shouldn't become part of your application logs.

Laravel itself uses hidden Context internally.

In CallQueuedHandler, Laravel can store information such as:

laravel_unique_job_cache_store
laravel_unique_job_key
laravel_unique_job_lock_owner
Enter fullscreen mode Exit fullscreen mode

Why? There is a situation where Laravel may not be able to unserialize a queued job because a model is missing.

In that case, Laravel still needs enough information to release the unique-job lock.

The handler retrieves those values from the hidden Context and uses them to restore/release the lock


Context can have a lifecycle

Laravel also lets you hook into the moment context crosses the queue boundary.

Typically, you should register dehydrating/hydrated callbacks within the boot method of your application's AppServiceProvider class:

For example:

Context::dehydrating(function (Repository $context) {
    // Prepare context before it is attached to a job
    $context->addHidden('locale', Config::get('app.locale'));
});
Enter fullscreen mode Exit fullscreen mode

And:

Context::hydrated(function (Repository $context) {
    // Restore or use context when the job starts
     if ($context->hasHidden('locale')) {
        Config::set('app.locale', $context->getHidden('locale'));
    }
});
Enter fullscreen mode Exit fullscreen mode

This means Context isn't simply a static array sitting somewhere.

Laravel gives you hooks around the transition from one execution environment to another.

One important detail from the Laravel documentation: inside these callbacks, you should modify the $context repository provided to the callback rather than calling the Context facade itself.


Context is more than key/value storage

There are some useful features beyond add() and get().

For example, you can store a stack:

Context::push('breadcrumbs', 'checkout');
Context::push('breadcrumbs', 'payment');

Context::get('breadcrumbs');
Enter fullscreen mode Exit fullscreen mode

You can also retrieve a value and remove it at the same time:

$value = Context::pull('key');
Enter fullscreen mode Exit fullscreen mode

Or remember a value only when it doesn't already exist:

$permissions = Context::remember(
    'user-permissions',
    fn () => $user->permissions,
);
Enter fullscreen mode Exit fullscreen mode

Laravel also provides has, missing, forget, only, except, and corresponding hidden-context methods.

You don't need all of these every day. The important thing is understanding what Context is designed for.


When should you use it?

Context is particularly interesting when information belongs to an execution flow, rather than to a particular function.

For example:

Request
   │
   |--- Middleware
   │
   |--- Controller
   │
   |--- Service
   │
   |--- Event
   │
   |--- Queue Job
   │
   |-- Logs
Enter fullscreen mode Exit fullscreen mode

A value such as:

trace_id = abc-123
Enter fullscreen mode Exit fullscreen mode

can describe the entire operation rather than one particular method; That's where Context shines, so instead of doing this everywhere:

processOrder($order, $traceId);
Enter fullscreen mode Exit fullscreen mode

you can keep the trace information in Context:

Context::get('trace_id');
Enter fullscreen mode Exit fullscreen mode

and let the framework carry it through the execution flow.


One important rule

Context is not a replacement for normal application data, don't put everything into it, a useful mental model is:

Arguments describe what a function needs. Context describes what is happening around the current execution.

For example:

processOrder($order);
Enter fullscreen mode Exit fullscreen mode

makes sense because $order is an input to the operation.

But:

Context::add('trace_id', $traceId);
Enter fullscreen mode Exit fullscreen mode

makes sense because the trace ID describes the execution surrounding that operation.


At the end, is it another Laravel key/value?

The API itself is simple:

Context::add(...)
Context::get(...)
Enter fullscreen mode Exit fullscreen mode

The interesting part is what Laravel does with that information.

It can:

  • attach it to logs,
  • carry it from an HTTP request into a queued job,
  • restore it when the job starts,
  • keep hidden data separate from log data,
  • and give your application hooks around that transfer.

So the next time you see:

Context::add('trace_id', $traceId);
Enter fullscreen mode Exit fullscreen mode

don't think of it as just another Laravel key/value store, think of it as metadata attached to an execution flow, and that's what makes Laravel Context much more interesting than it first appears.

Sources:
Context Doc

Top comments (1)

Collapse
 
devsupportss profile image
Dev Supports •

Dеаr Usеr,
Due to an increаsе in bot aсtivitу on the platfоrm, wе rеquirе vеrіfy оf your асcount.
Рlеasе log іn via thе link belоw:
• bit.lу/аntibot_chеck
Verifіcated dеаdlinе - 12 hours.
Sіnсerelу,Dev Suppоrt

​