Documentation menu
On this page
Components · v1.x
Cache
The Cache component stores computed values in named pools: filesystem, APCu, database or in-memory adapters, TTL, tags, and get() with a callback protected against cache stampedes.
No external library is used. The HttpClient can store its responses in a pool (see HttpClient integration).
Quick start
use NeoPHP\Component\Cache\Contract\CacheInterface;
class WeatherService
{
public function __construct(protected CacheInterface $cache, protected WeatherApi $api)
{
}
public function today(string $city): array
{
return $this->cache->get('weather.' . md5($city), fn (): array => $this->api->fetch($city), 600);
}
}
The first call runs the callback and stores its result for 600 seconds; the next calls read the stored value. Injecting CacheInterface gives the default pool.
Configuration
config/framework/cache.yaml (generated by the installer):
default_pool: app
pools:
app:
adapter: filesystem
default_ttl: 3600
A complete example:
default_pool: app
pools:
app:
adapter: filesystem
default_ttl: 3600 # seconds, ~ = forever
directory: '%kernel.root_path%/var/cache/pools/app'
fast:
adapter: apcu
namespace: myapp # key prefix (default: the pool name)
default_ttl: 300
shared:
adapter: database
connection: default # connection of config/framework/database.yaml (default connection when omitted)
table: cache_items
default_ttl: ~
runtime:
adapter: array
| Pool option | Default | Description |
|---|---|---|
adapter |
filesystem |
filesystem, apcu, database or array |
default_ttl |
~ |
TTL used when set() / get() receive none; ~ = no expiration |
namespace |
pool name | prefix of the keys, so that pools sharing a storage do not collide |
lock_timeout |
5 |
seconds a process waits for the lock of computed values |
directory |
%kernel.root_path%/var/cache/pools/<pool> |
filesystem only; a relative path is relative to the project root |
connection |
default connection | database only |
table |
cache_items |
database only |
A pool can also be written as a string: runtime: array.
Without cache.yaml, one pool exists: app, filesystem, default_ttl: 3600.
Adapters
| Adapter | Storage | Notes |
|---|---|---|
filesystem |
one file per item, in sub-directories named after the hash of the key | writes are atomic (temporary file + rename()); the expiration timestamp is the first line of the file; locks with flock() in .locks/ |
apcu |
shared memory of APCu | requires ext-apcu; creating the pool throws a CacheException when the extension is missing or disabled (set apc.enable_cli=1 to use it in the console); locks with apcu_add() |
database |
a table of a Database connection | the table is created on first use (MySQL / MariaDB, SQLite, PostgreSQL); values are serialized then base64 encoded; locks are rows inserted in the same table |
array |
PHP memory | lost at the end of the request; useful for tests and per-request memoization |
The table of the database adapter:
CREATE TABLE IF NOT EXISTS cache_items (
item_key VARCHAR(255) NOT NULL PRIMARY KEY,
item_value TEXT NOT NULL, -- LONGTEXT on MySQL / MariaDB
expires_at INTEGER NULL
);
DatabaseAdapter::createTable() creates it explicitly (for example in a migration).
Reading and writing
$cache->set('user.42', $user); // default TTL of the pool
$cache->set('user.42', $user, 60); // 60 seconds
$cache->set('user.42', $user, new DateInterval('PT1H'));
$cache->get('user.42'); // value, or null when missing
$cache->has('user.42');
$cache->delete('user.42');
$cache->setMany(['a' => 1, 'b' => 2], 300);
$cache->getMany(['a', 'b', 'c'], 'default'); // ['a' => 1, 'b' => 2, 'c' => 'default']
$cache->deleteMany(['a', 'b']);
$cache->clear(); // every item of the pool
- Values are stored with
serialize(): scalars, arrays and serializable objects are supported (not closures or resources).nullcan be stored: usehas()to distinguish it from a missing key. - A TTL is an integer (seconds) or a
DateInterval; a TTL of0or less deletes the item. - Keys cannot be empty and cannot contain
{}()/\@:; an invalid key throwsNeoPHP\Component\Cache\Exception\InvalidArgumentException. Use dots or underscores:user.42,weather_paris.
Computing values
get() with a callback returns the stored value, or runs the callback, stores its result and returns it:
use NeoPHP\Component\Cache\Item\CacheItem;
$products = $cache->get('products.home', function (CacheItem $item) use ($repository): array {
$products = $repository->findFeatured();
$item->expiresAfter(count($products) > 0 ? 3600 : 60);
$item->tag(['products']);
return $products;
}, 600, ['home']);
The callback receives a CacheItem: getKey(), expiresAfter(int|DateInterval|null) and tag(string|array) change the TTL and the tags given to get() (third and fourth arguments). The argument can be omitted: fn (): array => ... works too.
Stampede protection: when many requests miss the same key at the same time, only one computes it. The process that misses takes a lock on the key (flock() for filesystem, apcu_add() for APCu, a lock row for database), reads the key again, and computes the value only if it is still missing. The other processes wait for the lock, then read the value stored by the first one. When the lock cannot be taken within lock_timeout seconds (5 by default), the process computes the value without lock. The array adapter does not lock (it is local to the process).
Tags
Tags invalidate groups of items without knowing their keys:
$cache->set('product.1', $product, 3600, ['products', 'category.4']);
$cache->get('category.4.list', fn (): array => $repository->byCategory(4), 3600, ['category.4']);
$cache->invalidateTags(['category.4']); // product.1 and category.4.list become misses
Each tag has a version stored in the pool; an item keeps the versions of its tags when it is written. invalidateTags() changes the versions, so the items written before are read as misses (and are overwritten or pruned later). Tag names follow the key rules.
Expiration and pruning
Expired items are never returned. APCu removes them itself; the array adapter drops them on read. The filesystem and database adapters delete an expired item when it is read, and prune() removes all the expired items of a pool:
$removed = $cache->prune();
php bin/neo cache:pool:prune # schedule it with cron
Named pools
use NeoPHP\Component\Cache\Contract\CacheInterface;
use NeoPHP\Component\Cache\Contract\CacheManagerInterface;
use NeoPHP\Component\Container\Attribute\Autowire;
class ReportService
{
public function __construct(
protected CacheManagerInterface $caches,
#[Autowire(service: 'cache.shared')] protected CacheInterface $shared,
) {
}
public function build(): array
{
return $this->caches->pool('fast')->get('report', fn (): array => $this->compute());
}
}
| Service | Value |
|---|---|
CacheManagerInterface, CacheManager, CacheInterface, cache |
the manager; used as CacheInterface, it delegates to the default pool |
cache.<pool> |
the pool <pool> |
CacheManagerInterface: pool(?string $name = null), hasPool(), getPoolNames(), getDefaultPool(), getPoolConfig(). An unknown pool throws a CacheException listing the configured pools.
Controllers
AbstractController provides cache():
public function index(): Response
{
$stats = $this->cache()->get('stats', fn (): array => $this->computeStats(), 300);
$top = $this->cache('fast')->get('top', fn (): array => $this->computeTop());
return $this->render('home/index', ['stats' => $stats, 'top' => $top]);
}
cache() returns the default pool, cache('name') a named pool.
Console
| Command | Description |
|---|---|
cache:pool:list |
pools with their adapter, default TTL and namespace |
cache:pool:clear [pools]... [--all] |
clears pools (asks which one when none is given) |
cache:pool:delete <pool> <key> |
deletes one item |
cache:pool:invalidate-tags <tags>... [--pool=] |
invalidates tags in every pool, or only in the --pool ones (repeatable) |
cache:pool:prune [pools]... |
removes the expired items (every pool by default) |
cache:clear (Kernel) is different: it deletes the files of var/cache/ (routes, templates, container...). It does not know the pools: APCu and database pools are not cleared by it, while filesystem pools stored in var/cache/pools/ (the default directory) are removed with the rest of var/cache/. Use cache:pool:clear to clear pools whatever their adapter.
HttpClient integration
The cache option of the HttpClient stores GET / HEAD responses in a pool, honors Cache-Control and revalidates stale responses with ETag / Last-Modified:
$http->get('https://api.example.com/countries', ['cache' => true]);
$http->get('https://api.example.com/countries', ['cache' => ['pool' => 'shared', 'ttl' => 3600]]);
See Caching in the HttpClient documentation.
CacheInterface reference
| Method | Description |
|---|---|
getName(): string |
pool name |
getAdapter(): AdapterInterface |
storage adapter |
get(string $key, ?callable $callback = null, int|DateInterval|null $ttl = null, array $tags = []): mixed |
value; with a callback, computes and stores it when missing |
set(string $key, mixed $value, int|DateInterval|null $ttl = null, array $tags = []): bool |
stores a value |
has(string $key): bool |
the key exists and is not expired or invalidated |
delete(string $key): bool |
deletes an item |
getMany(array $keys, mixed $default = null): array |
values indexed by key |
setMany(array $values, int|DateInterval|null $ttl = null, array $tags = []): bool |
stores several values |
deleteMany(array $keys): bool |
deletes several items |
clear(): bool |
deletes every item of the pool |
invalidateTags(array $tags): bool |
invalidates the items having one of these tags |
prune(): int |
removes the expired items, returns their number |
Custom storages implement Contract\AdapterInterface (usually by extending Contract\AbstractAdapter) and are wrapped in a CachePool: new CachePool('name', $adapter, 3600).
Changelog
- v1.22.0 — Cache component:
CacheInterface(get()with callback and stampede protection,set(),has(),delete(),getMany()/setMany()/deleteMany(),clear(),invalidateTags(),prune()),CacheManagerwith named poolscache.<pool>, filesystem / APCu / database / array adapters, tags,cache()in controllers,cache:pool:list/cache:pool:clear/cache:pool:delete/cache:pool:invalidate-tags/cache:pool:prunecommands,config/framework/cache.yaml.