afterQuery(). Oltre ad aggiungere condizioni SQL, permette di registrare nel query builder una Closure eseguita sui risultati recuperati.
Tuttavia, non è “un hook che riceve un risultato dello stesso tipo a ogni esecuzione SQL”. Il contenuto della Collection passata e il numero di chiamate cambiano a seconda del metodo di recupero.
Questa pagina riguarda Laravel 13. Partendo da After Query Callbacks nella documentazione ufficiale, verifica i limiti di utilizzo a partire dal builder di Eloquent e dai test ufficiali.
Raggruppare l’elaborazione dei risultati in uno scope locale
Se accedi ripetutamente ai modelli tramite ID, puoi preparare uno scope che uniforma le chiavi dei risultati. Definisci l’esempio seguente inapp/Models/Post.php.
$posts resta una Eloquent Collection e i suoi elementi restano Post. get(42) restituisce il modello con chiave 42, oppure null se non esiste. Se limiti le colonne recuperate con select(), includi anche l’id usato come chiave.
Se elabori i risultati in un solo punto, get()->keyBy('id') è più semplice. Scegli afterQuery() quando vuoi includere l’elaborazione successiva al recupero in uno scope o in una query riutilizzata. Per la definizione degli scope, consulta Scope di Eloquent.
Leggere l’ordine di esecuzione di get
Eloquent\Builder::get() applica gli scope globali, crea i modelli, esegue l’eager loading delle relazioni indicate e infine esegue le callback di afterQuery.
Con un normale get(), le callback possono usare le relazioni caricate con eager loading. L’accesso a relazioni non indicate può generare SQL aggiuntivo, quindi progetta lo scope insieme all’eager loading.
Il momento è diverso da quello dell’evento di modello retrieved. retrieved viene emesso alla creazione di ogni modello in Model::newFromBuilder(), prima dell’eager loading di get(). afterQuery(), che gestisce l’insieme dei modelli recuperati, e gli eventi di ciascun modello non hanno lo stesso ruolo.
Il valore di ritorno viene passato alla callback successiva
afterQuery() aggiunge le Closure in ordine di registrazione. La parte che le esegue, applyAfterQueryCallbacks(), è implementata così.
??, ma ?:, che si basa sulla valutazione truthy di PHP.
Puoi anche usare una callback
void che modifica direttamente la Collection senza restituire nulla. In questo caso viene mantenuto lo stesso oggetto modificato. Se invece crei una nuova Collection, devi restituirla.
Internamente argomento e valore di ritorno sono mixed, ma evita di rendere parte dell’API pubblica conversioni di tipo arbitrarie. Ad esempio, sostituire il risultato di get() con una stringa o un array rompe la chiamata ->first() che first() esegue subito dopo. Negli scope, limita l’elaborazione a modifiche che conservano i modelli, il tipo di Collection, il numero di elementi e l’ordine.
Differenze tra i metodi di recupero
La tabella seguente mostra come viene gestito unafterQuery() registrato sul builder di Eloquent.
first() chiama internamente limit(1)->get()->first(). Usare first() non significa che devi cambiare il parametro della Closure in Post.
Se ti servono solo valori, non usare lo scope e scrivi Post::query()->pluck('id'). Per estrarre valori dopo aver elaborato i modelli, puoi scrivere Post::keyedById()->get()->pluck('id'). Il pluck() del secondo caso è un’operazione sulla Collection recuperata, non sul query builder.
Non modificare il conteggio delle pagine né la posizione di recupero successiva
paginate() calcola il totale in SQL e poi recupera i modelli della pagina. Filtrare la Collection dopo il recupero non ricalcola il totale.
Inoltre, simplePaginate() e cursorPaginate() usano il risultato elaborato per decidere se esiste una pagina successiva. chunkById() usa l’ID alla fine di ogni chunk come posizione di recupero successiva. Se modifichi il numero di elementi o l’ordine nella callback, il rilevamento delle pagine o la posizione di continuazione potrebbero non corrispondere più alla query originale.
Specifica filtri, ordinamento e colonne nell’SQL e limita afterQuery() a elaborazioni che non cambiano il significato dei risultati recuperati. Se vuoi solo cambiare il formato di output di un’API, valuta anche la trasformazione con le API resource.
La documentazione ufficiale contiene un esempio che aggiunge ai modelli un attributo con un estratto dei risultati di ricerca. Assegnare attributi di modello può anche modificare ciò che viene salvato. Evita progetti che chiamano direttamente save() su modelli con attributi di visualizzazione inesistenti nel database.
La registrazione resta nel query builder
Ogni chiamata aafterQuery() aggiunge una Closure, e l’esecuzione non rimuove la registrazione. Un nuovo recupero con lo stesso builder la riesegue, e chiamare due volte lo stesso scope registra due volte anche la callback. Anche clonando il builder le callback registrate vengono ereditate.
Nel codice che esegue più recuperi o nei worker di lunga durata, crea una nuova query per ogni scopo e limita le callback a un’elaborazione dei risultati senza effetti collaterali. Se ci metti l’invio di email o la persistenza, è facile scambiare più esecuzioni dovute a chunk o cursori per una singola operazione di business.
Se fornisci scope riutilizzabili in un pacchetto, indica anche i metodi di recupero supportati come API pubblica e testa il contratto su tipi e numero di chiamate.
Testare le differenze tra i percorsi di recupero
Nell’applicazione Laravel 13 che lo utilizza, verifica non solo l’elaborazione delle chiavi, ma anche le chiamate confirst(), cursor() e pagine vuote.
L’esempio seguente presuppone il modello Post precedente e una migration per la tabella posts con id, title, body, created_at e updated_at. RefreshDatabase isola i record tra i test.
Crea il test in tests/Feature/AfterQueryTest.php.
Checklist per la manutenzione
- Indica i metodi di recupero supportati dallo scope e i tipi del parametro e del valore di ritorno della Closure.
- Verifica separatamente il risultato vuoto di
get()e unpaginate()con totale 0. - Con chunk e cursori, conferma che il numero di chiamate e l’ordine di recupero non cambiano.
- Progetta il codice in modo che una doppia registrazione della callback o il riutilizzo del builder non causino effetti collaterali di business.
- Quando aggiorni Laravel, ricontrolla i percorsi di chiamata di
get(),pluck(),cursor()eBuildsQueries.
Pagine correlate
Scope di Eloquent
Ripassa le basi per raggruppare i vincoli SQL in scope locali e globali.
Collection di Eloquent
Ripassa la differenza tra le Collection di modelli e le Support Collection.
Observer di Eloquent ed eventi di modello
Ripassa gli eventi per modello, diversi dall’elaborazione dei risultati recuperati.
API resource
Separa la trasformazione dei dati restituiti dall’API dal salvataggio dei modelli.