memoization function
A magicThis package contains a once
function. You can pass a callable
to it. Here's quick example:
$myClass = new class() {
public function getNumber(): int
{
return once(function () {
return rand(1, 10000);
});
}
};
No matter how many times you run $myClass->getNumber()
inside the same request you'll always get the same number.
Are you a visual learner?
Under the hood, this package uses a PHP 8 Weakmap. In this video, you'll see what a weakmap is, together with a nice demo of the package.
Support us
We invest a lot of resources into creating best in class open source packages. You can support us by buying one of our paid products.
We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on our contact page. We publish all received postcards on our virtual postcard wall.
Installation
You can install the package via composer:
composer require spatie/once
Usage
The once
function accepts a callable
.
$myClass = new class() {
public function getNumber(): int
{
return once(function () {
return rand(1, 10000);
});
}
};
No matter how many times you run $myClass->getNumber()
you'll always get the same number.
The once
function will only run once per combination of argument values the containing method receives.
class MyClass
{
/**
* It also works in static context!
*/
public static function getNumberForLetter($letter)
{
return once(function () use ($letter) {
return $letter . rand(1, 10000000);
});
}
}
So calling MyClass::getNumberForLetter('A')
will always return the same result, but calling MyClass::getNumberForLetter('B')
will return something else.
Flushing the cache
To flush the entire cache you can call:
Spatie\Once\Cache::getInstance()->flush();
Disabling the cache
In your tests you probably don't want to cache values. To disable the cache you can call:
Spatie\Once\Cache::getInstance()->disable();
You can re-enable the cache with
Spatie\Once\Cache::getInstance()->enable();
Octane compatibility
Warning
This behaviour is in beta and needs to be tested by the community. You can find this topic in discussion #79.
In order to have once
working properly in an Octane environment, Cache::getInstance()->flush()
must be called when OperationTerminated
event is triggered. You can configure it in config/octane.php
file:
// config/octane.php
OperationTerminated::class => [
FlushTemporaryContainerInstances::class,
// DisconnectFromDatabases::class,
// CollectGarbage::class,
FlushSpatieOnce::class, // we should create this class we have added here
],
And create the FlushSpatieOnce:class
:
// app/Listeners/FlushSpatieOnce.php
use Spatie\Once\Cache;
class FlushSpatieOnce
{
public function handle($event)
{
Cache::getInstance()->flush();
}
}
Under the hood
The once
function will execute the given callable and save the result in the $values
property of Spatie\Once\Cache
. This class is a singleton. When we detect that once
has already run before, we're just going to return the value stored inside the $values
weakmap instead of executing the callable again.
The first thing it does is calling debug_backtrace
. We'll use the output to determine in which function and class once
is called and to get access to the object
that function is running in. Yeah, we're already in voodoo-land. The output of the debug_backtrace
is passed to a new instance of Backtrace
. That class is just a simple wrapper, so we can work more easily with the backtrace.
$trace = debug_backtrace(
DEBUG_BACKTRACE_PROVIDE_OBJECT, 2
)[1];
$backtrace = new Backtrace($trace);
$object = $backtrace->getObject();
Next, we calculate a hash
of the backtrace. This hash will be unique per function once
was called in and the values of the arguments that function receives.
$hash = $backtrace->getHash();
Finally, we will check if there's already a value stored for the given hash. If not, then execute the given $callback
and store the result in the weakmap of Spatie\Once\Cache
. In the other case, we just return the value from that cache (the $callback
isn't executed).
public function has(object $object, string $backtraceHash): bool
{
if (! isset($this->values[$object])) {
return false;
}
return array_key_exists($backtraceHash, $this->values[$object]);
}
Changelog
Please see CHANGELOG for more information what has changed recently.
Testing
composer test
Contributing
Please see CONTRIBUTING for details.
Security
If you've found a bug regarding security please mail [email protected] instead of using the issue tracker.
Credits
And a special thanks to Caneco for the logo
Credit for the idea of the once
function goes to Taylor Otwell. The code for this package is based upon the code he was kind enough to share with us.
License
The MIT License (MIT). Please see License File for more information.