What is once()?
once() is a global helper that executes a callback and caches the result in memory for the duration of the request. When the same call site invokes the same callback again, once() returns the cached value.
This “per call site, per instance” caching behavior is very different from a simple memoization helper. To understand how it works, you need to look at the two classes
Illuminate\Support\Once and Illuminate\Support\Onceable.The call in helpers.php
Theonce() function body in Illuminate\Support\helpers.php is quite small.
once() itself holds no cache: it builds an Onceable from the call site information returned by debug_backtrace() and delegates the actual work to the Once class.
Onceable — computing a hash that identifies the call site
TheOnceable class is responsible for computing a hash from the backtrace that uniquely identifies “which call site” is invoking once().
file path + class name + function name + line number + values captured by the closure's use. In other words, even two once() calls on the same line become separate cache entries when the values the closure uses differ.
object indicates whether the caller was an instance method. $trace[1]['object'] is the object one level above once() in the backtrace (the caller), so it will be $this inside an instance method and null inside a static method or a global function.
Once — the WeakMap-backed cache
The actual cache is held byIlluminate\Support\Once.
WeakMap. $onceable->object (the calling instance) is used as the map key, with a hash => result array as the value. If no object is available (global function or static method), the Once instance itself ($this) becomes the key, which effectively creates a single cache region shared across the entire process.
Because a
WeakMap is used, once no other references to the key object remain, the associated cache entries become eligible for garbage collection. This makes once() safe to call from a large number of objects without leaking memory.Using it in tests — enable / disable / flush
CallingOnce::disable() turns caching off entirely, so once() invokes the callback every time. This is handy in tests when you want a fresh value on each call.
Once::flush() discards the entire cache and creates a new WeakMap on the next access. Use it in Artisan command tests, or in long-lived environments like Octane where processes are reused and you don’t want the cache to leak across requests.
PreventsCircularRecursion — Onceable in action
Onceable::tryFromTrace() isn’t exclusive to once(). Eloquent’s Illuminate\Database\Eloquent\Concerns\PreventsCircularRecursion trait reuses it to prevent “the same call site on the same object” from re-entering itself within a single call stack.
Once is the finally block, which clears the cache once the call completes. Once caches persistently for the duration of a request, while PreventsCircularRecursion uses the same Onceable infrastructure to cache (in effect, lock) only for the duration of the currently executing call stack.
A typical use in an Eloquent model is preventing accessors or toArray() from recursively referencing themselves.
Applying it in package development
If you want “run this once per request per instance for the same method call” in your own package, usingonce() directly is by far the easiest option. You rarely need to touch Onceable/Once directly, but understanding the internals pays off in these situations:
- When you want to control caching behavior from Artisan commands or tests via
Once::disable()/Once::flush() - When you want to build your own trait that caches or prevents re-entry per call site (you can copy the
PreventsCircularRecursionpattern) - When
once()returns something unexpected and you need to debug why — knowing the hash inputs (file, class, function, line, and captured variables) makes it much easier to spot the cause
Related pages
tap() Helper and the Tappable Trait
Implementation patterns for the tap() helper, which lets you insert side effects while still returning the original value.
Eloquent Observers and Model Events
Centralize Eloquent model events with Observer classes.