Vai al contenuto principale

Cos’è la cache

Le interrogazioni al database e le chiamate ad API esterne hanno un alto costo in CPU e rete e possono impiegare diversi secondi. Se recuperi ripetutamente gli stessi dati, salvare il risultato in cache ti permette di gestire le richieste successive molto più velocemente. Laravel offre un’API unificata compatibile con vari backend di cache: Memcached, Redis, DynamoDB, database e altri.
Di default è configurato il driver database. Passando a Redis o Memcached ottieni una cache ancora più veloce.

Configurazione della cache

config/cache.php

Le impostazioni di cache sono concentrate in config/cache.php. Cambia il driver predefinito con la variabile d’ambiente CACHE_STORE.
// config/cache.php
'default' => env('CACHE_STORE', 'database'),

Driver disponibili

Salva i dati serializzati in una tabella del database. Dai nuovi progetti Laravel 11 in poi la migration è già inclusa.
CACHE_STORE=database
Se la migration non è inclusa, creala con Artisan.
php artisan make:cache-table
php artisan migrate
Salva i dati di cache sul filesystem. Non richiede setup aggiuntivo, adatto per applicazioni piccole.
CACHE_STORE=file
Driver di cache veloce, in-memory. Il più usato in produzione. Serve l’estensione PHP PhpRedis o il pacchetto predis/predis.
CACHE_STORE=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
Serve il pacchetto PECL Memcached. Configura i server in config/cache.php.
'memcached' => [
    'servers' => [
        [
            'host' => env('MEMCACHED_HOST', '127.0.0.1'),
            'port' => env('MEMCACHED_PORT', 11211),
            'weight' => 100,
        ],
    ],
],
Usa AWS DynamoDB come cache store. Crea prima la tabella DynamoDB e installa l’AWS SDK.
composer require aws/aws-sdk-php
CACHE_STORE=dynamodb
DYNAMODB_CACHE_TABLE=cache
AWS_DEFAULT_REGION=us-east-1
AWS_ACCESS_KEY_ID=your-key-id
AWS_SECRET_ACCESS_KEY=your-secret-key
Usa un qualsiasi disco filesystem come cache store chiave/valore. Utile per riutilizzare un disco S3 esistente come cache.
'storage' => [
    'driver' => 'storage',
    'disk' => env('CACHE_STORAGE_DISK'),
    'path' => env('CACHE_STORAGE_PATH', 'framework/cache/data'),
],
array è una cache in-memory valida solo per la richiesta corrente. null ignora ogni operazione. Entrambi utili nei test automatizzati.
CACHE_STORE=array

Gerarchia dei driver

Usa i driver in base allo scopo e alla velocità.

Operazioni di base

Ottenere la facade Cache

Usa la facade Cache per manipolare la cache.
<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Cache;

class UserController extends Controller
{
    public function index(): array
    {
        $value = Cache::get('key');

        return [
            // ...
        ];
    }
}
Per usare più store distinti usa store().
$value = Cache::store('file')->get('foo');

Cache::store('redis')->put('bar', 'baz', 600); // 10 minuti

Lettura: Cache::get()

get() legge un dato dalla cache. Se manca restituisce null. Puoi indicare un valore predefinito.
$value = Cache::get('key');

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

// Default lazy tramite closure
$value = Cache::get('key', function () {
    return DB::table('settings')->get();
});

Scrittura: Cache::put()

put() salva un dato. Il terzo argomento è la validità in secondi.
// Salva per 10 secondi
Cache::put('key', 'value', 10);

// Validità tramite istanza Carbon
Cache::put('key', 'value', now()->plus(minutes: 10));

// Senza scadenza (persistente)
Cache::put('key', 'value');
Con add() salvi solo se la chiave non esiste (operazione atomica).
Cache::add('key', 'value', $seconds);
Per salvare in modo permanente usa forever().
Cache::forever('key', 'value');

Leggi o salva: Cache::remember()

L’operazione più usata. Se la chiave è in cache, restituisce quel valore; altrimenti esegue la closure e salva il risultato.
$users = Cache::remember('users', 3600, function () {
    return DB::table('users')->get();
});
Con Cache::remember() scrivi i tre passaggi “controlla cache → altrimenti calcola → salva in cache” in una sola riga. Ideale per query DB e risposte API.

Flusso di remember()

Esiste anche rememberForever() senza scadenza.
$value = Cache::rememberForever('users', function () {
    return DB::table('users')->get();
});
Se vuoi sapere se il valore proviene dalla cache o è stato appena calcolato, usa rememberWithWarmth(). Restituisce un array con il valore e un booleano che indica se era “caldo” (cache hit).
[$value, $warm] = Cache::rememberWithWarmth('users', 3600, function () {
    return DB::table('users')->get();
});

if ($warm) {
    // Valore dalla cache
} else {
    // Appena calcolato dalla closure
}
rememberWithWarmth() è utile per monitorare l’efficienza della cache. Se $warm è spesso false, valuta di aumentare il TTL.

Stale While Revalidate (aggiornamento flessibile della cache)

Con Cache::flexible() indichi in un array il periodo “fresco” e il periodo “utilizzabile anche se vecchio”. Restituisce i dati vecchi all’utente aggiornando la cache in background.
// [periodo fresco (s), periodo utilizzabile anche se vecchio (s)]
$value = Cache::flexible('users', [5, 10], function () {
    return DB::table('users')->get();
});

Verifica dell’esistenza: Cache::has()

if (Cache::has('key')) {
    // La cache esiste
}

Increment / decrement

Manipola valori interi.
// Inizializza se non esiste
Cache::add('key', 0, now()->addHours(4));

// Increment / decrement
Cache::increment('key');
Cache::increment('key', $amount);
Cache::decrement('key');
Cache::decrement('key', $amount);

Leggi ed elimina: Cache::pull()

Recupera il valore e lo elimina. Utile per dati usa-e-getta.
$value = Cache::pull('key');

Eliminazione: Cache::forget()

// Elimina una chiave
Cache::forget('key');

// Svuota tutta la cache
Cache::flush();
Cache::flush() elimina tutte le voci indipendentemente dal “prefisso” impostato. Attenzione se la cache è condivisa tra più applicazioni.
Con Cache::flushLocks() elimini tutti i lock atomici.
Cache::flushLocks();

Estensione TTL: Cache::touch()

Estende la scadenza di una voce esistente.
// Indica i secondi
Cache::touch('key', 3600);

// Oppure un'istanza Carbon
Cache::touch('key', now()->addHours(2));

Memoization della cache

Il driver memo mantiene in memoria l’accesso alla cache durante la stessa richiesta. Utile quando accedi ripetutamente alla stessa chiave per ridurre i round trip verso lo store.
use Illuminate\Support\Facades\Cache;

// Memo dello store predefinito
$value = Cache::memo()->get('key');

// Memo dello store Redis
$value = Cache::memo('redis')->get('key');
// Primo accesso: raggiunge lo store
$value = Cache::memo()->get('key');

// Accessi successivi: restituiti dalla memoria (nessun accesso allo store)
$value = Cache::memo()->get('key');

Tag della cache

Raggruppa voci correlate con dei tag ed eliminale in blocco.
I tag di cache non sono disponibili con i driver file, dynamodb, database e storage. Servono redis o memcached.

Struttura della cache con tag

Le voci taggate con più tag possono essere eliminate tramite uno qualunque dei tag.

Salvataggio e lettura

use Illuminate\Support\Facades\Cache;

// Salva con tag
Cache::tags(['people', 'artists'])->put('John', $john, $seconds);
Cache::tags(['people', 'authors'])->put('Anne', $anne, $seconds);

// Leggi con tag
$john = Cache::tags(['people', 'artists'])->get('John');
$anne = Cache::tags(['people', 'authors'])->get('Anne');

Cancellazione

// Elimina tutte le voci con tag 'people' e 'authors' (elimina sia John sia Anne)
Cache::tags(['people', 'authors'])->flush();

// Elimina solo con tag 'authors' (elimina solo Anne; John resta)
Cache::tags('authors')->flush();
I tag sono utili per invalidare gruppi (per utente, per articolo, ecc.). Es.: Cache::tags(['user', "user:{$userId}"])->flush() per svuotare la cache di uno specifico utente.

Operazioni atomiche (lock)

Con Cache::lock() implementi lock distribuiti per evitare race condition tra processi o richieste concorrenti.
Disponibile con i driver memcached, redis, dynamodb, database, file e array. Tutti i server devono comunicare con lo stesso cache server centrale.

Lock di base

use Illuminate\Support\Facades\Cache;

$lock = Cache::lock('foo', 10);

if ($lock->get()) {
    // Lock ottenuto (valido 10 secondi)

    // Esegui l'operazione esclusiva

    $lock->release();
}
Passando una closure il lock viene rilasciato automaticamente al termine.
Cache::lock('foo', 10)->get(function () {
    // Il lock viene rilasciato automaticamente
});

Attesa del lock

Attende al massimo i secondi indicati per ottenere il lock. Al timeout viene lanciata LockTimeoutException.
use Illuminate\Contracts\Cache\LockTimeoutException;

$lock = Cache::lock('foo', 10);

