spatie/once: request-scoped memoization for PHP call sites
A magic memoization function
At a glance
- What is it?
- spatie/once caches a callable's result by calling function and argument values through a PHP 8 WeakMap. It suits small PHP classes that repeat expensive or nondeterministic work, but tests and long-running Octane workers need explicit cache controls.
- Who is it for?
- Adopt spatie/once when repeated work can be isolated to a specific method call within one PHP request, especially when the result should vary with that method's argument values. Do not adopt it for values that must be recomputed on every invocation or as an undocumented process-wide cache.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 6, 2026, and from our analysis. They are not legal advice.
Star history
- spatie/once1,443 ★ · -1
| Repository | 2026-10-21 | 2026-11-06 |
|---|---|---|
| spatie/once | 1.4k | 1.4k |
-1 Stars in the last 16 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
Why spatie/once exists in a PHP request
spatie/once addresses repeated execution within one request. The README's example wraps a call to rand(1, 10000) in once(), then calls the containing method repeatedly. The documentation states that every call within the same request receives the same number. This is useful when a method is invoked several times but its result only needs to be calculated once, such as a value derived from a slow operation or a nondeterministic source.
The package is aimed at PHP developers who can identify a stable call site and want memoization without introducing a general cache layer. Its key behavior is more specific than a global name-value cache. According to the README, once() runs once per combination of the containing function and the argument values received by that function. Calling a static method with one letter can therefore produce one cached result while a different letter produces another result.
This makes the package more suitable for request-oriented classes than for durable storage. It does not document persistence across processes, configurable expiration, serialization, or cross-request reuse. The repository description calls it a "magic memoization function," and that wording matches the narrow scope shown in the implementation notes: a callable is executed, its result is retained, and later matching calls return that retained value.
How spatie/once identifies each cached call
The package derives identity from the calling context instead of requiring callers to provide an explicit cache key. On the first operation, once() calls debug_backtrace() with DEBUG_BACKTRACE_PROVIDE_OBJECT and a limit of 2. The documentation then extracts the second trace entry and passes it to a Backtrace wrapper. That wrapper provides access to the object associated with the call and the information needed to calculate a hash.
The hash represents both the function where once() was called and the values of arguments supplied to that function. Once() asks the singleton Spatie\Once\Cache whether the current object already has a value for that hash. If the entry is absent, it executes the supplied callback and stores the returned value. If the entry exists, it returns that value without invoking the callback again.
The stored values live in the Cache singleton's $values WeakMap. The README's has() example first checks whether the WeakMap contains the current object, then checks the object's entries with array_key_exists(). This design ties cached data to the lifetime of PHP objects rather than presenting a persistent external cache. It also explains why automatic key construction is convenient inside a class: the package derives the context from the call stack. The cost of that convenience is that the identity rules are implicit, so developers should inspect them when moving memoized calls between methods or changing argument types.
Installing spatie/once and verifying first use
The README gives a Composer installation command. Run it in the PHP project that needs the helper, then load Composer's autoloader according to that project's existing setup. The README does not publish a standalone version constraint for this command.
composer require spatie/onceA first real use can follow the README's anonymous-class example. Each call to getNumber() asks once() for the same memoized result, while the callback containing rand(1, 10000) is eligible to run only when the matching call has not already been cached.
$myClass = new class() {
public function getNumber(): int
{
return once(function () {
return rand(1, 10000);
});
}
};Call the method more than once and compare the returned integers. The documented expectation is that they are equal within the same request. The repository also documents `composer test` as its test command, although the README does not describe individual test cases or expected console output.
composer testArgument-aware memoization in instance and static methods
The cache is not limited to callbacks with no inputs. The README's static example closes over a $letter argument and includes it in the result. Repeated calls with 'A' reuse the result for that argument combination, while a call with 'B' follows a separate hash path.
class MyClass
{
/**
* It also works in static context!
*/
public static function getNumberForLetter($letter)
{
return once(function () use ($letter) {
return $letter . rand(1, 10000000);
});
}
}This behavior makes argument values part of the cache identity, but it also makes that identity important. The README says the hash is unique per function and the values of arguments received by that function. It does not document rules for objects, resources, arrays with different internal representations, or mutations after a value has been cached. Consequently, callers should not infer broad normalization rules that the documentation does not state.
Static context is explicitly supported by the example, but that does not mean once() is a substitute for a general application cache. Its documented role is to avoid repeating a particular callable's computation under a particular call-site identity. If a value must change after the first call, if arguments are not suitable for the package's derived identity, or if reuse must extend beyond the documented request behavior, the call site needs a different cache policy.
Flushing, disabling, and testing spatie/once
The package exposes controls on Spatie\Once\Cache::getInstance(). These controls matter because test isolation and cache invalidation are separate concerns. Disabling the cache prevents a test from inheriting a memoized value during the period when the singleton remains available. Enabling it again restores normal operation.
To empty the entire cache, use the documented flush call:
Spatie\Once\Cache::getInstance()->flush();The README specifically suggests disabling the cache in tests and gives these commands for changing its state:
Spatie\Once\Cache::getInstance()->disable();Spatie\Once\Cache::getInstance()->enable();These methods operate on a singleton, so cache control is application-wide within that process rather than scoped through a callable parameter. The README does not document automatic test-reset integration, selective removal for one object or hash, TTL handling, or a way to pass a custom cache instance. A test suite that disables the package should account for re-enabling it if later tests expect memoized behavior.
Octane workers require explicit Spatie\Once cache flushing
The package does not treat a long-running Octane worker like an automatically isolated PHP request. According to the README, Octane compatibility is in beta and needs community testing. To keep once() working properly there, the cache must be flushed when Laravel's OperationTerminated event is triggered.
The documented configuration registers a custom FlushSpatieOnce listener alongside the existing listener entry:
// config/octane.php
OperationTerminated::class => [
FlushTemporaryContainerInstances::class,
// DisconnectFromDatabases::class,
// CollectGarbage::class,
FlushSpatieOnce::class, // we should create this class we have added here
],The application then creates the listener shown by the README. Its handler obtains the singleton cache and flushes it after operation termination.
// app/Listeners/FlushSpatieOnce.php
use Spatie\Once\Cache;
class FlushSpatieOnce
{
public function handle($event)
{
Cache::getInstance()->flush();
}
}Without this explicit lifecycle step, values associated with a worker process may outlive the operation that created them. That is the clearest documented failure mode for once(). The repository does not present Octane support as automatic, and it labels the behavior beta, so adopting it for a persistent worker means accepting both integration code and the stated testing status.
The WeakMap design, MIT licence, and maintenance state
The repository is not archived. Its last push was on 2026-07-09. The listed releases include 3.1.2 on 2025-11-25, 3.1.1 on 2024-05-27, and 3.1.0 on 2022-04-21. The README directs developers to CHANGELOG.md for release details, while the repository includes that file at the top level. The release spacing is irregular, so this package's exact change history should be read from the changelog rather than inferred from topic labels such as cache or performance.
The implementation uses a PHP 8 WeakMap, which makes PHP 8 a concrete compatibility consideration even though the short installation command does not include a version flag. WeakMap storage also means cache entries are connected to the lifetime of their object keys. That can avoid keeping every associated object alive through the cache itself, but the README does not quantify memory use or guarantee that every application object pattern behaves identically.
The project uses the MIT licence. For organizational review, that means the licence file is the relevant starting point for permitted use, modification, distribution, warranty, and liability terms. This article does not interpret the legal effect of those terms. Package installation and upgrades may also depend on the surrounding Composer application, PHP runtime, and custom Octane listener, none of which are version-pinned by the README.
When a property or a separate cache is a better fit
A manually managed property on the containing class is the clearest alternative when there is only one value to retain. Instead of deriving a hash from the backtrace and maintaining a shared WeakMap, the class can calculate a value in its constructor or lazily assign it to an instance property. The approach requires explicit naming and invalidation, but it removes the package's call-stack-based keying from the decision.
A separate application cache is a different alternative when the value must outlive a PHP object or request, expire after a defined interval, or be shared across workers. The repository does not document adapters or configuration for such persistent cache backends. Based on the published architecture, spatie/once is a small in-process memoization helper rather than a configurable caching service.
The right choice follows from lifetime and identity. Use once() when one method's result should be reused for matching arguments during the documented scope, and cache controls or Octane flushing are acceptable. Use explicit properties for straightforward object state. Use a separate cache system when persistence, expiration, serialization, or operational eviction is part of the requirement.
Editorial conclusion
Adopt spatie/once when repeated work can be isolated to a specific method call within one PHP request, especially when the result should vary with that method's argument values. Do not adopt it for values that must be recomputed on every invocation or as an undocumented process-wide cache. Check the PHP 8 requirement, the backtrace-based keying behavior, your test isolation, and the explicit Octane flush first. For short-lived PHP classes, the per-object WeakMap offers a focused memoization mechanism with little application-level cache code.
Frequently asked questions
What does spatie/once do in PHP?
spatie/once memoizes a callable result during the current request. It returns the stored value on later matching calls instead of executing the callback again.
How do I install and use spatie/once?
Install it with `composer require spatie/once`, then pass a callable to `once()`. The README states that PHP 8 is used through a WeakMap implementation.
Does spatie/once work with Octane?
The README describes Octane compatibility as beta and requires `Spatie\Once\Cache::getInstance()->flush()` when `OperationTerminated` is triggered. It does not provide automatic worker isolation.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/spatie-once)Star history chart for your README
[](https://hysenlabs.com/projects/spatie-once)Was this useful?