friendsofsymfony/http-cache-bundle
Symfony bundle to enhance HTTP caching: configure cache headers by path/controller, tag responses and invalidate by tags, set up invalidation schemes without code, send purge/ban requests efficiently, vary cache by user type, and plug in custom cache clients.
## Getting Started
### Minimal Setup
1. **Install the Package**
```bash
composer require friendsofsymfony/http-cache-bundle
Enable in config/bundles.php:
return [
// ...
FriendsOfSymfony\HttpCacheBundle\FOSHttpCacheBundle::class => ['all' => true],
];
Configure Basic Caching
Edit config/packages/fos_http_cache.yaml:
fos_http_cache:
cache_manager: cache.app
cache_warmer: fos_http_cache.cache_warmer
default_ttl: 3600
default_max_age: 3600
debug: '%kernel.debug%'
generate_url_type: 'absolute' # or 'relative' (now works with proxy_client.varnish.http.base_url)
cache_control:
rules:
- { path: '^/api/', max_age: 3600, shared_max_age: 3600 }
- { path: '^/public/', max_age: 86400, shared_max_age: 86400 }
First Use Case: Cache API Responses
Annotate a controller method with @Cache:
use Symfony\Component\HttpKernel\Attribute\Cache;
#[Cache(maxAge: 600, public: true)]
public function getData(Request $request): Response
{
return new Response('Cached data');
}
Define granular cache rules in fos_http_cache.yaml:
fos_http_cache:
cache_control:
rules:
# Cache all static assets for 1 year
- { path: '^/assets/', max_age: 31536000, shared_max_age: 31536000 }
# Cache authenticated user data for 1 hour
- { path: '^/user/', max_age: 3600, shared_max_age: 3600, roles: ROLE_USER }
# Dynamic content (no caching)
- { path: '^/dashboard/', match_response: false }
Tag Responses
use FriendsOfSymfony\HttpCacheBundle\Tag\TaggableResponseInterface;
public function listPosts(): Response
{
$response = new Response($postsJson);
$response->setSharedMaxAge(3600);
$response->headers->addCacheControlDirective('public');
// Tag the response for invalidation
$response = $this->tagResponse($response, ['posts']);
return $response;
}
Invalidate Tags
use FriendsOfSymfony\HttpCacheBundle\Invalidation\InvalidationManagerInterface;
public function deletePost(Post $post, InvalidationManagerInterface $invalidationManager): Response
{
$entityManager->remove($post);
$entityManager->flush();
// Invalidate cache tags
$invalidationManager->invalidateTags(['posts']);
return $this->redirectToRoute('home');
}
Configure a proxy client (e.g., Varnish) with base_url support:
fos_http_cache:
proxy_client:
varnish:
http:
base_url: 'http://varnish.example.com' # Now works with generate_url_type
dsn: 'http://varnish:6082'
servers:
- 'varnish1.example.com'
- 'varnish2.example.com'
Differentiate cache behavior by user roles:
fos_http_cache:
cache_control:
rules:
- { path: '^/admin/', max_age: 3600, roles: ROLE_ADMIN }
- { path: '^/user/', max_age: 3600, roles: ROLE_USER }
- { path: '^/public/', max_age: 86400, shared_max_age: 86400 }
Extend the default proxy client for custom logic:
use FriendsOfSymfony\HttpCacheBundle\Client\ProxyClientInterface;
use FriendsOfSymfony\HttpCacheBundle\Client\VarnishClient;
class CustomVarnishClient implements ProxyClientInterface
{
public function purge(string $url, array $tags = []): void
{
// Custom purge logic (e.g., with retries or logging)
$varnishClient = new VarnishClient([
'dsn' => 'http://varnish:6082',
'http' => ['base_url' => 'http://varnish.example.com']
]);
$varnishClient->purge($url, $tags);
}
}
Register the service in config/services.yaml:
services:
FriendsOfSymfony\HttpCacheBundle\Client\ProxyClientInterface: '@custom.varnish_client'
custom.varnish_client:
class: App\Service\CustomVarnishClient
Expression Language Dependency
If using match_response with expressions, ensure symfony/expression-language is installed:
composer require symfony/expression-language
Tag Header Parsing in Symfony HttpCache
If using Symfony’s built-in HttpCache, configure the PurgeTagsListener to handle custom tag headers:
fos_http_cache:
purge_tags_listener:
tag_header_parser: 'App\Service\CustomTagHeaderParser'
Flash Message Loss on Redirects The bundle fixes flash message loss in redirects (since v2.9.1), but ensure your firewall and session configuration is correct.
Deprecated Configurations
cache_manager.generate_url_type is now fully supported with proxy_client.varnish.http.base_url (previously required proxy_client.[client].base_url).Lazy-Loaded Proxy Clients
Clients like fastly and cloudflare are lazy-loaded to support runtime secrets (e.g., API keys). Avoid eager-loading them in tests.
Enable Debug Mode
Set debug: true in fos_http_cache.yaml to log cache headers and invalidation requests.
Check Cache Headers Inspect HTTP responses with tools like Browser DevTools or cURL:
curl -I http://your-app.com/api/data
Look for headers like:
Cache-Control: public, max_age=3600
X-Proxy-Cache: HIT
Validate Invalidation
Use the fos:httpcache:clear command to test full cache invalidation:
php bin/console fos:httpcache:clear
Log Invalidation Requests
Enable logging for the InvalidationListener:
services:
FriendsOfSymfony\HttpCacheBundle\EventListener\InvalidationListener:
calls:
- [setLogger, ['@logger']]
Avoid Over-Tagging
Tag responses sparingly to minimize invalidation overhead. Use broad tags (e.g., products) instead of granular ones (e.g., product_123).
Use shared_max_age for Public Content
Set shared_max_age for content that can be cached by CDNs or shared proxies:
- { path: '^/blog/', max_age: 300, shared_max_age: 86400 }
Leverage reverse_proxy_ttl
Configure a custom TTL header for reverse proxies:
fos_http_cache:
cache_control:
reverse_proxy_ttl: 3600
ttl_header: 'X-Cache-TTL'
Disable Caching for Dynamic Routes
Use match_response: false for routes with dynamic content:
- { path: '^/search/', match_response: false }
Custom Response Matcher
Extend the ResponseMatcher to implement complex logic:
use FriendsOfSymfony\HttpCacheBundle\Matcher\ResponseMatcherInterface;
class CustomResponseMatcher implements ResponseMatcherInterface
{
public function match(Request $request, Response $response): bool
{
// Custom logic (e.g., check response body for dynamic content)
return strpos($response->getContent(), 'dynamic') === false;
}
}
Register it in fos_http_cache.yaml:
fos_http_cache:
response_matcher: 'App\Matcher\CustomResponseMatcher'
Event Listeners
Subscribe to cache events (e.g., fos_http_cache.invalidate_tags):
use Symfony\Component\EventDispatcher\Attribute\AsEvent
How can I help you explore Laravel packages today?