1. Profundizando
  2. Caché

Únete a nuestra comunidad de Telegram @webblend!

Aquí encontrarás fragmentos de código de Laravel y consejos útiles sobre desarrollo web.

Introducción

Algunas de las tareas de recuperación o procesamiento de datos realizadas por tu aplicación podrían ser intensivas en CPU o llevar varios segundos en completarse. Cuando este sea el caso, es común almacenar en caché los datos recuperados durante un tiempo para poder recuperarlos 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é, lo que te permite aprovechar su rápida recuperación de datos y acelerar tu aplicación web.

Configuración

El archivo de configuración de caché de tu aplicación se encuentra en config/cache.php. En este archivo, puedes especificar qué controlador de caché te gustaría usar de forma predeterminada en toda tu aplicación. Laravel es compatible con backends de caché populares como Memcached, Redis, DynamoDB y bases de datos relacionales. Además, hay disponible un controlador de caché basado en archivos, mientras que los controladores de caché array y "null" proporcionan backends de caché convenientes para tus pruebas automatizadas.

El archivo de configuración de caché también contiene varias otras opciones, que se documentan dentro del archivo, así que asegúrate de leer estas opciones. De forma predeterminada, Laravel está configurado para usar el controlador de caché file, que almacena objetos serializados en el sistema de archivos del servidor. Para aplicaciones más grandes, se recomienda que uses un controlador más robusto como Memcached o Redis. Incluso puedes configurar múltiples configuraciones de caché para el mismo controlador.

Requisitos previos del controlador

Base de datos

Al usar el controlador de caché database, deberás configurar una tabla para contener los elementos de caché. Encontrarás un ejemplo de declaración de Schema para la tabla a continuación:

Schema::create('cache', function (Blueprint $table) {
$table->string('key')->unique();
$table->text('value');
$table->integer('expiration');
});

Nota También puedes usar el comando Artisan php artisan cache:table para generar una migración con el esquema adecuado.

Memcached

Para usar el controlador Memcached, es necesario instalar el paquete Memcached PECL. Puedes listar todos tus 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, puedes establecer la opción host en la ruta del socket UNIX. Si haces 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, deberás instalar la extensión PHP PhpRedis a través de PECL o instalar el paquete predis/predis (~1.0) a través de Composer. Laravel Sail ya incluye esta extensión. Además, las plataformas oficiales de implementación de Laravel, como Laravel Forge y Laravel Vapor, tienen instalada la extensión PhpRedis por defecto.

Para obtener más información sobre la configuración de Redis, consulta su página de documentación de Laravel.

DynamoDB

Antes de usar el controlador de caché DynamoDB, debes crear una tabla DynamoDB para almacenar todos los datos en caché. Normalmente, esta tabla debería llamarse cache. Sin embargo, debes nombrar la tabla según el valor de la configuración stores.dynamodb.table dentro del archivo de configuración cache de tu aplicación.

Esta tabla también debe tener una clave de partición de cadena con un nombre que coincida con el valor de stores.dynamodb.attributes.key en la configuración de caché de tu aplicación. Por defecto, la clave de partición debería llamarse key.

Uso de la caché

Obtener una instancia de caché

Para obtener una instancia de almacenamiento en caché, puedes utilizar el facade Cache, que es lo que usaremos en toda 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 [
// ...
];
}
}

Acceso a múltiples almacenes de caché

Usando el facade Cache, puedes acceder a varios almacenes de caché a través del método store. La clave pasada al método store debe corresponder a uno de los almacenes enumerados en la matriz de configuración stores en tu 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 utiliza para recuperar elementos de la caché. Si el elemento no existe en la caché, se devolverá null. Si lo deseas, puedes pasar un segundo argumento al método get especificando el valor predeterminado que deseas que se devuelva si el elemento no existe:

$value = Cache::get('key');
 
$value = Cache::get('key', 'default');

Incluso puedes pasar una clausura como valor predeterminado. El resultado de la clausura se devolverá si el elemento especificado no existe en la caché. Pasar una clausura te permite posponer la recuperación de valores predeterminados desde una base de datos u otro servicio externo:

$value = Cache::get('key', function () {
return DB::table(/* ... */)->get();
});

Comprobación de la existencia de elementos

El método has se puede utilizar 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 se pueden utilizar para ajustar el valor de elementos enteros en la caché. Ambos métodos aceptan un segundo argumento opcional que indica la cantidad en la que se debe 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, puedes desear recuperar un elemento de la caché, pero también almacenar un valor predeterminado si el elemento solicitado no existe. Por ejemplo, puedes desear recuperar todos los usuarios de la caché o, si no existen, recuperarlos de la base de datos y agregarlos a la caché. Puedes hacer esto utilizando 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á la clausura pasada al método remember y su resultado se colocará en la caché.

Puedes 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 necesitas recuperar un elemento de la caché y luego eliminar el elemento, puedes usar el método pull. Al igual que el método get, se devolverá null si el elemento no existe en la caché:

$value = Cache::pull('key');

Almacenar elementos en la caché

Puedes 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 puedes pasar una instancia de DateTime que represente el tiempo de vencimiento deseado del elemento almacenado en caché:

Cache::put('key', 'value', now()->addMinutes(10));

Almacenar si no está presente

El método add solo agregará el elemento a la caché si aún no existe en el almacén de caché. El método devolverá true si el elemento se agrega realmente a la caché. De lo contrario, el método 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 se puede utilizar para almacenar un elemento en la caché permanentemente. Dado que estos elementos no expirarán, deben eliminarse manualmente de la caché utilizando el método forget:

Cache::forever('key', 'value');

Nota Si estás utilizando el controlador Memcached, los elementos almacenados "para siempre" pueden eliminarse cuando la caché alcanza su límite de tamaño.

