phpfastcache/phpfastcache-bundle
Symfony bundle that integrates PhpFastCache into your application, letting you configure and inject cache services backed by files, Redis, Memcached, APCu, and other drivers through standard Symfony configuration. A lightweight way to add flexible, multi-backend caching.
Re-baseline your assumption about this bundle. The previous “archived, Symfony 3-only” framing is now out of date: v3.0.0 was published and it explicitly targets Symfony 4 (Flex) and PhpFastCache v7. That said, Symfony 4 is itself EOL and there is still no Symfony 5/6/7 support, so this remains a Symfony 4-era package rather than a modern-first choice. If you are on Symfony 4, v3 is a plausible upgrade; if you are starting fresh on a supported Symfony, still prefer symfony/cache.
If you are upgrading from v2, read the migration guide first. It ships at src/Resources/Docs/migration/MigratingFromV2ToV3.md. v3 contains three breaking changes that affect day-to-day code:
phpfastcache service is now private — you can no longer pull it from the container ($container->get('phpfastcache') / $this->get(...)); you must inject it.PhpFastCacheBundle\... references before and after the upgrade; the migration guide lists the mappings.getItem/cache pool APIs, and TTL edge-case behavior changed between v5/v6 and v7. Do not assume your v2 driver blocks still parse.Install / upgrade
composer require phpfastcache/phpfastcache-bundle:^3.0
The Symfony 4 Flex recipe should now register the bundle automatically in config/bundles.php. For non-Flex (legacy AppKernel), add the renamed bundle class manually.
Re-check the config root — do not assume it survived the namespace change. Run:
bin/console config:dump-reference php_fast_cache
A minimal files-driver setup still looks roughly like:
# config/packages/php_fast_cache.yaml
php_fast_cache:
driver: files
path: '%kernel.cache_dir%/phpfastcache'
Confirm the now-private service is injectable. Because the service is private, the fastest sanity check is:
bin/console debug:container phpfastcache
If autowiring by type is not resolving it in your services, alias the bundle’s type to the phpfastcache service id in config/services.yaml. Then inject it:
class ReportService
{
public function __construct(private ExtendedCacheItemPoolInterface $cache) {}
public function getMonthlyTotals(int $year): array
{
$key = sprintf('myapp.%s.v2.reports.monthly.%d', '%kernel.environment%', $year);
$item = $this->cache->getItem($key);
if (!$item->isHit()) {
$item->set($this->computeExpensiveTotals($year));
$item->expiresAfter(3600);
$this->cache->save($item);
}
return $item->get();
}
}
Validate the round-trip. In dev, the improved DataCollector now surfaces more detail in the profiler cache panel. Make a request, confirm a miss, make the same request again, confirm a hit. Then use the new CRUD-like commands (see the README’s “CLI command interactions” section — bin/console list to discover the exact verb names) to inspect and clear entries directly. That combination — profiler hit/miss, then CLI inspection — is the quickest way to prove the wiring end to end.
Injection is now mandatory, not just preferred. In v2 the “prefer DI over the static facade” advice was optional hygiene. In v3 the service is private, so any $container→get('phpfastcache') in a controller, command, or service factory is a hard break after upgrade. Convert those call sites to constructor injection. Keep refusing the static phpFastCache::getInstance() path — it creates a second driver instance pointing at the same backend and bypasses the container and profiler.
Wrap it in your own cache service. The bundle still gives you one generic pool. Wrap it so TTL defaults, key prefixes, and serialization policy live in one place:
final class AppCache
{
public function __construct(private ExtendedCacheItemPoolInterface $phpFastCache) {}
public function remember(string $key, int $ttl, callable $fn): mixed
{
$item = $this->phpFastCache->getItem($key);
if ($item->isHit()) {
return $item->get();
}
$value = $fn();
$item->set($value)->expiresAfter($ttl);
$this->phpFastCache->save($item);
return $value;
}
}
This wrapper is also your migration seam: when you eventually move to symfony/cache, you swap the injected pool here and leave call sites alone.
Twig fragment caching. The {% cache %} tag is still the bundle’s template-facing selling point:
{% cache 'myapp.prod.v2.sidebar.popular' 600 %}
{% include 'sidebar/popular.html.twig' %}
{% endcache %}
Use it for read-heavy, non-personalized fragments. The tag key is global — fold the user ID into the key or avoid it for authenticated output.
Cacheable Responses (v3-only). v3 adds a “Cacheable Response” feature (see the README’s “introducing cacheable responses” section). This lets you mark controller actions / responses so the bundle can store and serve the full HTTP response, which is a better fit than fragment caching for anonymous, read-heavy pages such as public landing pages, category listings, and product detail pages. Use it for content that is identical for all (or almost all) visitors; if the response varies by user, session, auth, or query string, either avoid it or ensure your cache key includes the varying dimensions. Do not layer it over a response that already sets user-specific Vary/cookie behavior without testing.
Environment-specific drivers. Same pattern as before: files in dev (inspectable under var/cache), Redis/APCu in prod, config split per environment, one shared key prefix scheme:
# config/packages/prod/php_fast_cache.yaml
php_fast_cache:
driver: redis
redis:
host: '%env(REDIS_HOST)%'
After the PhpFastCache v7 upgrade, re-validate these driver blocks against the v7 driver config — key names changed between major versions.
Use the CLI commands for operations, not just debugging. The new CRUD-like commands give you a supported way to inspect and evict entries from a shell or deploy script. That solves a long-standing operational gap from v2 (where you were on your own). Wire the clear command into your deploy/release step for prod drivers, and use the inspection command when debugging unexpected misses.
Profiler as the dev feedback loop. The DataCollector now reports more cache detail. Treat unexpected misses as bugs — usually a key that varies per request (timestamps, session IDs) or a TTL of 0.
Version/namespace your keys. Because the backend is shared, prefix with app name, environment, and a schema version (myapp.prod.v2.reports.monthly.2024). Bumping the version is still the simplest way to invalidate a whole family when a serialized payload shape changes.
Invalidation via version bumping still stands. PhpFastCache still has no tag-based invalidation. Keep embedding an invalidation token (a reports_version integer read from config/DB and incremented on deploy or data change) in keys.
v2→v3 upgrade checklist. (a) Search for the old bundle namespace and update it — the v3 rename is not just cosmetic; class imports will fail to autoload. (b) Any code that fetched phpfastcache from the container must become constructor injection or an explicit alias (see Getting Started). (c) Re-check every driver config block against PhpFastCache v7; driver option names and some pool APIs changed. (d) Follow MigratingFromV2ToV3.md end to end — do not improvise the migration.
Service is private — the classic failure mode. After upgrading, $container->get('phpfastcache') (or $this->get('phpfastcache') in a controller) returns a “service not found / not public” error. Fix by injecting the pool type into your service, or by adding an alias in services.yaml. Verify with bin/console debug:container phpfastcache.
Static vs service instances still bite. If reads that should hit return misses, check whether some code path still calls phpFastCache::getInstance() with different credentials or a different path than the container-managed pool. The two can point at different storage. v3 makes the container path the only sanctioned one, so eliminate the static calls.
cache:clear still does not clear your cache. Symfony’s warmer/clearer knows nothing about this bundle’s storage. If path is inside %kernel.cache_dir% you get it for free; otherwise (or on Redis), use the bundle’s new clear command or a deploy step. The new CLI commands are the supported mechanism here — prefer them over hand-deleting files.
Symfony 4 is EOL — plan the exit. v3’s headline feature is Symfony 4 Flex compatibility, but Symfony 4 is no longer supported upstream. Treat v3 as a way to stay stable on an existing Symfony 4 app, not as a stepping stone to Symfony 6/7. Keep the bundle isolated behind your AppCache interface so the eventual swap to cache.app/cache.adapter.redis (PSR-6/PSR-16, tag invalidation on supported adapters, native cache:clear integration, first-party profiler panel) is mechanical.
PhpFastCache v7 API drift. v7 changed more than the version number. If code compiles but cache reads fail unexpectedly, compare your getItem/save/expiresAfter usage against the v7 driver documentation and the migration guide before assuming a bundle bug. The MigratingFromV2ToV3.md file is the authoritative reference for the pool-level changes.
TTL units and zero. TTLs are still seconds. A 0 or missing expiresAfter can mean “never expires” rather than “expire immediately,” and v7 may differ from v5/v6 at the boundary — set TTLs explicitly and test the 0 case on your target driver.
Serialization constraints with the files driver. Caching closures, resources, or objects with unserializable internals fails on read. Cache plain arrays/scalars or ensure DTOs serialize cleanly. Redis/Memcached have the same constraint; a driver swap changes the error message, not the rule.
Key collisions across apps. If multiple apps share one Redis/Memcached instance, unnamespaced keys collide. Always prefix with app + environment.
No stampede protection. Concurrent requests on a cold key still all compute. For expensive keys, add a short Symfony Lock or accept the thundering herd on rollover. v3 does not add a locking layer.
Driver availability degrades silently. APCu/Redis/Memcached drivers need the PHP extension and a reachable server; a misconfigured prod driver can degrade to a near-no-op cache (slow but working pages). With the improved DataCollector, instrument hit rates in prod and alert on them — the profiler panel is dev-only.
Profiler overhead is dev-only. Don’t expect the cache panel in prod, and don’t benchmark with the profiler enabled.
Extension points are still thin. The realistic extension points remain (a) your own wrapper service around the bundle’s private pool and (b) a decorator for logging/metrics. The new CLI commands and Cacheable Response feature are the main added surfaces, but neither is a plugin system. Budget for a wrapper rather than fighting bundle internals.
How can I help you explore Laravel packages today?