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
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
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
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
{
// ...
}
This feature will be also in DoctrineMongoDBBundle 5.7.0: https://github.com/doctrine/DoctrineMongoDBBundle/pull/1000