Cache Management
Overview
ContainerCacheInterface lets a container persist its declarative bindings and aliases so a fresh container in a later process (a new PHP-FPM worker, a new CLI invocation, …) can restore them before anything is resolved with make() or get(). This is a resolution definition cache, not a resolved-object cache: it never stores closures, instances, or resolved values — only class-string bindings and the aliases that point at them, because only those can be safely reconstructed in a different process.
What Gets Cached
Only bindings registered with a concrete class string are cacheable:
// Cacheable — registered with a class-string concrete.
$container->bind(LoggerInterface::class, FileLogger::class);
// Not cacheable — registered with a closure or a pre-built instance.
$container->bind(LoggerInterface::class, fn () => new FileLogger());
$container->instance(LoggerInterface::class, new FileLogger());
Aliases are persisted only when their target is represented by a cacheable declarative binding or is an existing class that can be resolved on its own. An alias whose target exists only in the runtime instance map is not persisted. Anything else is silently excluded from the persisted definition set — the cache never fails a write because of an uncacheable entry.
Implementation Details
ContainerCacheInterface
The contract for an external cache store:
| Method | Signature | Description |
|---|---|---|
| get | get(string $key): mixed | Retrieves a value from the cache. |
| set | set(string $key, mixed $value, int $ttl = 3600): bool | Stores a value. A TTL of 0 means the adapter must not expire it. |
| has | has(string $key): bool | Checks whether a value exists for the key. |
| delete | delete(string $key): bool | Removes a value from the cache. |
The definitions are always stored under the interface's own key, ContainerCacheInterface::DEFINITION_CACHE_KEY, with ttl = 0 (never expire) — you don't choose the key or TTL yourself.
InMemoryContainerCache
A simple in-memory implementation of ContainerCacheInterface backed by a PHP array. It's useful for tests or single-process usage, but it doesn't persist across processes — for real cross-process reuse, implement ContainerCacheInterface against Redis, APCu, a file, or whatever your application already uses.
CacheManagerTrait
The trait that wires the container up to an external cache:
setExternalCache(ContainerCacheInterface $cacheStore)— attaches the cache and immediately attempts to hydrate the container from it (see below).clearResolutionCache()— deletes the persisted definitions from the external cache. This does not touch the current container's own registrations or retained instances.
Persisting and Hydrating Definitions
Every time you call bind(), instance(), or alias(), the container automatically persists the current set of cacheable bindings and aliases to the external cache (if one is attached) — you never call a "save" method yourself.
Hydration only happens once, right when setExternalCache() is called, and only into an empty container (no existing bindings, instances, or aliases). If validation of the cached payload fails for any reason — wrong shape, a referenced class that no longer exists, anything unexpected — the cache is simply ignored and the container starts empty, rather than throwing.
// Process A: build up bindings, definitions are persisted automatically.
$cache = new RedisContainerCache(/* ... */);
$container = new Container();
$container->setExternalCache($cache);
$container->bind(LoggerInterface::class, FileLogger::class);
$container->alias(LoggerInterface::class, 'logger');
// Process B (a fresh request/worker): the same definitions come back
// automatically, before anything is resolved.
$cache = new RedisContainerCache(/* ... */);
$container = new Container();
$container->setExternalCache($cache);
$logger = $container->get('logger'); // Resolves FileLogger, no rebinding needed.
Clearing Cached Definitions
// Remove only the externally persisted definitions.
// The container's own current bindings are untouched.
$container->clearResolutionCache();
// Clear everything: local bindings, instances, scopes, hooks, tags,
// contextual/union-type configuration, reflection cache, AND the
// external cache's persisted definitions.
$container->resetContainer();
Legacy Resolved-Service Cache API
cacheResolvedService(), cacheResolvedServices(), clearResolvedServicesCache(), and loadResolvedServicesFromExternalCache() remain available for backwards compatibility but are deprecated since 0.2.0. Unlike the definition cache above, they can store arbitrary resolved values and are therefore not part of the safe declarative-definition cache contract.
Migrate away from them:
- For process-local reuse of a resolved object, use a shared binding (
singleton()) instead of manually caching the resolved instance. - For persisting application data across requests, use your application's own cache layer —
ContainerCacheInterfaceis reserved for declarative definitions only.
Security Note
Cache adapters attached via setExternalCache() are trusted configuration stores. A cache entry an attacker could write to is effectively a way to change which concrete class gets instantiated for an abstract — protect it exactly as you would the application's own binding configuration.
Summary
The DomainFlow Container's cache management persists declarative class-string bindings and aliases — never closures, instances, or resolved values — so a cold container can safely restore its configuration in a new process before any resolution happens. setExternalCache() hydrates once into an empty container; every subsequent bind(), instance(), or alias() call keeps the persisted definitions in sync automatically. The older resolved-service cache methods still work but are deprecated in favor of shared bindings and your application's own cache.