Eliminar elementos de la caché

Puedes eliminar elementos de la caché utilizando el método forget:

Cache::forget('key');

También puedes eliminar elementos proporcionando un número de segundos de expiración cero o negativo:

Cache::put('key', 'value', 0);
 
Cache::put('key', 'value', -5);

Puedes borrar toda la caché utilizando el método flush:

Cache::flush();

Advertencia Vaciar la caché no respeta tu "prefijo" de caché configurado y eliminará todas las entradas de la caché. Considera esto cuidadosamente al borrar una caché que es compartida por otras aplicaciones.

El ayudante de la caché

Además de utilizar el facade Cache, también puedes usar la función global cache para recuperar y almacenar datos a través de la caché. Cuando se llama a la función cache con un solo argumento de tipo cadena, devolverá el valor de la clave proporcionada:

$value = cache('key');

Si proporcionas una matriz de pares clave/valor y un tiempo de vencimiento a la función, almacenará los valores en la caché durante la duración especificada:

cache(['key' => 'value'], $seconds);
 
cache(['key' => 'value'], now()->addMinutes(10));

Cuando se llama a la función cache sin argumentos, devuelve una instancia de la implementación de Illuminate\Contracts\Cache\Factory, lo que te permite llamar a otros métodos de caché:

cache()->remember('users', $seconds, function () {
return DB::table('users')->get();
});

Nota Cuando estás probando la llamada a la función global cache, puedes usar el método Cache::shouldReceive de la misma manera que si estuvieras probando el facade.

Bloqueos atómicos

Advertencia Para utilizar esta función, tu aplicación debe estar utilizando el controlador de caché predeterminado memcached, redis, dynamodb, database, file o array. Además, todos los servidores deben comunicarse con el mismo servidor central de caché.

Requisitos previos del controlador

Base de datos

Al usar el controlador de caché database, deberás configurar una tabla para contener los bloqueos de caché de tu aplicación. Encontrarás un ejemplo de declaración de Schema para la tabla a continuación:

Schema::create('cache_locks', function (Blueprint $table) {
$table->string('key')->primary();
$table->string('owner');
$table->integer('expiration');
});

Nota Si utilizaste el comando Artisan cache:table para crear la tabla de caché del controlador de la 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 las condiciones de carrera. Por ejemplo, Laravel Forge utiliza bloqueos atómicos para asegurarse de que solo se esté ejecutando una tarea remota en un servidor en un momento dado. Puedes crear y gestionar bloqueos utilizando el método Cache::lock:

use Illuminate\Support\Facades\Cache;
 
$lock = Cache::lock('foo', 10);
 
if ($lock->get()) {
// Bloqueo adquirido durante 10 segundos...
 
$lock->release();
}

El método get también acepta una clausura. Después de que se ejecute la clausura, Laravel liberará automáticamente el bloqueo:

Cache::lock('foo', 10)->get(function () {
// Bloqueo adquirido durante 10 segundos y liberado automáticamente...
});

Si el bloqueo no está disponible en el momento en que lo solicitas, puedes indicarle a Laravel que espere durante un número especificado de segundos. Si no se puede adquirir el bloqueo dentro del límite de tiempo especificado, se lanzará una 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 se puede simplificar pasando una clausura al método block. Cuando se pasa una clausura a este método, Laravel intentará adquirir el bloqueo durante el número especificado de segundos y automáticamente liberará el bloqueo una vez que se haya ejecutado la clausura:

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, puedes querer adquirir un bloqueo en un proceso y liberarlo en otro proceso. Por ejemplo, puedes adquirir un bloqueo durante una solicitud web y desear liberar el bloqueo al final de un trabajo en cola que es activado por esa solicitud. En este escenario, debes pasar el "token de propietario" del bloqueo al trabajo en cola para que el trabajo pueda reinstantáneo el bloqueo usando el token dado.

En el ejemplo a continuación, despacharemos un trabajo en cola si se adquiere el bloqueo correctamente. 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 deseas liberar un bloqueo sin respetar a su propietario actual, puedes usar el método forceRelease:

Cache::lock('processing')->forceRelease();

Agregar controladores de caché personalizados

Escribir el controlador

Para crear nuestro controlador de caché personalizado, primero debemos implementar el contrato Illuminate\Contracts\Cache\Store. Entonces, una implementación de caché de 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 utilizando una conexión de MongoDB. Para ver un ejemplo de cómo implementar cada uno de estos métodos, echa 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 controlador de forma llamando al método extend del facade Cache:

Cache::extend('mongo', function (Application $app) {
return Cache::repository(new MongoStore);
});

Nota Si te preguntas dónde poner tu código personalizado del controlador de caché, podrías crear un espacio de nombres Extensions dentro de tu directorio app. Sin embargo, ten en cuenta que Laravel no tiene una estructura de aplicación rígida y eres libre de organizar tu aplicación según tus preferencias.

Registrar el controlador

Para registrar el controlador de caché personalizado con Laravel, utilizaremos 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 controlador personalizado dentro de un callback booting. Al usar el callback booting, podemos asegurarnos de que el controlador 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 la 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 controlador. Esto corresponderá a tu opción driver en el archivo de configuración config/cache.php. El segundo argumento es una clausura que debe devolver una instancia de Illuminate\Cache\Repository. La clausura recibirá una instancia $app, que es una instancia del contenedor de servicios.

Una vez que tu extensión esté registrada, actualiza la opción driver del archivo de configuración config/cache.php con el nombre de tu extensión.

Eventos

Para ejecutar código en cada operación de caché, puedes escuchar los eventos emitidos por la caché. Por lo general, deberías colocar estos escuchadores de eventos dentro de la clase App\Providers\EventServiceProvider de tu 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;
 
/**
* Las asignaciones de escuchadores 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,
],
];