An HTTP response can pass through several caches before it reaches the user, and you may want different rules for each one. Symfony 8.2 adds support for two HTTP headers: one to set those rules, and another to see what happened to a request.
Targeted Cache-Control Headers
You might want your CDN to cache a page for an hour, while keeping a much shorter cache lifetime in the browser. That's because you can purge the CDN cache when the content changes, but you can't purge the copies stored in your visitors' browsers. Targeted cache-control headers, defined in RFC 9213, let you give each cache its own rules.
For example, a CDN that supports CDN-Cache-Control uses it instead of
Cache-Control. Browsers continue to use Cache-Control.
In Symfony 8.2, call the new cacheControl() method on a Response with
the target name (CDN, not CDN-Cache-Control):
1 2 3 4 5 6 7
// browsers consider the response stale immediately...
$response->setMaxAge(0)->setPrivate();
// ...but the CDN can cache it for one hour
$response->cacheControl('CDN')
->setMaxAge(3600)
->setStaleWhileRevalidate(60);
This code generates the following headers:
1 2
Cache-Control: max-age=0, private
CDN-Cache-Control: max-age=3600, stale-while-revalidate=60
You can use methods such as setPublic(), setPrivate() and
setStaleIfError() on the returned object, or set() for other directives.
Use setMaxAge() for the lifetime: there's no setSharedMaxAge() because
the target already identifies the cache these rules apply to.
The Cache-Status Header
When you're debugging HTTP caching, you need to know whether a cache served
the response or forwarded the request to your application. The Cache-Status
header, defined in RFC 9211, gives caches a common way to report this.
To enable it in Symfony's HTTP cache, set the new cache_status option
to the name you want to use for your cache:
1 2 3 4 5
# config/packages/framework.yaml
framework:
http_cache:
enabled: true
cache_status: 'Symfony'
With this configuration, responses include a Cache-Status header such as:
1 2 3
Cache-Status: Symfony; hit; ttl=58
Cache-Status: Symfony; fwd=miss; stored
Cache-Status: Symfony; fwd=stale; fwd-status=304; stored
The first example is a cache hit with 58 seconds of freshness left. The second
is a cache miss: Symfony fetched the response from your application and stored
it. In the third, the cached response was stale, but the application returned
304 Not Modified, so Symfony could reuse it. A negative ttl means the
cache served a stale response.
If another cache has already added a Cache-Status header, Symfony appends
its own entry, so you can follow the response through the caches.
You can enable this header in production, unlike the X-Symfony-Cache
debug header. It's disabled by default (cache_status: null), since it
exposes details about your cache that you may not want to share publicly.
I didn't know about RFC 9213 CDN-Cache-Control header, that's really nice!