Storages
The storage decides where cached data physically lives: in files on disk, on a Memcached server or in an SQLite database. You can switch it without touching the code that uses the cache. This page helps you:
- choose the right storage and see what each one supports
- configure it in a Nette application, or turn the cache off
- use a Nette storage as a PSR-16 cache
- write a storage of your own
Choosing a Storage
If you are not sure, keep the default FileStorage. It needs nothing but a directory, it is safe when many requests
read and write at once, and it supports every kind of dependency.
When your application runs on several servers, FileStorage gives each of them its own cache.
MemcachedStorage stores the data on a server shared by all of them, but it keeps tags and priorities in a journal, a file local to each server, so deleting by tag on one server does not reach the others.
The storages differ in which dependencies they can honor:
| Storage | Tags, priorities | Files, constants, callbacks | Other items | Sliding | Cleaning namespaces | Stampede protection |
|---|---|---|---|---|---|---|
| FileStorage | with journal | yes | yes | yes | yes | yes |
| MemcachedStorage | with journal | yes | no | yes | ignored | no |
| SQLiteStorage | tags only | ignored | ignored | yes | ignored | no |
| MemoryStorage | ignored | ignored | ignored | ignored | ignored | no |
| DevNullStorage | stores nothing |
Where the table says ignored, the storage silently drops the dependency, so the item just does not expire that way.
MemoryStorage ignores even the expiration. Only MemcachedStorage throws
Nette\NotSupportedException, for the dependency on other items.
Stampede protection means that when many requests ask for a missing item at once, only the first one runs the generator of
load() and the others wait for its result. Without it, all of them compute the value at the same time.
Configuration
In a Nette application, the DI container provides a FileStorage that stores data in the cache
subdirectory of the directory for temporary
files, together with a journal. You get it via dependency injection by asking for the type
Nette\Caching\Storage.
To use another storage, replace the service cache.storage in the configuration:
services:
cache.storage: Nette\Caching\Storages\MemcachedStorage('10.0.0.5')
The container holds these two services:
| Name | Type | Description |
|---|---|---|
cache.storage |
Nette\Caching\Storage | the storage |
cache.journal |
Nette\Caching\Storages\Journal | the journal for tags and priorities |
Turning Off the Cache
To turn the cache off, for example in tests, use DevNullStorage, which stores nothing:
services:
cache.storage: Nette\Caching\Storages\DevNullStorage
This does not affect the caches of Latte templates and of the DI container, which are separate and do not use this storage. They do not need to be turned off during development either.
FileStorage
Nette\Caching\Storages\FileStorage writes each item into a file in the given directory, which must exist. Tags and
priorities need a journal:
$journal = new Nette\Caching\Storages\SQLiteJournal('/path/to/temp/journal.s3db');
$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp', $journal);
All operations are atomic. You can never read a file another request has not finished writing yet, and nobody can delete it while you are reading it. That makes the storage safe even under heavy load.
It also protects against the cache stampede. When many requests ask for a missing item at once, for example right after a deployment, the first one locks the file and runs the generator. The others wait for the lock and then read the fresh value instead of computing it again.
Expired files are removed now and then by the constructor. To remove them at a moment of your choosing, call
$storage->clean([]).
MemcachedStorage
Nette\Caching\Storages\MemcachedStorage stores data on a Memcached server. The
constructor takes the host and the port, 11211 by default. Tags and priorities need a journal.
Requires the memcached PHP extension.
services:
cache.storage: Nette\Caching\Storages\MemcachedStorage('10.0.0.5')
Memcached cannot list its keys, so the storage ignores cleaning by namespaces. For the dependency on other items, it throws an exception. Clearing the whole cache empties the entire Memcached server, including the data of other applications.
SQLiteStorage
Nette\Caching\Storages\SQLiteStorage keeps the whole cache in a single SQLite database file. It stores tags in its
own table, so it needs no journal.
Requires the pdo and pdo_sqlite PHP extensions.
services:
cache.storage: Nette\Caching\Storages\SQLiteStorage('%tempDir%/cache.db')
The storage supports expiration, sliding expiration and tags. Priorities, dependencies on files, constants, callbacks and other items, and cleaning by namespaces are silently ignored.
MemoryStorage
Nette\Caching\Storages\MemoryStorage keeps data in a PHP array, so it is lost when the request ends. It is handy
in tests and for caching within a single request.
It ignores all dependencies, expiration included, and of the conditions of clean() it honors only
Cache::All. Keep that in mind when you use it in tests instead of a real storage: an item that would expire there
stays here.
DevNullStorage
Nette\Caching\Storages\DevNullStorage stores nothing, so every load() calls the generator. Use it to
turn the cache off.
Journal
FileStorage and MemcachedStorage cannot search their items by tag or priority, so they keep a
separate index, the so-called journal. The built-in Nette\Caching\Storages\SQLiteJournal stores it in an SQLite
database:
$journal = new Nette\Caching\Storages\SQLiteJournal('/path/to/temp/journal.s3db');
Requires the pdo and pdo_sqlite PHP extensions.
In a Nette application, the journal is created for you as the service cache.journal in the file
journal.s3db and passed to the storage automatically. Your own implementation of the interface
Nette\Caching\Storages\Journal can replace it:
services:
cache.journal: MyJournal
Without a journal, these storages work too, but storing an item with tags or a priority throws an exception.
Using a Storage as a PSR-16 Cache
Some libraries expect a PSR-16 cache. The adapter
Nette\Bridges\Psr\PsrCacheAdapter gives them one on top of any Nette storage:
$psrCache = new Nette\Bridges\Psr\PsrCacheAdapter($storage);
$psrCache->set('key', 'value', 3600); // stores the value for 1 hour
$value = $psrCache->get('key', 'default');
The adapter supports all methods of PSR-16, including getMultiple(), setMultiple() and
deleteMultiple().
Writing Your Own Storage
A storage is a class implementing the interface Nette\Caching\Storage (before version 3.1 named
Nette\Caching\IStorage) with five methods:
read(string $key)returns the stored value, ornullwhen the item does not exist or has expiredlock(string $key): voidlocks the item beforeCacheruns a generator; the lock is released bywrite()orremove(), and a storage that does not protect against the cache stampede leaves the method emptywrite(string $key, mixed $data, array $dependencies): voidstores the valueremove(string $key): voiddeletes the itemclean(array $conditions): voiddeletes items by the conditions of clean(); an empty array means removing the expired items
The dependencies arrive as an array with Cache::* keys, already simplified by Cache: the expiration
is a number of seconds from now, files and constants have become callbacks, and other items are given by their storage keys. When
reading an item, check its callbacks with Nette\Caching\Cache::checkCallbacks(). Throw
Nette\NotSupportedException for a dependency or condition your storage cannot honor.
Optional interfaces add more. Nette\Caching\BulkReader and, since version 3.3,
Nette\Caching\BulkWriter let the storage read and write several items in one round trip.
A short complete example is the source code of SQLiteStorage.