electivegroup/cache-bundle
Symfony bundle that adds cache routes and services via simple config imports, with cache subscriptions defined in your env file for flexible, environment-specific cache invalidation.
This is a Symfony bundle, not a Laravel package — there is no service provider, no config/cache.php hook, and no Cache:: facade integration. If you are coming from Laravel, treat it as a third-party HTTP-invalidation service you wire into a Symfony app (or reimplement in ~30 lines of Laravel routing if that is your actual stack).
Minimal wiring for a Symfony app:
composer require electivegroup/cache-bundle
config/bundles.php (Flex usually does this):
Elective\CacheBundle\ElectiveCacheBundle::class => ['all' => true],
# config/routes.yaml
cache_bundle:
resource: '@ElectiveCacheBundle/Resources/config/routes.yaml'
# config/services.yaml
imports:
- { resource: '@ElectiveCacheBundle/Resources/config/services.yaml' }
# .env / .env.local
CACHE_SUBSCRIPTIONS=-acl-api-organisation-updated-candidates-api,-acl-api-user-updated-candidates-api
bin/console cache:clear) and verify with bin/console debug:router and bin/console debug:container --tag=cache.subscriber (tag name is a guess — inspect services.yaml to confirm).Where to look first, in this order: Resources/config/routes.yaml (what endpoints exist and their HTTP methods), Resources/config/services.yaml (what tags/arguments are expected), DependencyInjection/Configuration.php and Extension.php (is CACHE_SUBSCRIPTIONS parsed there?), then the controller/listener that consumes the subscriptions. The README stops at installation, so the source is the documentation.
First use case: an upstream publisher (the "ACL API" named in the example env var) emits an "organisation updated" or "user updated" event; the bundle exposes a route that receives the notification and invalidates the matching cache pool/tag so downstream reads stop serving stale data. Validate this with a single manual curl against the route in dev before wiring any real publisher.
Environment-scoped subscriptions. Keep CACHE_SUBSCRIPTIONS per environment rather than global: a full list in production/staging, an empty or narrow list in .env.test so tests are deterministic. Because Symfony compiles env vars into the container by default, treat this value as deploy-time config — change it via your deploy pipeline, not by editing .env on a running box.
Webhook-driven invalidation. The natural production pattern is: publisher fires POST at the bundle route with the subscription key → bundle invalidates → your application reads fresh data on next request. Make the publisher retry-safe (idempotent invalidation) and keep the endpoint behind your internal network or a shared secret in the firewall config.
Event/messenger alternative. If you already use Symfony Messenger or an event dispatcher, don't route through HTTP at all: pull the invalidation service out of the container and call it directly from your message handler. You get the same cache semantics without exposing a route. This is the lowest-risk integration path when the HTTP contract of the bundle is undocumented.
Cache-tag hygiene. Invalidation only works if reads are stored under the same key/tag scheme the bundle forgets. Audit your pool usage (cache.app, custom pools defined in config/packages/cache.yaml) and make sure the items you expect to disappear are actually written with tag/key prefixes the bundle targets. In practice this is where most wasted debugging time goes.
Testing. Use WebTestCase to assert the route returns the expected status and that a seeded cache item is gone afterwards ($pool->getItem($key)->isHit()). Use KernelTestCase plus debug:container in a smoke test to assert the service is wired when CACHE_SUBSCRIPTIONS is non-empty. For the Symfony CLI/CI, set CACHE_SUBSCRIPTIONS in phpunit.xml rather than relying on developer .env files.
Multi-consumer apps. If several services need the same invalidation, register the bundle in each application and point each one's CACHE_SUBSCRIPTIONS at only the upstreams it actually caches. The env-driven list is a coarse but workable subscription filter.
Laravel bridge (if you must stay on Laravel). Mirror the behaviour rather than fight the framework: one route (POST /_cache/invalidate), a config array (config/cache-bundle.php with subscriptions), and a controller that calls Cache::forget()/Cache::tags()->flush(). You lose upstream bug-fixes but gain framework coherence. If you genuinely need this bundle, run it side-by-side in a Symfony service and call it over HTTP.
CACHE_SUBSCRIPTIONS=-acl-api-... is a comma-separated string, not YAML. Trailing commas, spaces after commas, or an empty value can produce empty entries or a parse failure. Normalise with array_filter(array_map('trim', explode(',', $value))) in your own config layer if the bundle is strict.CACHE_SUBSCRIPTIONS you must rebuild the container. On a warm production cache this means a deploy step, not a live edit. Use %env(default::...)%/runtime env vars only if the bundle supports it.routes.yaml lets your {slug} catch-all swallow them. Also check security.yaml — a freshly imported route is public until you add an access_control rule. An unauthenticated cache-invalidation endpoint is a denial-of-service vector.composer.json for the Symfony constraint before adopting on Symfony 6.4/7.x; expect to pin or to fork.bin/console debug:router --show-controllers, bin/console debug:container Elective, bin/console debug:event-dispatcher, and bin/console cache:pool:list / cache:pool:invalidate-tags to confirm the invalidation actually lands where you think it does. Bump monolog to debug on the bundle's namespace while bringing it up.decorates: in services.yaml), add a listener on the kernel event, or override the routes by importing your own controller action. Keep your overrides in config/services.yaml so a composer update can't silently revert them.docs/cache-bundle.md — the next developer will otherwise repeat the investigation.How can I help you explore Laravel packages today?