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

Nicolas Grekas
Contributed by Nicolas Grekas in #65282

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

Pascal CESCON
Contributed by Pascal CESCON in #65573

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.

Published in #Living on the edge