Skip to main content

Cos’è once()

once() è un helper globale che esegue una callback e memorizza il risultato in memoria per la durata della richiesta. Se viene chiamato di nuovo dallo stesso punto con la stessa callback, restituisce il risultato memorizzato.
Se chiamato all’interno del metodo di un’istanza, la cache è indipendente per ciascuna istanza.
Questo meccanismo “cache indipendente per punto di chiamata e per istanza” differisce da un semplice helper di memoizzazione come memoize. Per capire il funzionamento occorre guardare le due classi Illuminate\Support\Once e Illuminate\Support\Onceable.

La chiamata in helpers.php

La funzione once() in Illuminate\Support\helpers.php è molto semplice.
Il punto chiave è che once() di per sé non contiene alcuna cache: assembla un’istanza di Onceable con le informazioni sul chiamante ottenute da debug_backtrace() e delega l’elaborazione alla classe Once.

Onceable — calcolo dell’hash che identifica il punto di chiamata

La classe Onceable ha il compito di calcolare, a partire dalla backtrace, un hash che identifica univocamente “da quale punto è stata effettuata la chiamata”.
Le informazioni da cui deriva l’hash sono percorso del file + nome della classe + nome della funzione + numero di riga + valori delle variabili use della closure. In altre parole, anche una once() chiamata dalla stessa riga viene considerata una cache diversa se cambiano i valori delle variabili in use.
Se chiami once() da codice eseguito con eval(), hashFromTrace() restituisce null e non viene effettuata alcuna cache. Si tratta di una misura di sicurezza pensata per l’esecuzione tramite eval, come nelle viste Blade compilate.
object indica “se il chiamante è un metodo di istanza”. $trace[1]['object'] è l’oggetto un livello sopra nella debug_backtrace() (cioè chi ha chiamato once()), quindi contiene $this all’interno di un metodo di istanza e null in un metodo statico o in una funzione globale.

Once — il vero contenitore della cache basato su WeakMap

La conservazione effettiva della cache è a carico di Illuminate\Support\Once.
La tecnologia chiave è WeakMap. Usa $onceable->object (l’istanza chiamante) come chiave e conserva come valore un array associativo hash → risultato. Quando object è assente (funzione globale o metodo statico), la chiave diventa $this di Once stesso, che di fatto rappresenta un’unica area di cache condivisa in tutto il processo.
Poiché viene usata una WeakMap, quando non esistono più altri riferimenti all’oggetto usato come chiave, anche la cache associata a quell’oggetto diventa candidata alla garbage collection. È un design pensato per evitare memory leak anche quando once() viene chiamato da un gran numero di oggetti.

Uso nei test — enable / disable / flush

Chiamando Once::disable() la cache viene completamente disabilitata e once() esegue la callback ogni volta. È utile nei test quando “vuoi un nuovo valore a ogni chiamata”.
Once::flush() scarta l’intera cache e ricrea una nuova WeakMap al prossimo accesso. È utile nei test dei comandi Artisan o in ambienti come Octane in cui i processi vengono riutilizzati e non vuoi che la cache sopravviva alle richieste.

PreventsCircularRecursion — un’applicazione di Onceable

Onceable::tryFromTrace() non è esclusiva di once(): viene riutilizzata anche nel trait Illuminate\Database\Eloquent\Concerns\PreventsCircularRecursion di Eloquent. Questo trait serve a “evitare che lo stesso punto di chiamata sullo stesso oggetto rientri nella stessa call stack”.
La differenza decisiva rispetto a Once è che nel blocco finally la cache viene ripulita al termine della chiamata. Mentre Once “mette in cache in modo persistente per tutta la durata della richiesta”, PreventsCircularRecursion riutilizza lo stesso meccanismo Onceable per “mettere in cache (in pratica bloccare) solo per la durata della call stack in esecuzione”. Un esempio tipico di uso in un modello Eloquent è la prevenzione dei casi in cui il metodo toArray() o un accessor del modello finisce per riferirsi ricorsivamente a se stesso.

Applicazione nello sviluppo di pacchetti

Nel tuo pacchetto, quando vuoi “eseguire una sola volta per richiesta la stessa chiamata di metodo sulla stessa istanza”, il modo più semplice è usare direttamente once(). Raramente userai Onceable/Once in modo diretto, ma comprendere l’implementazione interna è utile nei seguenti casi:
  • Quando vuoi controllare il comportamento della cache nei comandi Artisan o nei test con Once::disable() / Once::flush()
  • Quando vuoi implementare in un tuo trait una cache “per punto di chiamata” o una prevenzione del rientro (puoi usare lo stesso schema di PreventsCircularRecursion)
  • Quando il risultato di once() non è quello atteso e devi fare debug: capire da quali elementi deriva l’hash (file, classe, funzione, riga, variabili use) rende più semplice individuare la causa
Poiché once() include nell’hash anche le variabili in use della closure, se dentro un ciclo passi una closure che cattura valori diversi a ogni iterazione, ti ritroverai involontariamente con una cache diversa ogni volta e di fatto non avrai alcuna cache. Quando lo usi in un ciclo, verifica che la granularità della chiave di cache sia quella voluta.

Pagine correlate

Helper tap() e trait Tappable

Pattern di implementazione dell’helper tap() che intercala side effect restituendo il valore.

Eloquent Observers ed eventi del modello

Gestione centralizzata tramite eventi e observer dei modelli Eloquent.
Ultima modifica il 11 settembre 2026