- Introducción
- Configuración
- Uso de la caché
- Bloqueos atómicos
- Agregar drivers de caché personalizados
- Eventos
#Introducción
Algunas de las tareas de recuperación o procesamiento de datos realizadas por su aplicación pueden ser intensivas para la CPU o tomar varios segundos en completarse. Cuando esto sucede, es común almacenar en caché los datos recuperados por un tiempo para que puedan ser obtenidos rápidamente en solicitudes posteriores para los mismos datos. Los datos en caché generalmente se almacenan en un almacén de datos muy rápido como Memcached o Redis.
Afortunadamente, Laravel proporciona una API expresiva y unificada para varios backends de caché, permitiéndole aprovechar su rápida recuperación de datos y acelerar su aplicación web.
#Configuración
El archivo de configuración de caché de su aplicación se encuentra en config/cache.php. En este archivo, puede especificar qué driver de caché desea usar por defecto en toda su aplicación. Laravel soporta de forma nativa backends de caché populares como Memcached, Redis, DynamoDB y bases de datos relacionales. Además, está disponible un driver de caché basado en archivos, mientras que los drivers array y "null" proporcionan backends de caché convenientes para sus pruebas automatizadas.
El archivo de configuración de caché también contiene varias otras opciones, que están documentadas dentro del archivo, así que asegúrese de revisarlas. Por defecto, Laravel está configurado para usar el driver de caché file, que almacena los objetos serializados en el sistema de archivos del servidor. Para aplicaciones más grandes, se recomienda usar un driver más robusto como Memcached o Redis. Incluso puede configurar múltiples configuraciones de caché para el mismo driver.
#Requisitos del driver
#Base de datos
Al usar el driver de caché database, necesitará configurar una tabla para contener los elementos de caché. A continuación encontrará un ejemplo de declaración Schema para la tabla:
Schema::create('cache', function (Blueprint $table) {
$table->string('key')->unique();
$table->text('value');
$table->integer('expiration');
});
También puede usar el comando Artisan php artisan cache:table para generar una migración con el esquema adecuado.
#Memcached
Usar el driver Memcached requiere que el paquete PECL Memcached esté instalado. Puede listar todos sus servidores Memcached en el archivo de configuración config/cache.php. Este archivo ya contiene una entrada memcached.servers para comenzar:
'memcached' => [
'servers' => [
[
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
'port' => env('MEMCACHED_PORT', 11211),
'weight' => 100,
],
],
],
Si es necesario, puede establecer la opción host a una ruta de socket UNIX. Si hace esto, la opción port debe establecerse en 0:
'memcached' => [
[
'host' => '/var/run/memcached/memcached.sock',
'port' => 0,
'weight' => 100
],
],
#Redis
Antes de usar una caché Redis con Laravel, necesitará instalar la extensión PhpRedis para PHP vía PECL o instalar el paquete predis/predis (~1.0) vía Composer. Laravel Sail ya incluye esta extensión. Además, plataformas oficiales de despliegue de Laravel como Laravel Forge y Laravel Vapor tienen la extensión PhpRedis instalada por defecto.
Para más información sobre la configuración de Redis, consulte su página de documentación de Laravel.
#DynamoDB
Antes de usar el driver de caché DynamoDB, debe crear una tabla DynamoDB para almacenar todos los datos en caché. Normalmente, esta tabla debería llamarse cache. Sin embargo, debe nombrar la tabla según el valor de la configuración stores.dynamodb.table dentro del archivo de configuración cache de su aplicación.
Esta tabla también debe tener una clave de partición de tipo string con un nombre que corresponda al valor del elemento de configuración stores.dynamodb.attributes.key dentro del archivo de configuración cache de su aplicación. Por defecto, la clave de partición debe llamarse key.
#Uso de la caché
#Obtener una instancia de caché
Para obtener una instancia de almacenamiento de caché, puede usar el facade Cache, que es lo que usaremos a lo largo de esta documentación. El facade Cache proporciona un acceso conveniente y conciso a las implementaciones subyacentes de los contratos de caché de Laravel:
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Cache;
class UserController extends Controller
{
/**
* Mostrar una lista de todos los usuarios de la aplicación.
*/
public function index(): array
{
$value = Cache::get('key');
return [
// ...
];
}
}
#Acceder a múltiples almacenes de caché
Usando el facade Cache, puede acceder a varios almacenes de caché mediante el método store. La clave pasada al método store debe corresponder a uno de los almacenes listados en el arreglo stores en su archivo de configuración cache:
$value = Cache::store('file')->get('foo');
Cache::store('redis')->put('bar', 'baz', 600); // 10 minutos
#Recuperar elementos de la caché
El método get del facade Cache se usa para recuperar elementos de la caché. Si el elemento no existe en la caché, se devolverá null. Si lo desea, puede pasar un segundo argumento al método get especificando el valor predeterminado que desea que se devuelva si el elemento no existe:
$value = Cache::get('key');
$value = Cache::get('key', 'default');
Incluso puede pasar un closure como valor predeterminado. El resultado del closure se devolverá si el elemento especificado no existe en la caché. Pasar un closure le permite diferir la recuperación de valores predeterminados desde una base de datos u otro servicio externo:
$value = Cache::get('key', function () {
return DB::table(/* ... */)->get();
});
#Determinar la existencia de un elemento
El método has puede usarse para determinar si un elemento existe en la caché. Este método también devolverá false si el elemento existe pero su valor es null:
if (Cache::has('key')) {
// ...
}
#Incrementar / Decrementar valores
Los métodos increment y decrement pueden usarse para ajustar el valor de elementos enteros en la caché. Ambos métodos aceptan un segundo argumento opcional que indica la cantidad por la cual incrementar o decrementar el valor del elemento:
// Inicializar el valor si no existe...
Cache::add('key', 0, now()->addHours(4));
// Incrementar o decrementar el valor...
Cache::increment('key');
Cache::increment('key', $amount);
Cache::decrement('key');
Cache::decrement('key', $amount);
#Recuperar y almacenar
A veces puede desear recuperar un elemento de la caché, pero también almacenar un valor predeterminado si el elemento solicitado no existe. Por ejemplo, puede querer recuperar todos los usuarios de la caché o, si no existen, obtenerlos de la base de datos y agregarlos a la caché. Puede hacer esto usando el método Cache::remember:
$value = Cache::remember('users', $seconds, function () {
return DB::table('users')->get();
});
Si el elemento no existe en la caché, se ejecutará el closure pasado al método remember y su resultado se almacenará en la caché.
Puede usar el método rememberForever para recuperar un elemento de la caché o almacenarlo para siempre si no existe:
$value = Cache::rememberForever('users', function () {
return DB::table('users')->get();
});
#Recuperar y eliminar
Si necesita recuperar un elemento de la caché y luego eliminarlo, puede usar el método pull. Al igual que el método get, devolverá null si el elemento no existe en la caché:
$value = Cache::pull('key');
#Almacenar elementos en la caché
Puede usar el método put en el facade Cache para almacenar elementos en la caché:
Cache::put('key', 'value', $seconds = 10);
Si no se pasa el tiempo de almacenamiento al método put, el elemento se almacenará indefinidamente:
Cache::put('key', 'value');
En lugar de pasar el número de segundos como un entero, también puede pasar una instancia de DateTime que represente el tiempo de expiración deseado del elemento en caché:
Cache::put('key', 'value', now()->addMinutes(10));
#Almacenar solo si no está presente
El método add solo agregará el elemento a la caché si no existe ya en el almacén de caché. El método devolverá true si el elemento fue realmente agregado a la caché. De lo contrario, devolverá false. El método add es una operación atómica:
Cache::add('key', 'value', $seconds);
#Almacenar elementos para siempre
El método forever puede usarse para almacenar un elemento en la caché de forma permanente. Dado que estos elementos no expiran, deben ser eliminados manualmente de la caché usando el método forget:
Cache::forever('key', 'value');
Si está usando el driver Memcached, los elementos almacenados "para siempre" pueden ser eliminados cuando la caché alcance su límite de tamaño.
#Eliminar elementos de la caché
Puede eliminar elementos de la caché usando el método forget:
Cache::forget('key');
También puede eliminar elementos proporcionando un número de segundos de expiración cero o negativo:
Cache::put('key', 'value', 0);
Cache::put('key', 'value', -5);
Puede limpiar toda la caché usando el método flush:
Cache::flush();
Limpiar la caché no respeta el "prefijo" configurado para la caché y eliminará todas las entradas de la caché. Considere esto cuidadosamente cuando limpie una caché que es compartida por otras aplicaciones.
#El helper de caché
Además de usar el facade Cache, también puede usar la función global cache para recuperar y almacenar datos a través de la caché. Cuando la función cache se llama con un solo argumento de tipo string, devolverá el valor de la clave dada:
$value = cache('key');
Si proporciona un arreglo de pares clave / valor y un tiempo de expiración a la función, almacenará los valores en la caché por la duración especificada:
cache(['key' => 'value'], $seconds);
cache(['key' => 'value'], now()->addMinutes(10));
Cuando la función cache se llama sin argumentos, devuelve una instancia de la implementación Illuminate\Contracts\Cache\Factory, permitiéndole llamar otros métodos de caché:
cache()->remember('users', $seconds, function () {
return DB::table('users')->get();
});
Al probar llamadas a la función global cache, puede usar el método Cache::shouldReceive tal como si estuviera probando el facade.
#Bloqueos atómicos
Para utilizar esta característica, su aplicación debe usar el driver de caché memcached, redis, dynamodb, database, file o array como driver de caché predeterminado. Además, todos los servidores deben comunicarse con el mismo servidor central de caché.
#Requisitos del driver
#Base de datos
Al usar el driver de caché database, necesitará configurar una tabla para contener los bloqueos de caché de su aplicación. A continuación encontrará un ejemplo de declaración Schema para la tabla:
Schema::create('cache_locks', function (Blueprint $table) {
$table->string('key')->primary();
$table->string('owner');
$table->integer('expiration');
});
Si usó el comando Artisan cache:table para crear la tabla de caché del driver de base de datos, la migración creada por ese comando ya incluye una definición para la tabla cache_locks.
#Gestión de bloqueos
Los bloqueos atómicos permiten la manipulación de bloqueos distribuidos sin preocuparse por condiciones de carrera. Por ejemplo, Laravel Forge usa bloqueos atómicos para asegurar que solo una tarea remota se ejecute en un servidor a la vez. Puede crear y gestionar bloqueos usando el método Cache::lock:
use Illuminate\Support\Facades\Cache;
$lock = Cache::lock('foo', 10);
if ($lock->get()) {
// Bloqueo adquirido por 10 segundos...
$lock->release();
}
El método get también acepta un closure. Después de que el closure se ejecute, Laravel liberará automáticamente el bloqueo:
Cache::lock('foo', 10)->get(function () {
// Bloqueo adquirido por 10 segundos y liberado automáticamente...
});
Si el bloqueo no está disponible en el momento en que lo solicita, puede indicar a Laravel que espere un número especificado de segundos. Si el bloqueo no puede ser adquirido dentro del límite de tiempo especificado, se lanzará una excepción Illuminate\Contracts\Cache\LockTimeoutException:
use Illuminate\Contracts\Cache\LockTimeoutException;
$lock = Cache::lock('foo', 10);
try {
$lock->block(5);
// Bloqueo adquirido después de esperar un máximo de 5 segundos...
} catch (LockTimeoutException $e) {
// No se pudo adquirir el bloqueo...
} finally {
$lock?->release();
}
El ejemplo anterior puede simplificarse pasando un closure al método block. Cuando se pasa un closure a este método, Laravel intentará adquirir el bloqueo durante el número especificado de segundos y liberará automáticamente el bloqueo una vez que el closure haya sido ejecutado:
Cache::lock('foo', 10)->block(5, function () {
// Bloqueo adquirido después de esperar un máximo de 5 segundos...
});
#Gestión de bloqueos entre procesos
A veces, puede querer adquirir un bloqueo en un proceso y liberarlo en otro proceso. Por ejemplo, puede adquirir un bloqueo durante una solicitud web y querer liberar el bloqueo al final de un trabajo en cola que es disparado por esa solicitud. En este escenario, debe pasar el "token de propietario" del bloqueo al trabajo en cola para que el trabajo pueda re-instanciar el bloqueo usando el token dado.
En el siguiente ejemplo, despacharemos un trabajo en cola si un bloqueo es adquirido exitosamente. Además, pasaremos el token de propietario del bloqueo al trabajo en cola mediante el método owner del bloqueo:
$podcast = Podcast::find($id);
$lock = Cache::lock('processing', 120);
if ($lock->get()) {
ProcessPodcast::dispatch($podcast, $lock->owner());
}
Dentro del trabajo ProcessPodcast de nuestra aplicación, podemos restaurar y liberar el bloqueo usando el token de propietario:
Cache::restoreLock('processing', $this->owner)->release();
Si desea liberar un bloqueo sin respetar su propietario actual, puede usar el método forceRelease:
Cache::lock('processing')->forceRelease();
#Agregar drivers de caché personalizados
#Escribir el driver
Para crear nuestro driver de caché personalizado, primero necesitamos implementar el contrato Illuminate\Contracts\Cache\Store. Así, una implementación de caché MongoDB podría verse algo así:
<?php
namespace App\Extensions;
use Illuminate\Contracts\Cache\Store;
class MongoStore implements Store
{
public function get($key) {}
public function many(array $keys) {}
public function put($key, $value, $seconds) {}
public function putMany(array $values, $seconds) {}
public function increment($key, $value = 1) {}
public function decrement($key, $value = 1) {}
public function forever($key, $value) {}
public function forget($key) {}
public function flush() {}
public function getPrefix() {}
}
Solo necesitamos implementar cada uno de estos métodos usando una conexión MongoDB. Para un ejemplo de cómo implementar cada uno de estos métodos, eche un vistazo a Illuminate\Cache\MemcachedStore en el código fuente del framework Laravel. Una vez que nuestra implementación esté completa, podemos finalizar el registro de nuestro driver personalizado llamando al método extend del facade Cache:
Cache::extend('mongo', function (Application $app) {
return Cache::repository(new MongoStore);
});
Si se pregunta dónde colocar el código de su driver de caché personalizado, podría crear un espacio de nombres Extensions dentro de su directorio app. Sin embargo, tenga en cuenta que Laravel no tiene una estructura rígida para la aplicación y usted es libre de organizar su aplicación según sus preferencias.
#Registrar el driver
Para registrar el driver de caché personalizado con Laravel, usaremos el método extend en el facade Cache. Dado que otros proveedores de servicios pueden intentar leer valores en caché dentro de su método boot, registraremos nuestro driver personalizado dentro de un callback booting. Al usar el callback booting, podemos asegurarnos de que el driver personalizado se registre justo antes de que se llame al método boot en los proveedores de servicios de nuestra aplicación, pero después de que se llame al método register en todos los proveedores de servicios. Registraremos nuestro callback booting dentro del método register de la clase App\Providers\AppServiceProvider de nuestra aplicación:
<?php
namespace App\Providers;
use App\Extensions\MongoStore;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Registrar cualquier servicio de la aplicación.
*/
public function register(): void
{
$this->app->booting(function () {
Cache::extend('mongo', function (Application $app) {
return Cache::repository(new MongoStore);
});
});
}
/**
* Inicializar cualquier servicio de la aplicación.
*/
public function boot(): void
{
// ...
}
}
El primer argumento pasado al método extend es el nombre del driver. Esto corresponderá a su opción driver en el archivo de configuración config/cache.php. El segundo argumento es un closure que debe devolver una instancia de Illuminate\Cache\Repository. El closure recibirá una instancia $app, que es una instancia del contenedor de servicios.
Una vez que su extensión esté registrada, actualice la opción driver en el archivo de configuración config/cache.php al nombre de su extensión.
#Eventos
Para ejecutar código en cada operación de caché, puede escuchar los eventos disparados por la caché. Normalmente, debe colocar estos listeners de eventos dentro de la clase App\Providers\EventServiceProvider de su aplicación:
use App\Listeners\LogCacheHit;
use App\Listeners\LogCacheMissed;
use App\Listeners\LogKeyForgotten;
use App\Listeners\LogKeyWritten;
use Illuminate\Cache\Events\CacheHit;
use Illuminate\Cache\Events\CacheMissed;
use Illuminate\Cache\Events\KeyForgotten;
use Illuminate\Cache\Events\KeyWritten;
/**
* Mapeo de listeners de eventos para la aplicación.
*
* @var array
*/
protected $listen = [
CacheHit::class => [
LogCacheHit::class,
],
CacheMissed::class => [
LogCacheMissed::class,
],
KeyForgotten::class => [
LogKeyForgotten::class,
],
KeyWritten::class => [
LogKeyWritten::class,
],
];