Context
Introducción
Las capacidades de «contexto» de Laravel te permiten capturar, recuperar y compartir información a lo largo de solicitudes, trabajos y comandos que se ejecutan dentro de tu aplicación. Esta información capturada también se incluye en los registros escritos por tu aplicación, brindándote una visión más profunda del historial de ejecución del código que ocurrió antes de que se escribiera una entrada de registro y permitiéndote rastrear los flujos de ejecución a lo largo de un sistema distribuido.
Cómo Funciona
La mejor manera de entender las capacidades de contexto de Laravel es verlas en acción utilizando las características de registro integradas. Para comenzar, puedes agregar información al contexto utilizando la fachada Context
. En este ejemplo, usaremos un middleware para agregar la URL de la solicitud y un ID de seguimiento único al contexto en cada solicitud entrante:
<?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
{
/**
* Manejar una solicitud entrante.
*/
public function handle(Request $request, Closure $next): Response
{
Context::add('url', $request->url());
Context::add('trace_id', Str::uuid()->toString());
return $next($request);
}
}
La información agregada al contexto se adjunta automáticamente como metadatos a cualquier entrada de registro que se escriba a lo largo de la solicitud. Adjuntar el contexto como metadatos permite que la información pasada a entradas de registro individuales se diferencie de la información compartida a través de Context
. Por ejemplo, imagina que escribimos la siguiente entrada de registro:
Log::info('Usuario autenticado.', ['auth_id' => Auth::id()]);
El registro escrito contendrá el auth_id
pasado a la entrada de registro, pero también contendrá la url
y el trace_id
del contexto como metadatos:
Usuario autenticado. {"auth_id":27} {"url":"https://example.com/login","trace_id":"e04e1a11-e75c-4db3-b5b5-cfef4ef56697"}
La información agregada al contexto también está disponible para los trabajos enviados a la cola. Por ejemplo, imagina que enviamos un trabajo ProcessPodcast
a la cola después de agregar algo de información al contexto:
// En nuestro middleware...
Context::add('url', $request->url());
Context::add('trace_id', Str::uuid()->toString());
// En nuestro controlador...
ProcessPodcast::dispatch($podcast);
Cuando se envía el trabajo, cualquier información almacenada actualmente en el contexto se captura y se comparte con el trabajo. La información capturada se rehidrata en el contexto actual mientras se ejecuta el trabajo. Entonces, si el método handle
de nuestro trabajo escribiera en el registro:
class ProcessPodcast implements ShouldQueue
{
use Queueable;
// ...
/**
* Ejecutar el trabajo.
*/
public function handle(): void
{
Log::info('Procesando podcast.', [
'podcast_id' => $this->podcast->id,
]);
// ...
}
}
La entrada de registro resultante contendría la información que se agregó al contexto durante la solicitud que originalmente envió el trabajo:
Procesando podcast. {"podcast_id":95} {"url":"https://example.com/login","trace_id":"e04e1a11-e75c-4db3-b5b5-cfef4ef56697"}
Aunque nos hemos centrado en las características relacionadas con el registro integradas de Laravel, la siguiente documentación ilustrará cómo el contexto te permite compartir información a través del límite de solicitud HTTP / trabajo en cola e incluso cómo agregar datos de contexto ocultos que no se escriben con las entradas de registro.
Capturando Contexto
Puedes almacenar información en el contexto actual utilizando el método add
de la fachada Context
:
use Illuminate\Support\Facades\Context;
Context::add('key', 'value');
Para agregar múltiples elementos a la vez, puedes pasar un array asociativo al método add
:
Context::add([
'first_key' => 'value',
'second_key' => 'value',
]);
El método add
sobrescribirá cualquier valor existente que comparta la misma clave. Si solo deseas agregar información al contexto si la clave no existe, puedes usar el método addIf
:
Context::add('key', 'first');
Context::get('key');
// "first"
Context::addIf('key', 'second');
Context::get('key');
// "first"
Contexto Condicional
El método when
puede usarse para agregar datos al contexto basado en una condición dada. El primer cierre proporcionado al método when
se invocará si la condición dada se evalúa como verdadera, mientras que el segundo cierre se invocará si la condición se evalúa como falsa:
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', []),
);
Pilas
Context
ofrece la capacidad de crear «pilas», que son listas de datos almacenados en el orden en que se agregaron. Puedes agregar información a una pila invocando el método 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',
// ]
Las pilas pueden ser útiles para capturar información histórica sobre una solicitud, como eventos que están ocurriendo a lo largo de tu aplicación. Por ejemplo, podrías crear un listener de eventos para agregar a una pila cada vez que se ejecuta una consulta, capturando el SQL de la consulta y la duración como una tupla:
use Illuminate\Support\Facades\Context;
use Illuminate\Support\Facades\DB;
DB::listen(function ($event) {
Context::push('queries', [$event->time, $event->sql]);
});
Puedes determinar si un valor está en una pila usando los métodos stackContains
y hiddenStackContains
:
if (Context::stackContains('breadcrumbs', 'first_value')) {
//
}
if (Context::hiddenStackContains('secrets', 'first_value')) {
//
}
Los métodos stackContains
y hiddenStackContains
también aceptan un cierre como su segundo argumento, permitiendo un mayor control sobre la operación de comparación de valores:
use Illuminate\Support\Facades\Context;
use Illuminate\Support\Str;
return Context::stackContains('breadcrumbs', function ($value) {
return Str::startsWith($value, 'query_');
});
Recuperando Contexto
Puedes recuperar información del contexto utilizando el método get
de la fachada Context
:
use Illuminate\Support\Facades\Context;
$value = Context::get('key');
El método only
puede usarse para recuperar un subconjunto de la información en el contexto:
$data = Context::only(['first_key', 'second_key']);
El método pull
puede usarse para recuperar información del contexto y eliminarla inmediatamente del contexto:
$value = Context::pull('key');
Si deseas recuperar toda la información almacenada en el contexto, puedes invocar el método all
:
$data = Context::all();
Determinando la Existencia de un Elemento
Puedes usar el método has
para determinar si el contexto tiene algún valor almacenado para la clave dada:
use Illuminate\Support\Facades\Context;
if (Context::has('key')) {
// ...
}
El método has
devolverá true
independientemente del valor almacenado. Por ejemplo, una clave con un valor null
se considerará presente:
Context::add('key', null);
Context::has('key');
// true
Eliminando Contexto
El método forget
puede usarse para eliminar una clave y su valor del contexto actual:
use Illuminate\Support\Facades\Context;
Context::add(['first_key' => 1, 'second_key' => 2]);
Context::forget('first_key');
Context::all();
// ['second_key' => 2]
Puedes olvidar varias claves a la vez proporcionando un array al método forget
:
Context::forget(['first_key', 'second_key']);
Contexto Oculto
Context
ofrece la capacidad de almacenar datos «ocultos». Esta información oculta no se adjunta a los registros y no es accesible a través de los métodos de recuperación de datos documentados anteriormente. Context
proporciona un conjunto diferente de métodos para interactuar con la información de contexto oculta:
use Illuminate\Support\Facades\Context;
Context::addHidden('key', 'value');
Context::getHidden('key');
// 'value'
Context::get('key');
// null
Los métodos «ocultos» reflejan la funcionalidad de los métodos no ocultos documentados anteriormente:
Context::addHidden(/* ... */);
Context::addHiddenIf(/* ... */);
Context::pushHidden(/* ... */);
Context::getHidden(/* ... */);
Context::pullHidden(/* ... */);
Context::onlyHidden(/* ... */);
Context::allHidden(/* ... */);
Context::hasHidden(/* ... */);
Context::forgetHidden(/* ... */);
Eventos
Context
despacha dos eventos que te permiten engancharte en el proceso de hidratación y deshidratación del contexto.
Para ilustrar cómo se pueden usar estos eventos, imagina que en un middleware de tu aplicación configuras el valor app.locale
basado en el encabezado Accept-Language
de la solicitud HTTP entrante. Los eventos de Context
te permiten capturar este valor durante la solicitud y restaurarlo en la cola, asegurando que las notificaciones enviadas en la cola tengan el valor correcto de app.locale
. Podemos usar los eventos de Context
y los datos ocultos para lograr esto, lo cual la siguiente documentación ilustrará.
Deshidratando
Siempre que se envía un trabajo a la cola, los datos en el contexto se «deshidratan» y se capturan junto con la carga útil del trabajo. El método Context::dehydrating
te permite registrar un cierre que se invocará durante el proceso de deshidratación. Dentro de este cierre, puedes hacer cambios en los datos que se compartirán con el trabajo en cola.
Normalmente, deberías registrar callbacks de deshidratación dentro del método boot
de la clase AppServiceProvider
de tu aplicación:
use Illuminate\Log\Context\Repository;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Context;
/**
* Inicializar cualquier servicio de la aplicación.
*/
public function boot(): void
{
Context::dehydrating(function (Repository $context) {
$context->addHidden('locale', Config::get('app.locale'));
});
}
No deberías usar la fachada Context
dentro del callback de deshidratación, ya que eso cambiará el contexto del proceso actual. Asegúrate de hacer cambios solo en el repositorio pasado al callback.
Hidratado
Siempre que un trabajo en cola comienza a ejecutarse en la cola, cualquier contexto que se compartió con el trabajo se «hidrata» de nuevo en el contexto actual. El método Context::hydrated
te permite registrar un cierre que se invocará durante el proceso de hidratación.
Normalmente, deberías registrar callbacks de hidratación dentro del método boot
de la clase AppServiceProvider
de tu aplicación:
use Illuminate\Log\Context\Repository;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Context;
/**
* Inicializar cualquier servicio de la aplicación.
*/
public function boot(): void
{
Context::hydrated(function (Repository $context) {
if ($context->hasHidden('locale')) {
Config::set('app.locale', $context->getHidden('locale'));
}
});
}
No deberías usar la fachada Context
dentro del callback de hidratación y en su lugar asegúrate de hacer cambios solo en el repositorio pasado al callback.