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.
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 functieonce() in Illuminate\Support\helpers.php is bijzonder eenvoudig.
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 klasseOnceable heeft als taak om vanuit de backtrace een hash te berekenen die “welk aanroeppunt” uniek identificeert.
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.
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 vanIlluminate\Support\Once.
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
DoorOnce::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”.
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 isonce() 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
PreventsCircularRecursionis 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
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.