PSR-6, PSR-16 et l'interface Symfony
Symfony Cache implémente trois interfaces — PSR-6 (CacheItemPool), PSR-16 (SimpleCache), et sa propre CacheInterface avec callback.
Le composant Cache de Symfony implémente trois contrats distincts. Comprendre lequel utiliser — et pourquoi — est la première étape vers un cache sans surprise.
PSR-6 — CacheItemPoolInterface
Défini par PHP-FIG, PSR-6 est l'interface bas niveau. Tu travailles explicitement avec des
CacheItem : tu les récupères, tu les modifies, tu les sauvegardes.
// PSR-6 : récupérer → vérifier → utiliser ou calculer → sauvegarder
$item = $pool->getItem('product_42');
if (!$item->isHit()) {
$data = $this->repository->find(42);
$item->set($data);
$item->expiresAfter(3600);
$pool->save($item);
}
return $item->get();
getItem(string $key): CacheItemInterfacegetItems(array $keys): iterablehasItem(string $key): boolsave(CacheItemInterface $item): boolsaveDeferred(CacheItemInterface $item): boolcommit(): booldeleteItem(string $key): boolclear(): bool
PSR-16 — SimpleCache
Plus simple, sans objet CacheItem intermédiaire. API get/set/delete/clear
directement sur les valeurs. Adapté aux cas simples, sans TTL par item ni tags.
// PSR-16 : API directe sur les valeurs
$data = $cache->get('product_42', null);
if ($data === null) {
$data = $this->repository->find(42);
$cache->set('product_42', $data, 3600);
}
return $data;
CacheInterface Symfony — le pattern callback
L'interface propre à Symfony introduit le pattern callback : tu passes une fonction qui calcule la valeur si elle n'est pas en cache. Symfony gère le hit/miss, le TTL, et la protection contre le stampede.
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;
$data = $cache->get('product_42', function (ItemInterface $item) {
$item->expiresAfter(3600);
return $this->repository->find(42); // calculé seulement si miss
});
get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixedLe callback reçoit
CacheItemInterface $item et bool &$save (optionnel).
Mettre $save = false dans le callback empêche la mise en cache du résultat.
RESERVED_CHARACTERS
Les caractères {}()/\@: sont interdits dans les clés et dans les noms de tags.
Tout adapter les rejette avec une InvalidArgumentException à la construction. Utilise
des tirets, des underscores et des points à la place.
Clique sur une interface pour voir le pattern de code correspondant.
Les adapters
Un adapter est le pilote derrière le pool — il définit où les données sont stockées.
Chaque adapter implémente AdapterInterface (qui étend CacheItemPoolInterface
de PSR-6) et expose les mêmes méthodes. Choisir le bon adapter, c'est choisir le bon stockage
selon le contexte : développement, production mono-serveur, production distribuée, tests.
Tableau des adapters
| Adapter | Stockage | Contexte recommandé |
|---|---|---|
FilesystemAdapter |
Fichiers disque | Dev, prod mono-serveur sans Redis |
ArrayAdapter |
Tableau PHP en mémoire | Tests, cache intra-requête |
RedisAdapter |
Redis | Prod multi-serveur, tags, haute performance |
MemcachedAdapter |
Memcached | Prod multi-serveur (sans tags natifs) |
PdoAdapter |
Base de données SQL | Quand seule la BDD est disponible |
ApcuAdapter |
APCu (mémoire partagée PHP) | Prod mono-serveur, très faible latence |
ChainAdapter |
Plusieurs adapters en cascade | Multi-niveaux : APCu + Redis |
NullAdapter |
Rien (blackhole) | Tests — désactive complètement le cache |
ProxyAdapter |
Délègue à un PSR-6 | Adapter une bibliothèque PSR-6 tierce |
Constructeurs principaux
FilesystemAdapter
new FilesystemAdapter(
$namespace = '', // préfixe de clé
$defaultLifetime = 0, // 0 = pas d'expiration par défaut
$directory = null, // null = sys_get_temp_dir()/symfony-cache
$marshaller = null,
)
ArrayAdapter
new ArrayAdapter(
$defaultLifetime = 0,
$storeSerialized = true, // false = stocke la référence PHP directement
$maxLifetime = 0, // TTL maximum (plafond)
$maxItems = 0, // 0 = illimité. Si > 0 : LRU (éviction du plus ancien)
)
Quand
$maxItems > 0, l'adapter applique une politique LRU (Least Recently Used) :
le plus ancien item non utilisé est évincé quand la limite est atteinte. Utile pour un cache
intra-requête de taille bornée.
ApcuAdapter
new ApcuAdapter(
$namespace = '',
$defaultLifetime = 0,
$version = null, // changer $version invalide tout le namespace
$marshaller = null,
)
APCu ne supporte pas
clear() par préfixe. Le paramètre $version est
intégré dans les clés stockées — changer la version crée de nouvelles clés, les anciennes
expirent naturellement. C'est l'équivalent d'un namespace versionné.
PdoAdapter
new PdoAdapter(
$connOrDsn, // PDO|Connection|string DSN
$namespace = '',
$defaultLifetime = 0,
$options = [], // db_table, db_id_col, db_data_col, db_lifetime_col, db_time_col
$marshaller = null,
)
ProxyAdapter
// Envelopper un pool PSR-6 tiers pour lui ajouter CacheInterface
new ProxyAdapter($psr6Pool, $namespace = '', $defaultLifetime = 0)
FilesystemAdapter.
Tests → ArrayAdapter ou NullAdapter.
Prod mono-serveur → ApcuAdapter ou FilesystemAdapter.
Prod multi-serveur → RedisAdapter.
Besoin de tags → TagAwareAdapter(new RedisAdapter(...)).
Performance maximale → ChainAdapter([new ApcuAdapter(), new RedisAdapter()]).
CacheItem — TTL, tags et métadonnées
CacheItem est plus qu'une paire clé-valeur — il porte sa date d'expiration, ses tags et ses métadonnées de création.
Un CacheItem est l'objet que tu manipules dans le pattern PSR-6 ou dans le callback
de CacheInterface::get(). Il stocke la valeur, son expiration, ses tags et ses métadonnées.
Méthodes de base
$item->getKey(); // string — clé du cache
$item->get(); // mixed — valeur stockée (null si miss)
$item->isHit(); // bool — true si l'item existe et n'est pas expiré
$item->set($value); // static — définir la valeur
TTL — deux approches
expiresAt() — date absolue
$item->expiresAt(new \DateTime('+1 hour'));
$item->expiresAt(new \DateTimeImmutable('2026-12-31 23:59:59'));
$item->expiresAt(null); // expiration indéfinie (durée par défaut du pool)
expiresAfter() — durée relative
$item->expiresAfter(3600); // en secondes
$item->expiresAfter(new \DateInterval('PT1H')); // intervalle
$item->expiresAfter(null); // durée par défaut du pool
expiresAt() reçoit un \DateTimeInterface — la date d'expiration est absolue.
expiresAfter() reçoit un int (secondes) ou un \DateInterval —
la durée est calculée à partir du moment de l'appel. En pratique, expiresAfter() est
plus lisible pour les durées courantes.
Tags
// Ajouter un ou plusieurs tags (nécessite TagAwareAdapter)
$item->tag('user_42');
$item->tag(['user_42', 'profile', 'global']);
// Dans le callback CacheInterface :
$cache->get('user_42_profile', function (ItemInterface $item) {
$item->expiresAfter(3600);
$item->tag(['user_42', 'profile']);
return $this->userRepository->findProfile(42);
});
tag() sur un item obtenu depuis un adapter
non-tagaware lève une LogicException à la sauvegarde. Assure-toi que ton pool
est un TagAwareAdapter ou qu'il implémente TagAwareCacheInterface.
Métadonnées
$metadata = $item->getMetadata();
// Clés disponibles via les constantes ItemInterface :
$metadata[ItemInterface::METADATA_EXPIRY]; // float — timestamp d'expiration
$metadata[ItemInterface::METADATA_CTIME]; // int — timestamp de création (ms)
$metadata[ItemInterface::METADATA_TAGS]; // array — tags de l'item
// Ou via le 4e argument de CacheInterface::get() :
$cache->get('ma_cle', fn($item) => 42, null, $metadata);
// $metadata est maintenant rempli après l'appel
{}()/\@:Interdits dans les clés et dans les noms de tags. Toute clé contenant un de ces caractères lève une
InvalidArgumentException. Utilise des tirets, underscores et points :
user_42.profile, product-catalog-fr.
ChainAdapter — cache multi-niveaux
Le ChainAdapter empile plusieurs adapters — lecture du plus rapide au plus lent, propagation vers le haut.
Imagine un bibliothécaire avec deux niveaux de rangement : un présentoir en surface pour les
livres très demandés, et des rayonnages en sous-sol pour les autres. Si le livre est en surface,
tu l'as immédiatement. Sinon, il descend chercher — et au retour, il en met un exemplaire en
surface pour la prochaine fois. C'est exactement le ChainAdapter.
Constructeur
use Symfony\Component\Cache\Adapter\ChainAdapter;
use Symfony\Component\Cache\Adapter\ApcuAdapter;
use Symfony\Component\Cache\Adapter\RedisAdapter;
$cache = new ChainAdapter([
new ApcuAdapter('', 300), // niveau 0 : mémoire partagée PHP, 5 min
new RedisAdapter($redis, '', 3600), // niveau 1 : Redis, 1 h
], 300); // $defaultLifetime = durée de propagation vers niveaux supérieurs
1. Cherche dans
adapters[0] (APCu). Hit → retourne la valeur.2. Si miss → cherche dans
adapters[1] (Redis). Hit → propage dans APCu avec
$defaultLifetime → retourne la valeur.3. Si miss partout → le callback est appelé → sauvegarde dans tous les adapters.
Write-back et defaultLifetime
Quand un item est trouvé dans un adapter de niveau N, il est propagé dans tous les adapters
0 à N-1 avec le $defaultLifetime du ChainAdapter. Ce n'est pas la durée
de l'item original — c'est la durée de la copie dans les niveaux supérieurs.
Ne confonds pas ce paramètre avec le TTL de l'item dans le backend final (Redis). Il contrôle uniquement combien de temps la copie remontée dans APCu est conservée. Mettre 0 ici signifie que les copies propagées n'expirent jamais dans les niveaux supérieurs — ce qui peut créer des données périmées si Redis expulse l'item mais qu'APCu garde sa copie.
Avantages et limites
- Latence réduite sur les items chauds (APCu ≈ 1 µs vs Redis ≈ 1 ms)
- Résilience : si Redis est indisponible, les items en APCu continuent de servir
- Transparent pour le code consommateur
- Données potentiellement désynchronisées entre niveaux pendant
$defaultLifetime - L'invalidation sur un seul niveau ne propage pas aux autres
- APCu est local à chaque worker PHP — cohérence inter-serveurs impossible
ChainAdapter en YAML
# config/packages/cache.yaml
framework:
cache:
pools:
cache.system:
adapter: cache.adapter.chain
adapters:
- cache.adapter.apcu
- cache.adapter.redis
default_lifetime: 300
ChainAdapter ne supporte pas les tags nativement.
Pour un cache multi-niveaux avec tags, enveloppe chaque adapter dans un TagAwareAdapter
avant de les passer au ChainAdapter.
Tags et invalidation
Les tags permettent d'invalider un groupe d'items en une seule opération — sans connaître leurs clés.
Imagine que tu as mis en cache le profil de l'utilisateur 42 sous des dizaines de clés différentes : photo de profil, préférences, panier, historique… Quand il change son email, tu dois invalider tout ça. Avec les clés, c'est impossible à tenir. Avec les tags, c'est une ligne.
TagAwareCacheInterface
use Symfony\Contracts\Cache\TagAwareCacheInterface;
// Invalider tous les items taggés 'user_42'
$cache->invalidateTags(['user_42']); // bool
// Invalider plusieurs tags d'un coup
$cache->invalidateTags(['user_42', 'products']);
TagAwareAdapter
use Symfony\Component\Cache\Adapter\TagAwareAdapter;
use Symfony\Component\Cache\Adapter\RedisAdapter;
// itemsPool + tagsPool séparé (recommandé en prod)
$cache = new TagAwareAdapter(
new RedisAdapter($redis), // pool des items
new RedisAdapter($redis, 'tags'), // pool des versions de tags
0.15 // knownTagVersionsTtl en secondes
);
// Écriture avec tags :
$cache->get('user_42_profile', function (ItemInterface $item) {
$item->expiresAfter(3600);
$item->tag(['user_42', 'profile']);
return $this->userRepository->findProfile(42);
});
Fonctionnement interne
Chaque tag a une clé de version stockée dans le $tagsPool.
Quand un item est écrit, ses versions de tags sont stockées avec lui.
À la lecture, Symfony compare les versions stockées dans l'item avec les versions actuelles des tags.
Si une version ne correspond plus, l'item est considéré comme invalide (miss).
Symfony supprime les clés de version dans le
$tagsPool. Lors de la prochaine lecture
d'un item taggé, la comparaison de versions échoue → l'item est recomputé et les nouvelles versions
sont stockées. L'item "invalide" n'est pas supprimé physiquement — il est ignoré jusqu'à ce qu'il
soit remplacé ou qu'il expire.
Pour éviter un aller-retour Redis à chaque appel de
get(), Symfony met en cache les
versions de tags en mémoire locale pendant knownTagVersionsTtl secondes. Si tu invalides
un tag, les workers qui ont une version en cache local peuvent servir l'ancien item pendant au
maximum 0.15 secondes. C'est un compromis performance/cohérence acceptable dans la plupart des cas.
Lance la simulation ci-dessous pour voir l'invalidation par tags en action.
Tape un tag dans le champ et clique sur invalidateTags.
Early expiration et beta
L'early expiration probabiliste recalcule le cache avant qu'il expire — c'est la protection contre le "cache stampede".
Imagine une page très populaire dont le cache expire à minuit pile. À minuit, 500 workers arrivent simultanément, voient tous un miss, et lancent tous le même calcul lourd en parallèle. C'est le cache stampede. L'early expiration probabiliste évite ça.
L'algorithme XFetch
Plutôt que d'attendre l'expiration réelle, chaque worker calcule probabilistiquement s'il doit regénérer le cache avant qu'il expire. La probabilité augmente exponentiellement à mesure qu'on approche de l'expiration.
now - delta * beta * log(rand(0,1)) ≥ expirySi cette condition est vraie, le worker regénère.
delta est le temps de calcul
estimé. beta contrôle l'agressivité de la recomputation précoce.
Le paramètre beta
// beta = 0 : jamais de recomputation précoce (stampede possible)
$cache->get('expensive_data', fn($item) => compute(), 0);
// beta = 1.0 (défaut) : protection optimale pour la plupart des cas
$cache->get('expensive_data', fn($item) => compute());
// beta = INF : force la recomputation immédiate (tests uniquement)
$cache->get('expensive_data', fn($item) => compute(), INF);
| beta | Comportement | Cas d'usage |
|---|---|---|
0 |
Pas de recomputation précoce. L'item expire à sa date exacte. | Données peu coûteuses à recalculer |
1.0 (défaut) |
Protection optimale. Un seul worker recompute avant expiration. | La grande majorité des cas |
INF |
Force la recomputation à chaque accès. Ignore le cache. | Tests, débogage |
Forcer un miss avec $save = false
Dans le callback, le second paramètre optionnel bool &$save permet de calculer
une valeur sans la mettre en cache :
$data = $cache->get('api_result', function (ItemInterface $item, bool &$save) {
try {
$result = $this->apiClient->fetch();
$item->expiresAfter(300);
return $result;
} catch (\Exception $e) {
$save = false; // erreur réseau : ne pas cacher le résultat
return $this->fallback();
}
});
expiresAfter ou expiresAt). Sur un item sans TTL, beta est
ignoré. Pour des données populaires et coûteuses à calculer, c'est une protection passive
qui ne demande aucune infrastructure supplémentaire — il suffit de ne pas passer beta=0.
saveDeferred() et commit()
saveDeferred() diffère la sauvegarde pour les opérations batch — commit() les envoie d'un coup.
Dans une boucle qui met en cache 500 items, faire un aller-retour Redis par item = 500 round-trips.
saveDeferred() accumule les items à sauvegarder, et commit() les envoie
en un seul pipeline. De 500 requêtes réseau à 1 — voilà l'intérêt.
Pattern deferred
// PSR-6 — saveDeferred + commit
foreach ($products as $product) {
$item = $pool->getItem('product_' . $product->getId());
if (!$item->isHit()) {
$item->set($product->toArray());
$item->expiresAfter(3600);
$pool->saveDeferred($item); // accumule, ne sauvegarde pas encore
}
}
$pool->commit(); // envoie tout en un seul pipeline
save(CacheItemInterface $item): bool — sauvegarde immédiate, un aller-retour par item.saveDeferred(CacheItemInterface $item): bool — accumule en mémoire sans écriture réseau.commit(): bool — envoie tous les items deferred en une seule opération batch.
commit() automatique dans __destruct()
Les adapters Symfony appellent commit() dans leur destructeur. Si ton script se
termine normalement, les items deferred sont sauvegardés même si tu n'appelles pas commit()
explicitement. C'est une protection, pas une invitation à l'oubli.
saveDeferred() et
commit(), et que l'exception remonte jusqu'au destructeur avant l'appel, les items
peuvent être perdus silencieusement selon le contexte d'exécution (FPM vs CLI vs long-running
process). Appelle toujours commit() explicitement dans les opérations critiques.
Cas Redis — pipeline
Pour Redis, commit() utilise le pipelining : les commandes
SET sont empilées localement et envoyées en un seul paquet TCP. La latence d'un
SET x 500 avec pipeline est comparable à celle d'un seul SET.
// Équivalent Redis interne :
// PIPELINE
// SET product_1 "..." EX 3600
// SET product_2 "..." EX 3600
// ... (500 SET)
// EXEC
// → 1 seul aller-retour réseau
saveDeferred n'est disponible que dans l'interface
PSR-6 (CacheItemPoolInterface). L'interface Symfony CacheInterface
utilise le pattern callback et gère l'optimisation en interne — tu n'as pas accès à
saveDeferred via elle.
Configuration Symfony (cache.yaml)
La configuration Symfony crée des pools nommés injectables — chaque pool est un service.
Plutôt que d'instancier les adapters à la main, Symfony crée des pools nommés à partir de la
configuration YAML. Chaque pool est un service injectable, tagué cache.pool.
Configuration de base
# config/packages/cache.yaml
framework:
cache:
default_redis_provider: '%env(REDIS_DSN)%'
pools:
cache.app:
adapter: cache.adapter.filesystem
cache.redis:
adapter: cache.adapter.redis
provider: '%env(REDIS_DSN)%'
default_lifetime: 3600
cache.tagged:
adapter: cache.adapter.redis
tags: true # → TagAwareAdapter automatique
cache.multi:
adapter: cache.adapter.chain
adapters:
- cache.adapter.apcu
- cache.adapter.redis
default_lifetime: 300
DSN Redis
| Format DSN | Description |
|---|---|
redis://localhost | Redis local, port 6379 par défaut |
redis://user:pass@host:6380 | Avec authentification |
rediss://host:6380 | TLS (redis over SSL) |
redis:?host[h1]&host[h2] | Cluster Redis |
Injection dans un service
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\TagAwareCacheInterface;
public function __construct(
// Pool spécifique par ID de service
#[Autowire(service: 'cache.redis')]
private readonly CacheInterface $cache,
// Pool tagaware
#[Autowire(service: 'cache.tagged')]
private readonly TagAwareCacheInterface $taggedCache,
) {}
Quand un pool est déclaré avec
tags: true, Symfony le configure automatiquement
comme un TagAwareAdapter. Le service implémente alors TagAwareCacheInterface
et peut être injecté avec ce type.
cache.system destiné aux
données stables du framework (annotations, routes compilées…). Il n'est pas vidé par
cache:clear — seulement par cache:pool:clear cache.system. Ne l'utilise
pas pour des données applicatives changeantes.
php bin/console cache:pool:list — lister tous les pools déclarésphp bin/console cache:pool:clear cache.redis — vider un pool spécifiquephp bin/console cache:pool:prune — supprimer les items expirés (FilesystemAdapter)
Marshaller et namespace
Le marshaller contrôle la sérialisation des données en cache — le namespace isole les pools partageant le même backend.
Deux mécanismes orthogonaux contrôlent la façon dont les données sont stockées dans le backend : le marshaller sérialise (et éventuellement chiffre) les valeurs avant écriture, le namespace préfixe les clés pour isoler les pools partageant le même serveur.
MarshallerInterface
interface MarshallerInterface
{
/** @param mixed[] $values @param string[]|null &$failed */
public function marshall(array $values, ?array &$failed): array;
public function unmarshall(string $value): mixed;
}
DefaultMarshaller
Utilisé par défaut. Il sérialise avec serialize() / unserialize().
Si l'extension igbinary est disponible, elle est utilisée automatiquement à la place —
plus compact et plus rapide.
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
new RedisAdapter(
$redis,
$namespace = '',
$lifetime = 0,
$marshaller = new DefaultMarshaller(useIgbinary: true), // force igbinary
)
SodiumMarshaller — données chiffrées au repos
use Symfony\Component\Cache\Marshaller\SodiumMarshaller;
$marshaller = new SodiumMarshaller(
[sodium_crypto_box_keypair()], // tableau de clés (rotation possible)
new DefaultMarshaller(),
);
new RedisAdapter($redis, '', 0, $marshaller);
Chiffre les données avec libsodium avant de les écrire dans Redis. Utile quand le backend est partagé entre plusieurs applications ou accessible à des tiers. Le tableau de clés permet la rotation : la première clé sert à chiffrer, toutes les clés servent à déchiffrer.
Namespace — isoler les pools
Le $namespace est un préfixe inséré dans chaque clé avant l'écriture dans le backend.
Deux pools Redis avec des namespaces différents peuvent coexister sur le même serveur sans collision.
// Pool "orders" et pool "catalog" sur le même Redis
new RedisAdapter($redis, 'orders', 3600);
new RedisAdapter($redis, 'catalog', 7200);
// La clé Redis réelle sera du type :
// orders:Ab3xK9...:product_42
// catalog:Zf7mQ2...:product_42
clear() avec namespace
$cache->clear(); // vide UNIQUEMENT les clés du namespace de ce pool
// les autres namespaces sur le même Redis ne sont pas affectés
APCu ne supporte pas
clear() par préfixe. Le paramètre $version est
intégré dans les clés : changer la version crée de nouvelles clés (les anciennes expirent
naturellement). C'est l'invalidation de namespace sans suppression physique.
cache.redis et
cache.tagged pointant sur le même Redis auront des namespaces auto-générés
différents — pas de collision même si tu n'en configures pas.
Synthèse — le guide de choix
Le guide de choix des adapters et les anti-patterns du cache.
Le composant Cache de Symfony est riche mais cohérent. Si tu sais quel problème tu résous, le bon outil est évident. Voici le guide de choix.
Guide de choix des adapters
| Contexte | Adapter recommandé | Raison |
|---|---|---|
| Développement local | FilesystemAdapter |
Pas de dépendance externe, persistant entre requêtes |
| Tests unitaires | ArrayAdapter ou NullAdapter |
Array = comportement réaliste, Null = désactive le cache |
| Prod mono-serveur (PHP process partagés) | ApcuAdapter |
Mémoire partagée entre workers FPM, latence microseconde |
| Prod multi-serveur | RedisAdapter |
Cache centralisé, cohérence inter-serveurs |
| Prod multi-serveur + tags | TagAwareAdapter(RedisAdapter) |
Invalidation par tags requiert Redis |
| Performance maximale | ChainAdapter([ApcuAdapter, RedisAdapter]) |
APCu pour les hits chauds, Redis pour la cohérence |
| Données sensibles au repos | RedisAdapter + SodiumMarshaller |
Chiffrement avant écriture dans Redis |
| Seule la BDD est disponible | PdoAdapter |
Stockage SQL, moins performant mais sans infra supplémentaire |
Anti-patterns
Si tu n'appelles jamais
delete() ni invalidateTags(), les données
périmées restent indéfiniment. Mets toujours un TTL réaliste — ou implémente une invalidation
explicite à la mutation des données.
Stocker
user_42 sans namespace quand plusieurs tenants partagent le même Redis
crée des fuites de données entre tenants. Utilise toujours un namespace ou préfixe tenant dans
la clé : tenant_A.user_42.
Vider tout le cache d'un coup crée un stampede massif. Préfère l'invalidation par tags ou par pool spécifique (
cache:pool:clear cache.catalog). En dernier recours, utilise
un warm-up de cache après le clear.
Désactiver l'early expiration sur une page calculée en 2 secondes avec 1000 req/s à l'expiration, c'est garantir 2000 requêtes simultanées vers la BDD. Garde beta = 1.0 par défaut.
Récapitulatif des interfaces
| Interface | Méthodes clés | Quand l'utiliser |
|---|---|---|
CacheItemPoolInterface (PSR-6) |
getItem / save / saveDeferred / commit |
Batch, contrôle fin sur les items, interopérabilité |
CacheInterface (PSR-16) |
get(key, default) / set(key, val, ttl) |
API simple, pas de tags, cas basiques |
CacheInterface (Symfony) |
get(key, callback, beta) / delete |
Pattern callback, stampede protection, tags |
TagAwareCacheInterface |
hérite Symfony CacheInterface + invalidateTags |
Invalidation groupée sans connaître les clés |