Security expressions let you use functions such as is_granted() and is_authenticated() to define complex access rules in access_control, #[IsGranted] and other security features. Symfony 8.2 makes these functions available in more expressions, such as validation constraints and controller attributes.

Security Functions in Validation Constraints

Matthias Perret
Contributed by Matthias Perret in #63719

Sometimes, validation rules depend on who's submitting the data. For example, you might want to limit the length of a blog post summary for most users, while letting editors write longer ones.

In Symfony 8.2, when your application uses both the Validator and Security components, the Expression constraint and the When constraint can use the is_granted(), is_authenticated(), is_fully_authenticated() and is_remember_me() functions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/Entity/BlogPost.php
use Symfony\Component\Validator\Constraints as Assert;

class BlogPost
{
    // editors can write summaries of any length; other users can't
    #[Assert\When(
        expression: 'not is_granted("ROLE_EDITOR")',
        constraints: [new Assert\Length(max: 280)],
    )]
    public string $summary = '';

    // ...
}

Caution

These functions can only be evaluated inside a request. If you validate the same object in a console command or a Messenger worker, they throw an exception instead of returning false. Otherwise, the constraints guarded by a When expression would be silently skipped.

A New current_user() Function

Sébastien JEAN
Contributed by Sébastien JEAN in #65821 and #2280

The new current_user() expression function returns the currently authenticated user (or null if nobody is logged in). You can use it in validation constraints, for example to prevent users from sending an invitation to their own email address:

1
2
3
4
5
6
7
8
9
10
11
12
13
// src/Entity/Invitation.php
use Symfony\Component\Validator\Constraints as Assert;

class Invitation
{
    #[Assert\Expression(
        'value != current_user()?.getEmail()',
        message: 'You cannot invite yourself.',
    )]
    public string $email = '';

    // ...
}

With the upcoming DoctrineBundle 3.4, you can also use all these security functions in #[MapEntity] expressions. For example, you can pass current_user() directly to a repository method instead of injecting the token storage into the repository:

1
2
3
4
5
6
7
8
9
10
11
12
// src/Controller/OrderController.php
use App\Entity\Order;
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
// ...

#[Route('/orders')]
public function list(
    #[MapEntity(class: Order::class, expr: 'repository.findLatestByCustomer(current_user())')]
    iterable $orders,
): Response {
    // ...
}

Security Functions in Controller Attributes

Tom Sommer
Contributed by Tom Sommer in #66176

Some controller attributes accept expressions, including the key option of #[RateLimit] and the validationGroups option of #[MapRequestPayload]. Until now, these expressions could only access the request, the controller arguments (args) and the controller itself (this). If you needed the current user, you had to add a #[CurrentUser] argument, even if the controller method never used it.

In Symfony 8.2, you can call current_user() directly:

1
2
3
4
5
6
7
8
9
10
11
use Symfony\Component\ExpressionLanguage\Expression;
  use Symfony\Component\HttpKernel\Attribute\RateLimit;
  // ...

- #[RateLimit('api', key: new Expression('args["user"].getUserIdentifier()'))]
- public function index(#[CurrentUser] User $user): Response
+ #[RateLimit('api', key: new Expression('current_user().getUserIdentifier()'))]
+ public function index(): Response
  {
      // ...
  }
Published in #Living on the edge