Laravel Context: Share Data Across Your Application
Master Laravel's Context facade for sharing data throughout request lifecycle. Learn context stacks, hidden context, dehydration, and logging integration.
Laravel Context: Share Data Across Your Application
Laravel Context provides a clean way to share data throughout your application's request lifecycle. From controllers to jobs, middleware to logging—context travels with your code.
Basic Usage
use Illuminate\Support\Facades\Context;
// Add context
Context::add('user_id', auth()->id());
Context::add('request_id', Str::uuid()->toString());
// Retrieve context
$userId = Context::get('user_id');
// Check existence
if (Context::has('tenant_id')) {
// ...
}
// Get all context
$all = Context::all();
Adding Context in Middleware
// app/Http/Middleware/AddRequestContext.php
namespace App\Http\Middleware;
use Illuminate\Support\Facades\Context;
use Illuminate\Support\Str;
class AddRequestContext
{
public function handle(Request $request, Closure $next)
{
Context::add([
'request_id' => Str::uuid()->toString(),
'ip' => $request->ip(),
'user_agent' => $request->userAgent(),
'url' => $request->fullUrl(),
]);
if ($request->user()) {
Context::add([
'user_id' => $request->user()->id,
'user_email' => $request->user()->email,
]);
}
return $next($request);
}
}
Register in bootstrap/app.php:
->withMiddleware(function (Middleware $middleware) {
$middleware->append(AddRequestContext::class);
})
Context in Logging
Context automatically flows into log messages:
// Add context
Context::add('order_id', $order->id);
Context::add('customer_id', $order->customer_id);
// All logs include context
Log::info('Processing order');
// Output: [2024-01-15 10:30:00] local.INFO: Processing order {"order_id":123,"customer_id":456}
Log::error('Payment failed', ['reason' => 'Insufficient funds']);
// Output: [2024-01-15 10:30:01] local.ERROR: Payment failed {"reason":"Insufficient funds","order_id":123,"customer_id":456}
Context Stacks
For hierarchical data like breadcrumbs:
// Push to stack
Context::push('breadcrumbs', 'Home');
Context::push('breadcrumbs', 'Products');
Context::push('breadcrumbs', 'Electronics');
// Get stack
Context::get('breadcrumbs'); // ['Home', 'Products', 'Electronics']
// Check if stack contains value
Context::stackContains('breadcrumbs', 'Products'); // true
Tracking Method Calls
class OrderProcessor
{
public function process(Order $order): void
{
Context::push('trace', 'OrderProcessor::process');
$this->validateOrder($order);
$this->chargePayment($order);
$this->updateInventory($order);
$this->sendNotifications($order);
}
private function validateOrder(Order $order): void
{
Context::push('trace', 'OrderProcessor::validateOrder');
// ...
}
private function chargePayment(Order $order): void
{
Context::push('trace', 'OrderProcessor::chargePayment');
// If error occurs, logs show full trace
}
}
Hidden Context
Data that shouldn't appear in logs but should propagate:
// Add hidden context
Context::addHidden('api_key', $apiKey);
Context::addHidden('session_token', $token);
// Retrieve hidden context
$apiKey = Context::getHidden('api_key');
// Won't appear in logs
Log::info('API call made'); // api_key NOT included
// But propagates to jobs
dispatch(new ProcessWebhook($data)); // Hidden context travels with job
Context with Jobs
Context automatically propagates to queued jobs:
// In controller
Context::add('tenant_id', $tenant->id);
Context::add('user_id', auth()->id());
dispatch(new ProcessOrder($order));
// In job - context is available
class ProcessOrder implements ShouldQueue
{
public function handle()
{
$tenantId = Context::get('tenant_id');
$userId = Context::get('user_id');
Log::info('Processing order in job');
// Includes: tenant_id, user_id
}
}
Controlling Propagation
// Don't propagate specific keys
Context::add('temporary_data', $data);
Context::forget('temporary_data'); // Remove before job dispatch
// Or use dehydrating callbacks
Context::dehydrating(function (Context $context) {
$context->forget('temporary_data');
});
Scoped Context
Temporarily add context:
$result = Context::scope(function () use ($order) {
Context::add('processing_order', $order->id);
// This context only exists within this scope
$this->processOrder($order);
return $order->fresh();
});
// 'processing_order' is automatically removed
Context::has('processing_order'); // false
Multi-Tenancy Context
// app/Http/Middleware/TenantContext.php
class TenantContext
{
public function handle(Request $request, Closure $next)
{
$tenant = $request->route('tenant') ?? $this->resolveTenant($request);
Context::add([
'tenant_id' => $tenant->id,
'tenant_slug' => $tenant->slug,
'tenant_plan' => $tenant->plan,
]);
// Hidden - for internal use only
Context::addHidden('tenant_database', $tenant->database);
return $next($request);
}
}
// Anywhere in application
class ReportService
{
public function generate()
{
$tenantId = Context::get('tenant_id');
// Logs automatically include tenant context
Log::info('Generating report');
}
}
API Request Tracing
// Middleware for API tracing
class ApiTracing
{
public function handle(Request $request, Closure $next)
{
$traceId = $request->header('X-Trace-ID') ?? Str::uuid()->toString();
$spanId = Str::uuid()->toString();
Context::add([
'trace_id' => $traceId,
'span_id' => $spanId,
'parent_span_id' => $request->header('X-Span-ID'),
]);
$response = $next($request);
return $response
->header('X-Trace-ID', $traceId)
->header('X-Span-ID', $spanId);
}
}
// All logs include trace_id for correlation
Log::info('API request received');
Log::info('Database query executed');
Log::info('Response sent');
// All three logs share the same trace_id
Dehydration and Hydration
Control what context travels to jobs:
// app/Providers/AppServiceProvider.php
public function boot(): void
{
// Before serializing for job
Context::dehydrating(function (Context $context) {
// Remove large or sensitive data
$context->forget('large_payload');
$context->forgetHidden('temporary_token');
});
// After deserializing in job
Context::hydrated(function (Context $context) {
// Add job-specific context
$context->add('job_started_at', now()->toIso8601String());
});
}
Testing with Context
use Illuminate\Support\Facades\Context;
test('order processing adds context', function () {
Context::add('user_id', 1);
$order = Order::factory()->create();
app(OrderProcessor::class)->process($order);
expect(Context::get('order_id'))->toBe($order->id);
expect(Context::get('order_status'))->toBe('processed');
});
test('context propagates to jobs', function () {
Context::add('tenant_id', 5);
dispatch(new ProcessOrder($order));
// Assert job received context
Queue::assertPushed(ProcessOrder::class, function ($job) {
return $job->context['tenant_id'] === 5;
});
});
beforeEach(function () {
Context::flush(); // Clear context between tests
});
Exception Context
Add context when exceptions occur:
class OrderProcessor
{
public function process(Order $order): void
{
Context::add('order_id', $order->id);
Context::add('order_total', $order->total);
try {
$this->charge($order);
} catch (PaymentException $e) {
// Context included in exception report
report($e);
throw $e;
}
}
}
// In exception handler
class Handler extends ExceptionHandler
{
public function report(Throwable $e): void
{
// Context automatically included
Log::error($e->getMessage(), [
'exception' => $e,
// context: order_id, order_total included
]);
}
}
Best Practices
- Add context early - In middleware before business logic
- Use hidden for sensitive data - API keys, tokens
- Clean up temporary context - Use
forget()orscope() - Consistent naming -
user_idnotuserIdoruser-id - Don't overload - Keep context focused and relevant
Conclusion
Laravel Context provides a clean, framework-integrated way to share data throughout your application. Use it for request tracing, multi-tenancy, logging enrichment, and anywhere you need data to flow without explicit passing.