Skip to main content

Was ist once()?

once() ist ein globaler Helper, der einen Callback ausführt und dessen Ergebnis für die Dauer des Requests im Speicher cacht. Wird derselbe Callback erneut von derselben Aufrufstelle heraus aufgerufen, gibt der Helper das zwischengespeicherte Ergebnis zurück.
Rufen Sie den Helper aus einer Instanzmethode eines Objekts auf, ist der Cache auf diese Instanz beschränkt.
Der Mechanismus, dass der Cache „pro Aufrufstelle und pro Instanz” isoliert wird, unterscheidet sich von einfachen Memoization-Helpern wie memoize. Um ihn zu verstehen, muss man sich die beiden Klassen Illuminate\Support\Once und Illuminate\Support\Onceable anschauen.

Der Aufruf in helpers.php

Der Rumpf der Funktion once() in Illuminate\Support\helpers.php ist denkbar einfach.
Entscheidend ist: once() selbst hält keinen Cache. Es baut aus den Informationen zum Aufrufer, die debug_backtrace() liefert, eine Onceable-Instanz zusammen und delegiert die eigentliche Verarbeitung an die Klasse Once.

Onceable — Hash-Berechnung zur Identifikation der Aufrufstelle

Die Klasse Onceable ist dafür zuständig, aus dem Backtrace einen Hash zu berechnen, der eindeutig festlegt, „von welcher Aufrufstelle” der Aufruf kommt.
Grundlage des Hashes sind Dateipfad + Klassenname + Funktionsname + Zeilennummer + Werte der von der Closure per use eingefangenen Variablen. Anders gesagt: Selbst wenn once() aus derselben Zeile aufgerufen wird, gilt es als anderer Cache, sobald sich der Wert einer per use eingefangenen Variablen ändert.
Wird once() aus per eval() ausgeführtem Code aufgerufen, liefert hashFromTrace() null zurück und es wird gar nicht gecacht. Das ist eine Sicherheitsmaßnahme für Ausführungen über eval, etwa in kompilierten Blade-Views.
object gibt an, ob der Aufrufer eine Instanzmethode ist. $trace[1]['object'] bezieht sich auf eine Ebene über once() (also die Seite, die once() aufgerufen hat). Bei einer Instanzmethode landet dort $this; bei einer statischen Methode oder einer globalen Funktion ist der Wert null.

Once — der eigentliche Cache über WeakMap

Die tatsächliche Haltung des Caches übernimmt Illuminate\Support\Once.
Die entscheidende Technik ist WeakMap. Als Schlüssel dient $onceable->object (die Aufrufer-Instanz); der Wert ist ein assoziatives Array von Hash → Ergebnis. Fehlt object (bei globalen Funktionen oder statischen Methoden), dient $this von Once selbst als Schlüssel – das bedeutet praktisch einen einzigen, prozessweit geteilten Cache-Bereich.
Da WeakMap verwendet wird, wird auch der an das Objekt gebundene Cache Garbage-Collection-fähig, sobald es keine weiteren Referenzen mehr auf das Schlüssel-Objekt gibt. Selbst wenn Sie once() aus vielen Objekten heraus aufrufen, ist das Design robust gegen Memory-Leaks.

Einsatz in Tests — enable / disable / flush

Ein Aufruf von Once::disable() deaktiviert den Cache vollständig; once() führt den Callback dann jedes Mal aus. Praktisch für Tests, in denen Sie „jedes Mal einen neuen Wert” möchten.
Once::flush() verwirft den gesamten Cache und legt beim nächsten Zugriff einen neuen WeakMap an. Sinnvoll bei Tests für Artisan-Befehle oder in Umgebungen, in denen Prozesse wiederverwendet werden (z. B. Octane), wenn Sie den Cache nicht zwischen Requests behalten möchten.

PreventsCircularRecursion — ein Anwendungsbeispiel von Onceable

Onceable::tryFromTrace() wird nicht nur von once() genutzt. Auch das Eloquent-Trait Illuminate\Database\Eloquent\Concerns\PreventsCircularRecursion verwendet es wieder. Dieses Trait dient dazu, „denselben Aufruf desselben Objekts innerhalb des Call-Stacks nicht erneut zuzulassen”.
Der entscheidende Unterschied zu Once liegt darin, dass der Cache im finally-Block nach Abschluss des Aufrufs wieder gelöscht wird. Während Once „während des Requests dauerhaft cacht”, nutzt PreventsCircularRecursion denselben Onceable-Mechanismus, um „nur für die Dauer des aktuellen Call-Stacks zu cachen (praktisch als Lock)”. Ein typischer Einsatz bei Eloquent-Modellen ist die Vermeidung von Rekursionen, wenn toArray() oder ein Accessor sich selbst rekursiv referenziert.

Einsatz in der Paketentwicklung

Möchten Sie in einem eigenen Paket „denselben Methodenaufruf derselben Instanz während eines Requests nur einmal ausführen”, ist es am einfachsten, once() direkt zu verwenden. Direkt mit Onceable/Once zu arbeiten kommt seltener vor, doch in folgenden Situationen hilft das Verständnis der internen Implementierung:
  • Wenn Sie in Artisan-Befehlen oder Tests mit Once::disable() / Once::flush() das Cache-Verhalten steuern möchten
  • Wenn Sie in einem eigenen Trait einen Cache oder eine Rekursionssperre „pro Aufrufstelle” implementieren möchten (dasselbe Muster wie PreventsCircularRecursion)
  • Wenn das Ergebnis von once() von der Erwartung abweicht und Sie debuggen müssen: Ein Verständnis der Bestandteile, die in den Hash einfließen (Datei, Klasse, Funktion, Zeile, per use gefangene Variablen), erleichtert die Ursachensuche
Da once() auch die per use in der Closure eingefangenen Variablen mit in den Hash einbezieht, kann eine Closure, die in einer Schleife bei jedem Durchlauf andere Werte einfängt, ungewollt jedes Mal einen neuen Cache erhalten und damit faktisch gar nichts cachen. Wenn Sie once() innerhalb einer Schleife verwenden, prüfen Sie, ob der Cache-Schlüssel die von Ihnen gewünschte Granularität besitzt.

Verwandte Seiten

tap()-Helper und Tappable-Trait

Das Implementierungsmuster des tap()-Helpers, der einen Seiteneffekt einschleift und dennoch den Wert zurückgibt.

Eloquent-Observer und Model-Events

Zentrale Verwaltung über Events und Observer von Eloquent-Modellen.
Zuletzt geändert am 11. September 2026