Once
A magic memoization function

#A magic memoization function
This 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 security@spatie.be 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.
