Caching

Cache accelerates your application by storing data – once hardly retrieved – for future use. We will show you:

How to use the cache

How to change the cache storage

How to properly invalidate the cache

Nette Framework offers a very intuitive API for cache manipulation. After all, you wouldn't expect anything else, right? ;-)
Before we proceed to the first example, we need to think about place where to store data physically. We can use a database,
Memcached server, or the most available storage – hard drive:

// the `temp` directory will be the storage$storage = new Nette\Caching\Storages\FileStorage('temp');

The Nette\Caching\Storages\FileStorage storage
is very well optimized for performance and in the first place, it provides full atomicity of operations. What does that mean? When
we use cache we can be sure we are not reading a file that is not fully written yet (by another thread) or that the file gets
deleted “under our hands”. Using the cache is therefore completely safe. More on storage in Cache
Storage section.

This way, we can read from the cache: (if there is no such item in the cache, the null value is returned)

$value = $cache->load($key);
if ($value === null) ...

Method load() has second parameter callable$fallback, which is called when there is no
such item in the cache. This callback receives the array $dependencies by reference, which you can use for setting expiration rules.

We could delete the item from the cache either by saving null or by calling remove() method:

$cache->save($key, null);
// or$cache->remove($key);

It's possible to save any structure to the cache, not only strings. The same applies for keys.

Web applications typically consist of a number of independent parts, and if they all cache data in the same storage (for
example the same directory), sooner or later there would be collisions in names. Nette Framework solves this by splitting the
whole storage to sections (in the FileStorage case using subdirectories). Each part of the application uses it's own
section with unique name, therefore no collision can occur. The name of the section can be passed as the second parameter to the
Cache class constructor. (These sections are often referred to as cache namespaces.)

$cache = new Cache($storage, 'HtmlOutput');

Caching in Templates

Caching in templates is very easy, just wrap the part of the template with the {cache} ... {/cache}. The cache is
automatically invalidated when the source template is changed, including any templates included inside the {cache}
macro. {cache} blocks can be nested, and when a nested block gets invalidated (for example by a tag), the parent
block is also invalidated.

It's possible to define keys to which the cache will be binded (in this case, the $id variable) and set the
expiration and tags for invalidation.

Output Caching

In case that the output is already present in the cache, the start() method prints it and return
null. Otherwise, it starts to buffer the output and returns the $block object using which we finally
save the data to the cache.

Expiration and Invalidation

Here come two problems of storing data in the cache. First, there is a possibility that the storage is completely filled and
you cannot save more data inside. And it may happen that some od the previously saved data will become invalid over time.
Therefore, 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).

Data validity is set when saving the data using the third parameter of the save() method:

It's obvious from the code itself, that we saved the data for next 20 minutes. After this period, the cache will report that
there is no record under the ‘$key’ key (ie, will return null). In fact, you can use any time value
that is a valid value for PHP function strToTime() or the DateTime class. If we want to extend the validity period with each reading, it can be
achieved this way:

Very handy is also the ability to let the data expire when a particular file is changed or one of several files. That can be
used for storing data resulting from parsing these files to the cache. For trouble-free functionality, it's recommended to use
absolute paths.

$cache->save($key, $data, [
Cache::FILES => 'data.yaml', // an array of files can also be specified
]);

The Cache::FILES criteria, of course, can be combined with the time expiration using
Cache::EXPIRE etc.

The cache can also depend on other cached items. That can be used when we save the whole HTML page in the cache and under
different keys, some of its fragments. As soon as a part changes, the whole page is invalidated.

Expiration Using Tags and Priority

So called tags are a very useful invalidation tool. We can assign a list of tags to each item stored in 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:

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"],
]);

And in the place for adding new comments (or editing them), don't forget to invalidate appropriate tag:

$cache->clean([
Cache::TAGS => ["comments/$articleId"],
]);

What we have achieved? That the HTML cache will invalidate automatically. Whenever someone changes the article with ID of 10,
it will force the article/10 tag to invalidate and the tagged HTML page in cache is cleared. The same will happen
when someone inserts a new comment below the article.

Similar to tags, you can control expiration by priority:

$cache->save($key, $value, [
Cache::PRIORITY => 50,
]);
// all cached items with priority less than or equal to 100 will be removed.$cache->clean([
Cache::PRIORITY => 100,
]);

Finally we mention the Cache::ALL flag, which deletes everything.

Cache Storage

In addition to already mentioned FileStorage, Nette also provides another ones:

Of course, it's possible to create your own storage. The only requirement is to implement the IStorage interface.

Storage Service

We can use the Dependency Injection so we do not have to create the
$storage object everywhere. Nette framework provides a service implementing IStorage interface. If you don't specify
a concrete implementation in configuration, FileStorage is used by default and it saves data to the directory you
specified by $configurator->setTempDirectory() in bootstrap.php.

Disabling Cache For Testing Purposes

Special implementation of the Nette\Caching\IStorage is a DevNullStorage. It doesn't save any data. This is
useful when we want to eliminate the effect of caching, usually while testing.

Configure the storage in config.neon:

services:
cacheStorage:factory: Nette\Caching\Storages\DevNullStorage

Concurrent Caching

Deleting the cache is a common operation when uploading a new application version to the server. At that moment, however, the
server gets pretty hard time, because it has to build a complete new cache. Retrieving some data can be quite difficult, for
example the RobotLoader cache building. And moreover, if, say, 30 requests come in a short
period, the resource consumption is even higher.

The solution is to modify application behaviour so that data are created only by one thread and others are waiting. To do this,
specify the value as a callback or use an anonymous function: