Caching
⏱ Caching library with easy-to-use API and many cache backends.
Install / Use
/learn @nette/CachingREADME
Nette Caching
Introduction
Cache accelerates your application by storing data - once hardly retrieved - for future use.
Documentation can be found on the website.
Support Me
Do you like Nette Caching? Are you looking forward to the new features?
Thank you!
Installation
composer require nette/caching
It requires PHP version 8.1 and supports PHP up to 8.5.
Basic Usage
The center of work with the cache is the object Nette\Caching\Cache. We create its instance and pass the so-called storage to the constructor as a parameter. Which is an object representing the place where the data will be physically stored (database, Memcached, files on disk, ...). You will find out all the essentials in section Storages.
For the following examples, suppose we have an alias Cache and a storage in the variable $storage.
use Nette\Caching\Cache;
$storage // instance of Nette\Caching\IStorage
The cache is actually a key–value store, so we read and write data under keys just like associative arrays. Applications consist of a number of independent parts, and if they all used one storage (for idea: one directory on a disk), sooner or later there would be a key collision. The Nette Framework solves the problem by dividing the entire space into namespaces (subdirectories). Each part of the program then uses its own space with a unique name and no collisions can occur.
The name of the space is specified as the second parameter of the constructor of the Cache class:
$cache = new Cache($storage, 'Full Html Pages');
We can now use object $cache to read and write from the cache. The method load() is used for both. The first argument is the key and the second is the PHP callback, which is called when the key is not found in the cache. The callback generates a value, returns it and caches it:
$value = $cache->load($key, function () use ($key) {
$computedValue = ...; // heavy computations
return $computedValue;
});
If the second parameter is not specified $value = $cache->load($key), the null is returned if the item is not in the cache.
The great thing is that any serializable structures can be cached, not only strings. And the same applies for keys.
The item is cleared from the cache using method remove():
$cache->remove($key);
You can also cache an item using method $cache->save($key, $value, array $dependencies = []). However, the above method using load() is preferred.
Memoization
Memoization means caching the result of a function or method so you can use it next time instead of calculating the same thing again and again.
Methods and functions can be called memoized using call(callable $callback, ...$args):
$result = $cache->call('gethostbyaddr', $ip);
The function gethostbyaddr() is called only once for each parameter $ip and the next time the value from the cache will be returned.
It is also possible to create a memoized wrapper for a method or function that can be called later:
function factorial($num)
{
return ...;
}
$memoizedFactorial = $cache->wrap('factorial');
$result = $memoizedFactorial(5); // counts it
$result = $memoizedFactorial(5); // returns it from cache
Expiration & Invalidation
With caching, it is necessary to address the question that some of the previously saved data will become invalid over time. Nette Framework provides a mechanism, how to limit the validity of data and how to delete them in a controlled way ("to invalidate them", using the framework's terminology).
The validity of the data is set at the time of saving using the third parameter of the method save(), eg:
$cache->save($key, $value, [
Cache::Expire => '20 minutes',
]);
Or using the $dependencies parameter passed by reference to the callback in the load() method, eg:
$value = $cache->load($key, function (&$dependencies) {
$dependencies[Cache::Expire] = '20 minutes';
return ...;
]);
Or using 3rd parameter in the load() method, eg:
$value = $cache->load($key, function () {
return ...;
], [Cache::Expire => '20 minutes']);
In the following examples, we will assume the second variant and thus the existence of a variable $dependencies.
Expiration
The simplest exiration is the time limit. Here's how to cache data valid for 20 minutes:
// it also accepts the number of seconds or the UNIX timestamp
$dependencies[Cache::Expire] = '20 minutes';
If we want to extend the validity period with each reading, it can be achieved this way, but beware, this will increase the cache overhead:
$dependencies[Cache::Sliding] = true;
The handy option is the ability to let the data expire when a particular file is changed or one of several files. This can be used, for example, for caching data resulting from procession these files. Use absolute paths.
$dependencies[Cache::Files] = '/path/to/data.yaml';
// nebo
$dependencies[Cache::Files] = ['/path/to/data1.yaml', '/path/to/data2.yaml'];
We can let an item in the cache expired when another item (or one of several others) expires. This can be used when we cache the entire HTML page and fragments of it under other keys. Once the snippet changes, the entire page becomes invalid. If we have fragments stored under keys such as frag1 and frag2, we will use:
$dependencies[Cache::Items] = ['frag1', 'frag2'];
Expiration can also be controlled using custom functions or static methods, which always decide when reading whether the item is still valid. For example, we can let the item expire whenever the PHP version changes. We will create a function that compares the current version with the parameter, and when saving we will add an array in the form [function name, ...arguments] to the dependencies:
function checkPhpVersion($ver): bool
{
return $ver === PHP_VERSION_ID;
}
$dependencies[Cache::Callbacks] = [
['checkPhpVersion', PHP_VERSION_ID] // expire when checkPhpVersion(...) === false
];
Of course, all criteria can be combined. The cache then expires when at least one criterion is not met.
$dependencies[Cache::Expire] = '20 minutes';
$dependencies[Cache::Files] = '/path/to/data.yaml';
Invalidation using Tags
Tags are a very useful invalidation tool. We can assign a list of tags, which are arbitrary strings, to each item stored in the cache. For example, suppose we have an HTML page with an article and comments, which we want to cache. So we specify tags when saving to cache:
$dependencies[Cache::Tags] = ["article/$articleId", "comments/$articleId"];
Now, let's move to the administration. Here we have a form for article editing. Together with saving the article to a database, we call the clean() command, which will delete cached items by tag:
$cache->clean([
Cache::Tags => ["article/$articleId"],
]);
Likewise, in the place of adding a new comment (or editing a comment), we will not forget to invalidate the relevant tag:
$cache->clean([
Cache::Tags => ["comments/$articleId"],
]);
What have we achieved? That our HTML cache will be invalidated (deleted) whenever the article or comments change. When editing an article with ID = 10, the tag article/10 is forced to be invalidated and the HTML page carrying the tag is deleted from the cache. The same happens when you insert a new comment under the relevant article.
Tags require Journal.
Invalidation by Priority
We can set the priority for individual items in the cache, and it will be possible to delete them in a controlled way when, for example, the cache exceeds a certain size:
$dependencies[Cache::Priority] = 50;
Delete all items with a priority equal to or less than 100:
$cache->clean([
Cache::Priority => 100,
]);
Priorities require so-called Journal.
Clear Cache
The Cache::All parameter clears everything:
$cache->clean([
Cache::All => true,
]);
Bulk Reading
For bulk reading and writing to cache, the bulkLoad() method is used, where we pass an array of keys and obtain an array of values:
$values = $cache->bulkLoad($keys);
Method bulkLoad() works similarly to load() with the second callback parameter, to which the key of the generated item is passed:
$values = $cache->bulkLoad($keys, function ($key, &$dependencies) {
$computedValue = ...; // heavy computations
return $computedValue;
});
Output Caching
The output can be captured and cached very elegantly:
if ($capture = $cache->start($key)) {
echo ... // printing some data
$capture->end(); // save the output to the cache
}
In case that the output is already present in the cache, the start() method prints it and returns null, so the condition will not be executed. Otherwise, it starts to buff
