winzou/cache-bundle
A Symfony bundle that provides a simple, unified cache service with support for multiple backends such as APC, Memcache, Redis, and filesystem. It offers an easy-to-use interface for storing and retrieving cached data with configurable lifetimes.
This bundle is a thin Symfony 2 wrapper around Zend\Cache (ZF2). It does not implement caching itself — it gives you configured Zend\Cache\Storage\StorageInterface instances as services so you don't hand-build adapters in every project. Treat it as a configuration/DI convenience layer, not a framework feature.
Requirements to verify before anything else (read composer.json in the repo, not the README):
AppKernel, bundle registration).zendframework/zend-cache ~2.x. The later laminas/laminas-cache rename is not compatible; neither is Symfony 3+ without changes.apc, xcache, file, zenddata, array) are mostly legacy: XCache is gone on PHP 7+, the APC adapter targets the old APC extension (not modern APCu), and ZendData requires Zend Server. On a working stack, expect to use file (persistent) and array (per-request/tests).Install and register:
composer require winzou/cache-bundle
// app/AppKernel.php
public function registerBundles()
{
$bundles = array(
// ...
new Winzou\CacheBundle\WinzouCacheBundle(),
);
}
Configure one instance per logical cache, not one global cache. The real source of truth for keys is DependencyInjection/Configuration.php; open it first — the README lags the code.
# app/config/config.yml
winzou_cache:
instances:
app_data:
type: file
options:
cache_dir: %kernel.cache_dir%/winzou/app_data
namespace: myapp_data
ttl: 3600
First use case — cache an expensive repository call:
$cache = $this->get('winzou_cache')->get('app_data');
// Depending on the version, instances may also be exposed directly as
// 'winzou_cache.app_data' — check Resources/config/services.xml.
$key = 'user.'.$id.'.profile';
if (null === ($profile = $cache->getItem($key, $success)) || !$success) {
$profile = $profileRepository->loadProfile($id);
$cache->setItem($key, $profile);
}
If winzou_cache does not expose get() in your version, inject winzou_cache.app_data directly.
Inject the manager, resolve names once. In a service, take the cache manager (or the concrete instance service) as a constructor argument and fetch the named instance in the constructor. Avoid $container->get() inside domain code — the bundle's service ids become a framework detail you can't later swap.
Wrap it in your own typed cache. Never let setItem/getItem calls spread across the codebase. One small class per domain area gives you typed methods and a single place to migrate to symfony/cache later:
class ProfileCache
{
private $cache;
public function __construct(\Zend\Cache\Storage\StorageInterface $cache)
{
$this->cache = $cache;
}
public function get($id, callable $fallback)
{
$key = 'v3.user.'.$id.'.profile';
if (null !== ($value = $this->cache->getItem($key, $success)) && $success) {
return $value;
}
$value = $fallback();
$this->cache->setItem($key, $value);
return $value;
}
public function invalidate($id)
{
$this->cache->removeItem('v3.user.'.$id.'.profile');
}
}
The callable $fallback cache-aside signature is the single most valuable pattern here — it removes the repeated get/miss/set boilerplate that causes most caching bugs.
Version your keys, don't flush. Prefix keys with v1., v2., etc. A schema change means bumping the prefix in one constant; old entries expire naturally. This is much safer than calling flush(), especially on shared backends.
Namespace every instance. Set an explicit namespace option per instance. Two apps or two instances sharing the same APC/memcached/file backend will otherwise collide under Zend's default namespace.
Use the array adapter for tests. Override the instance's type to array in config_test.yml. It is in-process only, so it gives you deterministic behavior without clearing real caches between tests.
Separate data caches from Symfony's caches deliberately. If cache_dir lives under %kernel.cache_dir%, cache:clear wipes your data along with Symfony's. That's often desirable in dev; in production put it elsewhere and add an explicit clear step to your deploy. Either way, decide on purpose.
Warm hot keys at deploy time. A small console command that calls your ProfileCache::get() for the top N ids (or triggers your normal code path) turns cold-cache latency into a deploy cost. Run it after the clear step.
Invalidate on write, not on a timer. In the repository/service that updates the entity, call invalidate($id). TTLs are a backstop, not an invalidation strategy, unless stale reads are acceptable for that data.
Fallback for unsupported backends. If you need Redis or Memcached and the bundle doesn't expose it, don't patch the bundle. Define your own Zend\Cache\Storage\StorageInterface service in your app's DI and inject it into your wrapper — the wrapper is the contract, not the bundle.
null look identical. getItem() returns null on miss; use the &$success by-reference parameter (or hasItem()) if you ever cache nulls or falsy values. This is the most common silent bug with Zend\Cache.key_pattern and throw on illegal characters. Separators like : may be rejected — use . or _, or relax key_pattern in the options. This bites everyone once.md5($raw)) rather than storing raw composite strings.ttl: 0 means "never expires" in Zend\Cache. Omitting TTL entirely on a file/APC instance means stale entries accumulate forever. Always set a TTL or an explicit invalidation path.setItem($key, $value) signature. TTL is typically configured at the adapter/instance level (ttl option or getOptions()->setTtl()). Verify support for your zend-cache version before assuming a per-call TTL works — silently falling back to the instance default is the failure mode.flush() on a shared backend can delete other applications' keys, depending on adapter and namespace support. Prefer removeItem() per key, or namespace-scoped clearing if your adapter supports it. Test this before shipping.file, and write your wrapper so a backend swap is one constructor change.array adapter to isolate cache logic from backend issues; log key, hit/miss, and fallback duration in dev via your wrapper (or a decorating service); inspect files directly in cache_dir; use getMetadata() to confirm TTL and expiration on a suspect key.cache:clear scope confusion — if your cache dir is under %kernel.cache_dir%, it's gone after every clear; if it's outside, nothing clears it and you must do it yourself on deploy. Pick one and document it.symfony/cache (PSR-6/PSR-16, cache.adapter.filesystem|apcu|redis) is a one-file change. For any new project, start with symfony/cache directly — there is no reason to adopt this bundle today.How can I help you explore Laravel packages today?