henzeb / warmable
A framework agnostic cache warmer
Requires
- php: ^8.1
- henzeb/datetime: ^1.0
- psr/simple-cache: ^3.0
Requires (Dev)
- infection/infection: *
- mockery/mockery: ^1.6
- phpunit/phpunit: ^9|^10
This package is auto-updated.
Last update: 2024-12-22 12:05:17 UTC
README
This package was inspired by a talk by @timacdonald where he showed a technique to warm up a cache entry scheduled by using a simple invokable class.
When you cache the result of a heavy operation, once in a while, some user will have to wait for the operation to be cached again. If the request rate is high enough, multiple people may have to do so at the same time.
This package provides the framework in which you can take all that away. It is implementing the PSR16 interface for caching, so it pretty much supports every caching mechanism you can think of, and if not, one can implement a wrapper, and it still supports every caching mechanism you can think of.
Laravel-developers can use this package as well, but in order to harvest the full power of Laravel, it's better to install and use Warmable for Laravel
Installation
Just install with the following command.
composer require henzeb/warmable
Terminology
Warm up / warming up
The term warm up
or warming up
refers to caching
during a cron job or in a queue job.
preheating
Preheating is the term used when the cache is populated during execution, which is the default operation when the cache does not exist. This may also be dispatched to a background operation like a Queueing System.
Usage
Creating a Warmable
Creating a Warmable
is pretty easy.
use Henzeb\Warmable\Warmable; use Psr\SimpleCache\CacheInterface; use DateTimeInterface; use DateInterval; class HeavyOperation extends Warmable { protected function key() : string { // ... return your key } protected function ttl() : DateTimeInterface|DateInterval|int|null { // ... return your desired ttl } protected function gracePeriod() : DateTimeInterface|DateInterval|int|null { // ... return your desired grace period } protected function cache(): CacheInterface { // ... your cache implementation } protected function warmable(): mixed { // ... your heavy operation } }
Note: The key
and the ttl
can be omitted. When omitting the key
,
a key is generated based on the FQCN of your object. When ttl
is omitted,
the Warmable
result will be stored in cache forever.
chaining methods.
Warmable
allows chaining methods. You can start with any method you wish.
HeavyOperation::withKey('yourKey')->get(); HeavyOperation::withTtl(300)->withKey('yourKey')->get(); HeavyOperation::withKey('yourKey')->withTtl(300)->get();
make
With make
you can easily get an instance of your Warmable
. You can add
arguments that will be passed on to your constructor.
// equivalent to new HeavyOperation(); HeavyOperation::make(); // equivalent to new HeavyOperation(new YourService()); HeavyOperation::make(new YourService());
get
With this method you can easily access the cached data.
HeavyOperation::get(); HeavyOperation::make()->get();
By default,Warmable
preheats the data for the first time if it does
not exist yet. If you don't want that to happen before response,
you can set a default. In that case it will postpone that action until
after the response is sent to the browser.
HeavyOperation::get([]); HeavyOperation::get(true); HeavyOperation::get(fn()=>false);
Note: this doesn't work with null
.
Use withoutPreheating instead.
with
The warmable method supports a rudimentary version of dependency injection. Using this method, you can make your cache unique per user or item. You donĀ“t have to do anything else besides passing it into the constructor.
use Henzeb\Warmable\Warmable; class HeavyOperation extends Warmable { // ... public function warmable(int $id): mixed { // ... return 'this is '.$id; } } HeavyOperation::with(12)->get(); // returns 'this is 12' HeavyOperation::with(14)->get(); // returns 'this is 14' HeavyOperation::with(12)->get(); // returns 'this is 12' from cache HeavyOperation::with(14)->get(); // returns 'this is 14' from cache
key
By default, Warmable
will generate a unique key for you. You don't need to do
anything. Even if you want to reuse the Warmable
for different items,
Warmable
got your back. But if you do want to customize the key, there are 2
ways to do it.
The first is by setting the key
method on your Warmable
class.
use Henzeb\Warmable\Warmable; class HeavyOperation extends Warmable { protected function key() : string{ return 'your-key'; } } HeavyOperation::get(); // retrieves from 'your-key' HeavyOperation::with(12)->get(); // retrieves from 'your-key.<sha1 hash>'
The second option is inline:
HeavyOperation::withKey('other-key')->get(); // retrieves from 'other-key' HeavyOperation::withKey('other-key') ->with(12) ->get(); // retrieves from 'other-key.<sha1 hash>'
Cache
The cache is, obviously, a required element. It's set through the mandatory
cache
method. There is also a withCache
method that can be used inline.
use Psr\SimpleCache\CacheInterface; HeavyOperation::withCache(<CacheInterface::class>); HeavyOperation::make()->withCache(<CacheInterface::class>);
Note: Warmable
accepts any Cache implementation that implements the PSR-16
CacheInterface
. If the cache system you are using does not implement it,
you can easily create a wrapper that does implement it.
Time To Live (Ttl)
Allows you to use a different TTL than the one defined inside your class.
Note Ttl is null
, and thus forever
, by default.
use Henzeb\Warmable\Warmable; class HeavyOperation extends Warmable { protected function ttl(): DateTimeInterface|DateInterval|int|null { return DateInterval::createFromDateString('300 seconds'); } }
Or you can set things inline inline:
use Henzeb\Warmable\Warmable; // expires on given date HeavyOperation::withTtl(new DateTime('2024-02-31 23:00')); // expires in 300 seconds HeavyOperation::withTtl( DateInterval::createFromDateString('300 seconds') ); // expires in 300 seconds HeavyOperation::withTtl(300);
Grace periods
In combination with a Ttl, a grace period allows you to give the user the old data while updating the cache. This updating happens after returning the response to the browser.
The example below will create a cache item that lives for 600 seconds. 300+ seconds in, the next call will return the value in the cache, and registers a shutdown function that updates the cache item, which will live for 600 seconds, and so on.
use Henzeb\Warmable\Warmable; class HeavyOperation extends Warmable { protected function ttl(): DateTimeInterface|DateInterval|int|null { return 300 } protected function gracePeriod(): DateTimeInterface|DateInterval|int|null { return 300 } }
getKey
There may be occasions where you need to see the key used by your Warmable
.
// returns warmable.HeavyOperation HeavyOperation::getKey(); // returns test HeavyOperation::withKey('test')->getKey(); // returns test.<sha1 hash> HeavyOperation::withKey('test')->with(id)->getKey();
missing
// returns true HeavyOperation::missing(); // returns false HeavyOperation::get(); // will preheat the cache HeavyOperation::make()->missing(); HeavyOperation::missing();
isPreheated
This method tells you if the cache was preheated.
$operation = HeavyOperation::make(); $operation->isPreheated(); // returns false $operation->get(); // will preheat the cache $operation->isPreheated(); // returns true
withoutPreheating
This method allows you to explicitly disable preheating.
HeavyOperation::withoutPreheating()->get(); // returns null when not warmed up // and when called again: HeavyOperation::withoutPreheating()->get(); // returns null when not warmed up
withPreheating
Works exactly like withoutPreheating, except it switches the preheating on.
// would preheat the warmable data. HeavyOperation::withPreheating()->get(); // would preheat the data after shutdown, and return default instead. HeavyOperation::withPreheating()->get([]);
shouldPreheat
A method that tells you if preheat flag is turned on
HeavyOperation::shouldPreheat(); // returns true (default) HeavyOperation::withPreheating()->shouldPreheat(); // returns true HeavyOperation::withoutPreheating()->shouldPreheat(); // returns false
cooldown
If for some reason you need to delete the cache, you can call cooldown
.
HeavyOperation::cooldown(); HeavyOperation::make()->cooldown();
Custom warmup strategy
If your application has access to a queue, it is easy to change the preheat strategy. By default, it preheats during execution.
use Henzeb\Warmable\Warmable; class HeavyOperation extends Warmable { // ... your Warmable definition protected function getPreheated(bool $hasDefault): mixed { // ... } }
Note: Returning null
causes the get
method to use the default.
Overriding public interface
Under the hood, Warmable
uses __call
and __callStatic
to allow chaining
static and dynamic calls. If you want to override a method on the public interface,
you should find it's call<method>
counterpart.
For Example: If you want to override the with
method, you should extend the
callWith
method.
Testing
composer test
Contributing
Please see CONTRIBUTING for details.
Security
If you discover any security related issues, please email henzeberkheij@gmail.com instead of using the issue tracker.
Credits
License
The GNU AGPLv. Please see License File for more information.