Skip to main content

Wat is once()?

once() is een globale helper die een callback uitvoert en het resultaat gedurende het request in het geheugen cachet. Wordt hij vanaf hetzelfde aanroeppunt opnieuw aangeroepen met dezelfde callback, dan wordt de gecachte waarde teruggegeven.
Wordt hij aangeroepen vanuit een methode op een object, dan is de cache per instantie onafhankelijk.
Het feit dat de cache per aanroeppunt én per instantie onafhankelijk is, onderscheidt once() van een eenvoudige memoization-helper zoals memoize. Om het mechanisme te begrijpen, moet je naar de twee klassen Illuminate\Support\Once en Illuminate\Support\Onceable kijken.

De aanroep in helpers.php

De implementatie van de functie once() in Illuminate\Support\helpers.php is bijzonder eenvoudig.
Wat opvalt is dat once() zelf geen cache bijhoudt: het bouwt met behulp van de aanroepinformatie uit debug_backtrace() een Onceable-instantie en delegeert de verwerking aan de klasse Once.

Onceable — een hash berekenen die het aanroeppunt identificeert

De klasse Onceable heeft als taak om vanuit de backtrace een hash te berekenen die “welk aanroeppunt” uniek identificeert.
De informatie waaruit de hash wordt opgebouwd is: bestandspad + klassenaam + functienaam + regelnummer + waarden van de variabelen die de closure via use binnenhaalt. Met andere woorden: zelfs bij een once() op dezelfde regel wordt het als een andere cache behandeld als de waarden van de use-variabelen veranderen.
Als je once() aanroept vanuit code die via eval() is uitgevoerd, retourneert hashFromTrace() null en wordt er helemaal niets gecachet. Dit is een veiligheidsmaatregel voor uitvoering via eval, zoals bij gecompileerde Blade-views.
object geeft aan of de aanroeper een instantiemethode is. $trace[1]['object'] is het object één niveau boven once() (de aanroeper), dus binnen een instantiemethode bevat het $this en binnen een statische methode of globale functie is het null.

Once — het eigenlijke cachemechanisme met WeakMap

Het daadwerkelijke bijhouden van de cache is de verantwoordelijkheid van Illuminate\Support\Once.
De kerntechniek is WeakMap. $onceable->object (de aanroepende instantie) wordt gebruikt als sleutel, en als waarde wordt een associatief array van hash → resultaat opgeslagen. Ontbreekt object (bij globale functies of statische methoden), dan wordt $this van Once zelf de sleutel, waardoor er in feite één gedeelde cache voor het hele proces ontstaat.
Doordat er een WeakMap gebruikt wordt, komt de cache die aan een object hangt in aanmerking voor garbage collection zodra er geen andere verwijzingen naar dat object bestaan. Zo blijft once() weinig gevoelig voor geheugenlekken, zelfs als hij vanuit heel veel objecten wordt aangeroepen.

Toepassing bij tests — enable / disable / flush

Door Once::disable() aan te roepen wordt de cache volledig uitgeschakeld en voert once() de callback iedere keer opnieuw uit. Dat is handig als je in tests “elke keer een nieuwe waarde” wilt.
Once::flush() gooit de volledige cache weg en maakt bij de volgende toegang een nieuwe WeakMap aan. Dit is nuttig bij het testen van Artisan-commando’s of in omgevingen zoals Octane, waar het proces wordt hergebruikt en je de cache niet tussen requests wilt behouden.

PreventsCircularRecursion — een toepassing van Onceable

Onceable::tryFromTrace() is niet exclusief voor once(); ook Eloquents trait Illuminate\Database\Eloquent\Concerns\PreventsCircularRecursion hergebruikt hem. Deze trait dient om “hetzelfde aanroeppunt op hetzelfde object niet opnieuw binnen dezelfde call stack te laten uitvoeren”.
Het beslissende verschil met Once is dat er in het finally-blok de cache wordt gewist nadat de aanroep is voltooid. Waar Once “gedurende het request permanent cachet”, gebruikt PreventsCircularRecursion hetzelfde Onceable-mechanisme om “alleen tijdens de huidige call stack te cachen (feitelijk een lock)”. Een typisch gebruik op een Eloquent-model is het voorkomen van gevallen waarin toArray() of een accessor van het model zichzelf recursief refereert.

Toepassing bij packageontwikkeling

Wil je in je eigen package “dezelfde methodeaanroep op dezelfde instantie tijdens één request maar één keer uitvoeren”, dan is once() rechtstreeks gebruiken het eenvoudigst. Je zult Onceable/Once zelden rechtstreeks nodig hebben, maar het begrip van de interne werking is nuttig in situaties als deze:
  • Wanneer je in Artisan-commando’s of tests met Once::disable() / Once::flush() het cachegedrag wilt sturen
  • Wanneer je in een eigen trait cache of herintredebeveiliging per aanroeppunt wilt implementeren (hetzelfde patroon als PreventsCircularRecursion is bruikbaar)
  • Wanneer het resultaat van once() niet is wat je verwacht en je moet debuggen; als je begrijpt uit welke elementen de hash bestaat (bestand, klasse, functie, regel, use-variabelen), kun je de oorzaak sneller opsporen
Omdat once() ook de variabelen die de closure via use binnenhaalt in de hash meeneemt, kan het gebeuren dat je in een lus een closure doorgeeft die telkens andere waarden vastlegt. Dan wordt onbedoeld iedere keer een aparte cache aangemaakt en wordt er feitelijk niets gecachet. Controleer bij gebruik binnen een lus of je de cachesleutel op het bedoelde detailniveau hebt.

Gerelateerde pagina’s

De tap()-helper en de Tappable-trait

Implementatiepatronen van de tap()-helper waarmee je bijeffecten tussenvoegt en de waarde teruggeeft.

Eloquent Observers en model events

Gecentraliseerd beheer van Eloquent-model events en observers.
Laatst gewijzigd op 11 september 2026