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.
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 funzioneonce() in Illuminate\Support\helpers.php è molto semplice.
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 classeOnceable ha il compito di calcolare, a partire dalla backtrace, un hash che identifica univocamente “da quale punto è stata effettuata la chiamata”.
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.
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 diIlluminate\Support\Once.
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
ChiamandoOnce::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”.
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 direttamenteonce(). 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
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.