Las consultas a la base de datos o las llamadas a APIs externas tienen un coste elevado en CPU y en red, y su procesamiento puede tardar varios segundos.
Si necesitas obtener los mismos datos una y otra vez, guardar el resultado en caché permite atender las peticiones posteriores mucho más rápido.Laravel ofrece una API unificada compatible con distintos backends de caché: Memcached, Redis, DynamoDB, base de datos y otros.
Por defecto se utiliza el driver database. Puedes cambiarlo a Redis o Memcached para conseguir una caché aún más rápida.
Guarda los datos de caché serializados en una tabla de la base de datos.
Los nuevos proyectos con Laravel 11 y posteriores incluyen la migración desde el principio.
CACHE_STORE=database
Si no se incluye la migración, créala con Artisan.
php artisan make:cache-tablephp artisan migrate
file
Guarda los datos de caché en el sistema de ficheros.
No requiere configuración adicional y es adecuado para aplicaciones pequeñas.
CACHE_STORE=file
redis
Driver de caché en memoria muy rápido.
Es el más habitual en producción. Requiere la extensión PhpRedis o el paquete predis/predis.
El método get() obtiene datos de la caché.
Devuelve null si no existen. También puedes indicar un valor por defecto.
$value = Cache::get('key');// Valor por defecto$value = Cache::get('key', 'default');// Valor por defecto perezoso mediante closure$value = Cache::get('key', function () { return DB::table('settings')->get();});
El método put() guarda datos en la caché. En el tercer argumento se indica el TTL en segundos.
// Guardar durante 10 segundosCache::put('key', 'value', 10);// Indicar el TTL con un objeto CarbonCache::put('key', 'value', now()->plus(minutes: 10));// Sin caducidad (almacenamiento permanente)Cache::put('key', 'value');
También existe add(), que guarda el valor solo si la clave no existe.
// Solo se añade si no existe (operación atómica)Cache::add('key', 'value', $seconds);
Para guardar de forma permanente utiliza forever().
Es la operación más habitual. Si la clave existe en la caché devuelve su valor; si no, ejecuta la closure y almacena el resultado.
$users = Cache::remember('users', 3600, function () { return DB::table('users')->get();});
Cache::remember() reduce a una sola línea el patrón «comprobar la caché → si no está, obtener el valor → almacenarlo». Es ideal para cachear resultados de consultas a base de datos o respuestas de APIs externas.
También existe la versión permanente rememberForever().
$value = Cache::rememberForever('users', function () { return DB::table('users')->get();});
Si necesitas saber si la caché ha «acertado» o si el valor se acaba de generar en la closure, utiliza rememberWithWarmth(). Devuelve un array con el valor y un booleano que indica si es «cálido» (proveniente de la caché).
[$value, $warm] = Cache::rememberWithWarmth('users', 3600, function () { return DB::table('users')->get();});if ($warm) { // Datos de la caché} else { // Datos recién obtenidos por la closure}
rememberWithWarmth() es útil para monitorizar la eficiencia de la caché. Si $warm es false la mayoría de las veces, puede que te convenga ajustar el TTL.
Cache::flexible() recibe un array con el «periodo de frescura» y el «periodo aceptable aunque caduco». Devuelve datos antiguos al usuario y refresca la caché en segundo plano.
// [tiempo fresco (s), tiempo aceptable aunque caduco (s)]$value = Cache::flexible('users', [5, 10], function () { return DB::table('users')->get();});
// Inicializa el valor si no existeCache::add('key', 0, now()->addHours(4));// Incrementar / decrementarCache::increment('key');Cache::increment('key', $amount);Cache::decrement('key');Cache::decrement('key', $amount);
Con el driver memo puedes memorizar en memoria los accesos a la caché durante una misma petición.
Reduce los viajes de ida y vuelta al almacén de caché cuando se accede repetidas veces a la misma clave.
use Illuminate\Support\Facades\Cache;// Memoizar el almacén por defecto$value = Cache::memo()->get('key');// Memoizar el almacén Redis$value = Cache::memo('redis')->get('key');
// El primer acceso consulta al almacén de caché$value = Cache::memo()->get('key');// Los accesos posteriores devuelven desde memoria (sin ir al almacén)$value = Cache::memo()->get('key');
// Elimina todos los elementos con las etiquetas 'people' y 'authors' (borra John y Anne)Cache::tags(['people', 'authors'])->flush();// Elimina solo los de la etiqueta 'authors' (borra Anne; John se mantiene)Cache::tags('authors')->flush();
Las etiquetas son útiles cuando quieres invalidar caché por grupos (por usuario, por artículo, etc.).
Ejemplo: Cache::tags(['user', "user:{$userId}"])->flush() borra todas las cachés de ese usuario.
Cache::lock() permite implementar locks distribuidos para evitar condiciones de carrera entre procesos o peticiones paralelas.
Esta función está disponible con los drivers memcached, redis, dynamodb, database, file y array. Todos los servidores deben comunicarse con el mismo servidor central de caché.
Espera hasta el número indicado de segundos a obtener el lock. Si se agota el tiempo se lanza LockTimeoutException.
use Illuminate\Contracts\Cache\LockTimeoutException;$lock = Cache::lock('foo', 10);try { $lock->block(5); // Espera hasta 5 segundos // Procesamiento tras obtener el lock} catch (LockTimeoutException $e) { // No se ha podido obtener el lock} finally { $lock->release();}
Con una closure el código queda más limpio.
Cache::lock('foo', 10)->block(5, function () { // Espera hasta 5 segundos por el lock y lo libera al finalizar});
Una forma sencilla de impedir que un mismo proceso se ejecute varias veces a la vez.
Cache::withoutOverlapping('foo', function () { // Solo se ejecuta uno a la vez});// Personaliza el tiempo de espera y la duración del lockCache::withoutOverlapping('foo', function () { // ...}, lockFor: 120, waitFor: 5);
Cache::funnel('foo') ->limit(3) // Permite hasta 3 ejecuciones paralelas ->releaseAfter(60) // Se libera automáticamente a los 60 s ->block(10) // Espera hasta 10 s ->then(function () { // Bloque de código si se ha obtenido una ranura }, function () { // Bloque de código si no se ha obtenido ranura });
Caso típico: obtener el lock en una petición web y liberarlo desde un job de la cola.
// En la petición se obtiene el lock y se despacha el job$lock = Cache::lock('processing', 120);if ($lock->get()) { ProcessPodcast::dispatch($podcast, $lock->owner());}// Dentro del job se libera el lockCache::restoreLock('processing', $this->owner)->release();
Para liberar el lock de forma forzada, sin importar el propietario actual, utiliza el método forceRelease.
Con el método refresh puedes ampliar la validez del lock que ya tienes. Si no indicas los segundos se usa la duración con la que se creó. Es útil para procesos largos en los que quieres ir extendiendo un lock corto en lugar de configurar de entrada un TTL muy grande.
$lock = Cache::lock('generate-reports', 60);if ($lock->get()) { foreach ($reports as $report) { $report->generate(); // Amplía el lock otros 60 segundos $lock->refresh(); } $lock->release();}
La función auxiliar cache() permite hacer lo mismo que el facade Cache con una sintaxis más breve.
// Obtener un valor$value = cache('key');// Guardar un valor (con TTL)cache(['key' => 'value'], $seconds);cache(['key' => 'value'], now()->addMinutes(10));// Sin argumentos obtienes la misma instancia que con el facadecache()->remember('users', $seconds, function () { return DB::table('users')->get();});
Cachear resultados de consultas a la base de datos
1
Cachear el resultado de la consulta en el controlador
use Illuminate\Support\Facades\Cache;use Illuminate\Support\Facades\DB;public function index(): array{ $users = Cache::remember('all-users', 3600, function () { return DB::table('users')->orderBy('name')->get(); }); return compact('users');}
2
Cuando los datos cambien, invalida la caché
public function store(Request $request): RedirectResponse{ User::create($request->validated()); // Elimina la caché para que se regenere en la siguiente petición Cache::forget('all-users'); return redirect()->route('users.index');}
use App\Models\Product;use Illuminate\Support\Facades\Cache;// Cachear una hora el listado de productos por categoríapublic function byCategory(int $categoryId): array{ $products = Cache::remember( "products:category:{$categoryId}", 3600, fn () => Product::where('category_id', $categoryId) ->where('is_active', true) ->orderBy('name') ->get() ); return compact('products');}
use Illuminate\Support\Facades\Cache;class ArticleController extends Controller{ public function show(Article $article): array { $data = Cache::tags(['articles', "article:{$article->id}"]) ->remember("article:{$article->id}:detail", 3600, function () use ($article) { return $article->load(['author', 'tags', 'comments']); }); return compact('data'); } public function update(Request $request, Article $article): RedirectResponse { $article->update($request->validated()); // Limpia todas las cachés relacionadas con este artículo Cache::tags(["article:{$article->id}"])->flush(); return redirect()->route('articles.show', $article); }}