Tagged Cache
setWithTags() and invalidateTags() — group related cache entries and invalidate them all at once without knowing individual keys.
Tagged Cache
Tags group related cache entries so they can all be invalidated together. When a user updates their profile, you invalidate ['user:123'] — every cache entry tagged with that value is gone, atomically, without you needing to track what keys exist.
TaggedCacheInterface
interface TaggedCacheInterface extends CacheInterface
{
public function setWithTags(string $key, mixed $value, array $tags, ?int $ttl = null): bool;
public function invalidateTags(array $tags): bool;
}Extends PSR-16 CacheInterface — all standard get, set, delete, has, clear methods are available too.
setWithTags
use Vortos\Cache\Contract\TaggedCacheInterface;
// Store with one tag
$cache->setWithTags('user:123:profile', $profile, ['user:123'], 3600);
// Store with multiple tags — key is in ALL tag indices
$cache->setWithTags('user:123:permissions', $perms, ['user:123', 'permissions'], 3600);
// TTL null = use configured defaultTtl
$cache->setWithTags('config:theme', $theme, ['config'], null);invalidateTags
// Invalidate everything tagged with user:123
$cache->invalidateTags(['user:123']);
// Invalidate multiple tags at once
$cache->invalidateTags(['user:123', 'permissions']);
// Safe to call with tags that have no entries — returns true
$cache->invalidateTags(['nonexistent_tag']);Practical Examples
User Profile Cache
final class UserProfileService
{
public function __construct(private TaggedCacheInterface $cache) {}
public function getProfile(string $userId): array
{
$key = "user:{$userId}:profile";
if ($this->cache->has($key)) {
return $this->cache->get($key);
}
$profile = $this->loadFromDb($userId);
// Tag with user ID — invalidated when user data changes
$this->cache->setWithTags($key, $profile, ["user:{$userId}"], 3600);
return $profile;
}
public function onProfileUpdated(string $userId): void
{
// Invalidates all cache entries tagged with this user
$this->cache->invalidateTags(["user:{$userId}"]);
}
}Permission Matrix Cache
final class PermissionService
{
public function getMatrix(string $tenantId): array
{
$key = "permissions:matrix:{$tenantId}";
if ($this->cache->has($key)) {
return $this->cache->get($key);
}
$matrix = $this->loadPermissionsFromDb($tenantId);
$this->cache->setWithTags(
$key,
$matrix,
["permissions", "tenant:{$tenantId}"],
3600,
);
return $matrix;
}
public function onRoleChanged(string $tenantId): void
{
// Invalidates permission matrix for this tenant only
$this->cache->invalidateTags(["tenant:{$tenantId}"]);
}
public function onGlobalPermissionsChanged(): void
{
// Invalidates ALL permission matrices across all tenants
$this->cache->invalidateTags(['permissions']);
}
}Multi-Layer Tags
A single cache entry can have multiple tags at different granularities:
// Tags: specific user + general category
$cache->setWithTags(
"user:{$userId}:orders:recent",
$orders,
["user:{$userId}", "orders", "tenant:{$tenantId}"],
1800,
);
// Invalidate just this user's data
$cache->invalidateTags(["user:{$userId}"]);
// Invalidate all orders cache (e.g. after order schema migration)
$cache->invalidateTags(["orders"]);
// Invalidate entire tenant's cache
$cache->invalidateTags(["tenant:{$tenantId}"]);Standard set() vs setWithTags()
Use set() for data with no related invalidation needs. Use setWithTags() when the data must be invalidated when a related entity changes:
// No invalidation needed — config never changes mid-deploy
$cache->set('app:version', '1.2.3', 86400);
// Invalidation needed — user data changes
$cache->setWithTags('user:123:settings', $settings, ['user:123'], 3600);set() and get() are slightly faster than setWithTags() — no tag index write. For high-frequency keys with no invalidation requirement, use set().
Tag TTL in Redis
In RedisAdapter, each tag's index SET has a 7-day TTL. This means if a tagged key naturally expires before invalidation, the tag index entry remains for up to 7 days. When invalidateTags() is called, it may try to delete already-expired keys — this is safe and a no-op in Redis.