afterQuery() verwenden. Neben zusätzlichen SQL-Bedingungen können Sie damit auch eine Closure im Query Builder registrieren, die auf die abgerufenen Ergebnisse angewendet wird.
Es handelt sich jedoch nicht um „einen Hook, der bei jeder SQL-Ausführung ein Ergebnis desselben Typs erhält“. Je nach Abrufmethode ändern sich der Inhalt der übergebenen Collection und die Anzahl der Aufrufe.
Diese Seite bezieht sich auf Laravel 13. Ausgehend von After Query Callbacks in der offiziellen Dokumentation prüft sie die Grenzen der Verwendung anhand des Eloquent Builders und der offiziellen Tests.
Ergebnisverarbeitung in einem lokalen Scope bündeln
Wenn Sie Modelle wiederholt über ihre ID ansprechen, können Sie einen Scope vorbereiten, der die Schlüssel der Ergebnisse vereinheitlicht. Das folgende Beispiel definieren Sie inapp/Models/Post.php.
$posts bleibt eine Eloquent Collection, und die Elemente bleiben Post-Modelle. get(42) gibt das Modell mit dem Schlüssel 42 zurück oder null, wenn keines existiert. Wenn Sie die abgerufenen Spalten mit select() einschränken, nehmen Sie auch die als Schlüssel verwendete id auf.
Wenn Sie die Ergebnisse nur an einer Stelle verarbeiten, ist get()->keyBy('id') einfacher. Wählen Sie afterQuery(), wenn Sie die Verarbeitung nach dem Abruf in einen Scope oder eine wiederverwendete Abfrage aufnehmen möchten. Wie Sie Scopes selbst definieren, lesen Sie unter Eloquent-Scopes.
Die Ausführungsreihenfolge von get verstehen
Eloquent\Builder::get() wendet globale Scopes an, erzeugt die Modelle, lädt die angegebenen Beziehungen per Eager Loading und führt zuletzt die afterQuery-Callbacks aus.
Bei einem normalen get() können Callbacks die per Eager Loading geladenen Beziehungen verwenden. Der Zugriff auf nicht angegebene Beziehungen kann zusätzliches SQL auslösen. Planen Sie den Scope daher zusammen mit Eager Loading.
Der Zeitpunkt unterscheidet sich vom Modell-Event retrieved. retrieved wird ausgelöst, wenn jedes Modell in Model::newFromBuilder() erzeugt wird, also vor dem Eager Loading in get(). afterQuery(), das die Menge der abgerufenen Modelle verarbeitet, und die Events einzelner Modelle erfüllen nicht dieselbe Aufgabe.
Rückgabewerte werden an den nächsten Callback übergeben
afterQuery() fügt Closures in der Reihenfolge ihrer Registrierung hinzu. Der ausführende Teil, applyAfterQueryCallbacks(), ist wie folgt implementiert.
??, sondern ?:, das auf der Truthy-Prüfung von PHP beruht.
Sie können auch einen
void-Callback verwenden, der die Collection direkt verändert und nichts zurückgibt. In diesem Fall bleibt dasselbe veränderte Objekt erhalten. Erzeugen Sie dagegen eine neue Collection, müssen Sie diese zurückgeben.
Intern sind Argument und Rückgabewert mixed, doch machen Sie beliebige Typumwandlungen nicht zur öffentlichen API. Ersetzen Sie etwa das Ergebnis von get() durch einen String oder ein Array, bricht der anschließende Aufruf ->first() in first(). Beschränken Sie sich in Scopes auf Verarbeitungen, die Modelle, den Collection-Typ, die Anzahl und die Reihenfolge beibehalten.
Unterschiede zwischen den Abrufmethoden
Die folgende Tabelle zeigt, wie ein im Eloquent Builder registriertesafterQuery() behandelt wird.
first() ruft intern limit(1)->get()->first() auf. Nur weil Sie first() verwenden, dürfen Sie den Parameter der Closure nicht auf Post ändern.
Wenn Sie nur Werte benötigen, verzichten Sie auf den Scope und schreiben Post::query()->pluck('id'). Um Werte nach der Verarbeitung der Modelle zu entnehmen, können Sie Post::keyedById()->get()->pluck('id') schreiben. Das pluck() im zweiten Fall ist eine Operation auf der abgerufenen Collection, nicht auf dem Query Builder.
Seitenanzahl und nächste Abrufposition nicht verändern
paginate() ermittelt die Gesamtzahl per SQL und ruft danach die Modelle der Seite ab. Filtern Sie die Collection nach dem Abruf, wird die Gesamtzahl nicht neu berechnet.
Außerdem entscheiden simplePaginate() und cursorPaginate() anhand des verarbeiteten Ergebnisses, ob es eine nächste Seite gibt. chunkById() verwendet die ID am Ende eines Chunks als nächste Abrufposition. Wenn Sie im Callback Anzahl oder Reihenfolge ändern, stimmen Seitenerkennung und Fortsetzungsposition möglicherweise nicht mehr mit der ursprünglichen Abfrage überein.
Geben Sie Filterung, Sortierung und abgerufene Spalten im SQL an, und beschränken Sie afterQuery() auf Verarbeitungen, die die Bedeutung der abgerufenen Ergebnisse nicht verändern. Wenn Sie nur das Ausgabeformat einer API ändern möchten, ziehen Sie auch eine Umwandlung mit API-Ressourcen in Betracht.
Die offizielle Dokumentation enthält ein Beispiel, das Modellen ein Attribut mit einem Auszug aus Suchergebnissen hinzufügt. Zuweisungen an Modellattribute können auch die zu speichernden Daten verändern. Vermeiden Sie Designs, bei denen Modelle mit reinen Anzeigeattributen, die nicht in der Datenbank existieren, direkt mit save() gespeichert werden.
Registrierungen bleiben im Query Builder erhalten
Jeder Aufruf vonafterQuery() fügt eine Closure hinzu, und die Ausführung entfernt die Registrierung nicht. Ein erneuter Abruf mit demselben Builder führt sie erneut aus, und wenn Sie denselben Scope zweimal aufrufen, wird auch der Callback zweimal registriert. Auch beim Klonen des Builders werden die registrierten Callbacks übernommen.
In Code mit mehreren Abrufen oder in langlebigen Workern erstellen Sie für jeden Zweck eine neue Abfrage und beschränken Callbacks auf eine Ergebnisverarbeitung ohne Seiteneffekte. Wenn Sie dort E-Mail-Versand oder Persistierung unterbringen, verwechselt man mehrfache Ausführungen durch Chunks oder Cursor leicht mit einem einzigen fachlichen Vorgang.
Wenn Sie wiederverwendbare Scopes in einem Paket bereitstellen, legen Sie die unterstützten Abrufmethoden ebenfalls als öffentliche API fest und testen Sie den Vertrag zu Typen und Aufrufanzahl.
Unterschiede zwischen den Abrufwegen testen
Prüfen Sie in der nutzenden Laravel-13-Anwendung nicht nur die Schlüsselverarbeitung, sondern auch die Aufrufe beifirst(), cursor() und leeren Seiten.
Das folgende Beispiel setzt das vorherige Post-Modell und eine Migration für eine posts-Tabelle mit id, title, body, created_at und updated_at voraus. Mit RefreshDatabase werden die Datensätze zwischen den Tests isoliert.
Erstellen Sie den Test in tests/Feature/AfterQueryTest.php.
Checkliste für die Wartung
- Legen Sie die vom Scope unterstützten Abrufmethoden sowie Parameter- und Rückgabetypen der Closure fest.
- Prüfen Sie ein leeres Ergebnis von
get()und einpaginate()mit Gesamtzahl 0 getrennt. - Stellen Sie bei Chunks und Cursorn sicher, dass sich Aufrufanzahl und Abrufreihenfolge nicht ändern.
- Gestalten Sie den Code so, dass doppelt registrierte Callbacks oder wiederverwendete Builder keine fachlichen Seiteneffekte auslösen.
- Prüfen Sie bei Laravel-Updates die Aufrufwege von
get(),pluck(),cursor()undBuildsQuerieserneut.
Verwandte Seiten
Eloquent-Scopes
Die Grundlagen, um SQL-Einschränkungen in lokalen und globalen Scopes zu bündeln.
Eloquent Collections
Der Unterschied zwischen Collections mit Modellen und Support Collections.
Eloquent Observer und Modell-Events
Events pro Modell, die sich von der Verarbeitung abgerufener Ergebnisse unterscheiden.
API-Ressourcen
Die Umwandlung der von der API zurückgegebenen Anzeigedaten vom Speichern der Modelle trennen.