Home / Symfony / New in Symfony 8.1: Improved Cache Attribute

New in Symfony 8.1: Improved Cache Attribute

The #[Cache] attribute lets you define HTTP cache headers directly on
controllers, including dynamic values for the Last-Modified and ETag
headers. Symfony 8.1 makes this attribute easier to use and more flexible.

New request and args Variables in Expressions


HypeMC
Contributed by
HypeMC
in
#62939

Until now, expressions used in the lastModified and etag options
received all request attributes merged with all controller arguments as flat
variables. This caused variable name clashes when an action argument shared a
name with a request attribute, and it made the full Request object
inaccessible from the expression.

Symfony 8.1 aligns this behavior with the #[IsGranted] attribute by exposing
two explicit variables to expressions:

  • request: the current Request object
  • args: an array of resolved controller arguments

The previous flat variables remain available, so existing expressions keep
working unchanged:

use SymfonyComponentHttpKernelAttributeCache;

#[Cache(
    etag: "args['article'].computeETag()",
    lastModified: "args['article'].getUpdatedAt()",
    public: true,
)]
public function show(Article $article): Response
{
    // ...
}

You can now also access the full Request object from the expression to mix
request data into the cache key:

#[Cache(etag: "request.headers.get('Accept-Language') ~ args['article'].getId()")]
public function show(Article $article): Response
{
    // ...
}

Using Closures Instead of Expressions


HypeMC
Contributed by
HypeMC
in
#62940

When string expressions become more complex, some developers prefer using PHP
code because it provides better static analysis, IDE support, and debugging.
That’s why the lastModified and etag options now accept PHP closures as
an alternative to string expressions.

Closures receive the resolved controller arguments and the current Request object:

use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentHttpKernelAttributeCache;

#[Cache(
    lastModified: static function (array $args, Request $request): DateTimeInterface {
        return $args['post']->getUpdatedAt();
    },
    etag: static function (array $args, Request $request): string {
        return (string) $args['post']->getId();
    },
)]
public function show(Post $post): Response
{
    // ...
}

Closures follow the same rules as expressions: cache headers already set on the
response are not overridden.

Applying the Cache Attribute Conditionally


HypeMC
Contributed by
HypeMC
in
#62941

The #[Cache] attribute now accepts a new if option that decides whether
the attribute should be applied. The option accepts either an expression or a
closure that must return a boolean:

use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentHttpKernelAttributeCache;

#[Cache(
    public: true,
    maxage: 3600,
    if: static fn (array $args, Request $request): bool => !$request->query->has('preview'),
)]
public function show(Request $request): Response
{
    // ...
}

This is useful when caching depends on runtime conditions, feature flags, or
when your controller does not return a Response object directly (for
example when using a third-party view layer).

The attribute is now also repeatable, so you can stack multiple #[Cache]
attributes with different conditions on the same controller:

#[Cache(
    public: true,
    maxage: 3600,
    if: static fn (array $args, Request $request): bool => !$request->query->has('preview'),
)]
#[Cache(
    public: false,
    maxage: 0,
    if: static fn (array $args, Request $request): bool => $request->query->has('preview'),
)]
public function article(Request $request): Response
{
    // ...
}


Sponsor the Symfony project.
Tagged:

Leave a Reply

Your email address will not be published. Required fields are marked *