try {
    $lock->block(5); // Attesa max 5 secondi

    // Elaborazione dopo aver ottenuto il lock

} catch (LockTimeoutException $e) {
    // Lock non ottenuto
} finally {
    $lock->release();
}
Con closure è più conciso.
Cache::lock('foo', 10)->block(5, function () {
    // Attende max 5s, esegue e rilascia
});

Prevenzione dell’esecuzione sovrapposta: withoutOverlapping()

Modo semplice per evitare esecuzioni multiple della stessa logica.
Cache::withoutOverlapping('foo', function () {
    // Solo una alla volta
});

// Personalizza i tempi
Cache::withoutOverlapping('foo', function () {
    // ...
}, lockFor: 120, waitFor: 5);

Limitazione della concorrenza: funnel()

Limita quanti processi possono girare in parallelo.
Cache::funnel('foo')
    ->limit(3)          // Massimo 3 in parallelo
    ->releaseAfter(60)  // Rilascio automatico dopo 60s
    ->block(10)         // Attende max 10s
    ->then(function () {
        // Slot ottenuto
    }, function () {
        // Slot non ottenuto
    });

Passaggio del lock tra processi

Ottieni il lock in una richiesta web e lo rilasci in un job in coda.
// Nella richiesta: ottieni il lock e dispatcha il job
$lock = Cache::lock('processing', 120);

if ($lock->get()) {
    ProcessPodcast::dispatch($podcast, $lock->owner());
}

// Nel job: rilascia il lock
Cache::restoreLock('processing', $this->owner)->release();
Per forzare il rilascio indipendentemente dall’owner corrente usa forceRelease.
Cache::lock('processing')->forceRelease();

Rinnovo del lock

Per estendere la scadenza del lock in tuo possesso usa refresh. Senza argomenti usa la durata originale. Utile per elaborazioni lunghe con lock brevi che vuoi rinnovare periodicamente.
$lock = Cache::lock('generate-reports', 60);

if ($lock->get()) {
    foreach ($reports as $report) {
        $report->generate();

        // Rinnova per altri 60 secondi
        $lock->refresh();
    }

    $lock->release();
}

Helper cache

L’helper cache() fa le stesse cose della facade in modo più conciso.
// Leggi
$value = cache('key');

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

// Senza argomenti ottieni la stessa istanza della facade
cache()->remember('users', $seconds, function () {
    return DB::table('users')->get();
});

Esempi pratici

Cache dei risultati di query

1

Cache dei risultati nel controller

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

Invalidare la cache all'aggiornamento

public function store(Request $request): RedirectResponse
{
    User::create($request->validated());

    // Elimina la cache: verrà ricalcolata alla prossima lettura
    Cache::forget('all-users');

    return redirect()->route('users.index');
}

Cache di modelli Eloquent

use App\Models\Product;
use Illuminate\Support\Facades\Cache;

// Cache dell'elenco per categoria per 1 ora
public 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');
}

Cache di risposte API

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;

public function getWeather(string $city): array
{
    return Cache::remember(
        "weather:{$city}",
        1800, // 30 minuti
        function () use ($city) {
            $response = Http::get('https://api.weather.example.com/current', [
                'city' => $city,
            ]);

            return $response->json();
        }
    );
}

Gestione a gruppi con i tag

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());

        // Svuota tutta la cache legata all'articolo
        Cache::tags(["article:{$article->id}"])->flush();

        return redirect()->route('articles.show', $article);
    }
}
MetodoDescrizione
Cache::get('key')Recupera la cache
Cache::put('key', $value, $ttl)Salva in cache
Cache::remember('key', $ttl, fn)Leggi o salva (il più importante)
Cache::forget('key')Elimina una voce
Cache::has('key')Verifica l’esistenza
Cache::flush()Svuota tutto
Cache::forever('key', $value)Salva senza scadenza
Cache::pull('key')Recupera ed elimina
Cache::increment('key')Incrementa il valore
Cache::tags([...])->flush()Svuota le voci con tag
  • Sviluppo/piccole app: file o database
  • Produzione/alto traffico: redis (con Laravel Horizon anche il monitoraggio è facile)
  • Ambienti AWS: dynamodb o storage (per riutilizzare S3)
  • Test: array o null
  • Naming univoco delle chiavi in tutta l’app (es. users:1:profile)
  • Invalida la cache con Cache::forget() o Cache::tags()->flush() quando i dati cambiano
  • Adatta il TTL alla frequenza di aggiornamento (né troppo breve né troppo lungo)
  • Con Cache::remember() gestisci i miss in modo conciso
  • In produzione usa Redis e valuta il failover
Ultima modifica il 13 luglio 2026