Skip to content

Commit a1b2446

Browse files
committed
Merge branch '7.3' into 7.4
* 7.3: [Security] Update the main voters article
2 parents c732589 + c850209 commit a1b2446

File tree

1 file changed

+83
-40
lines changed

1 file changed

+83
-40
lines changed

security/voters.rst

Lines changed: 83 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,6 @@ which makes creating a voter even easier::
5656

5757
.. _how-to-use-the-voter-in-a-controller:
5858

59-
.. tip::
60-
61-
Checking each voter several times can be time consuming for applications
62-
that perform a lot of permission checks. To improve performance in those cases,
63-
you can make your voters implement the :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\CacheableVoterInterface`.
64-
This allows the access decision manager to remember the attribute and type
65-
of subject supported by the voter, to only call the needed voters each time.
66-
6759
Setup: Checking for Access in a Controller
6860
------------------------------------------
6961

@@ -310,6 +302,89 @@ If you're using the :ref:`default services.yaml configuration <service-container
310302
you're done! Symfony will automatically pass the ``security.helper``
311303
service when instantiating your voter (thanks to autowiring).
312304

305+
Improving Voter Performance
306+
---------------------------
307+
308+
If your application defines many voters and checks permissions on many objects
309+
during a single request, this can impact performance. Most of the time, voters
310+
only care about specific permissions (attributes), such as ``EDIT_BLOG_POST``,
311+
or specific object types, such as ``User`` or ``Invoice``. That's why Symfony
312+
can cache the voter resolution (i.e. the decision to apply or skip a voter for
313+
a given attribute or object).
314+
315+
To enable this optimization, make your voter implement
316+
:class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\CacheableVoterInterface`.
317+
This is already the case when extending the abstract ``Voter`` class shown above.
318+
Then, override one or both of the following methods::
319+
320+
use App\Entity\Post;
321+
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
322+
// ...
323+
324+
class PostVoter extends Voter
325+
{
326+
const VIEW = 'view';
327+
const EDIT = 'edit';
328+
329+
protected function supports(string $attribute, mixed $subject): bool
330+
{
331+
// ...
332+
}
333+
334+
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
335+
{
336+
// ...
337+
}
338+
339+
// this method returns true if the voter applies to the given attribute;
340+
// if it returns false, Symfony won't call it again for this attribute
341+
public function supportsAttribute(string $attribute): bool
342+
{
343+
return in_array($attribute, [self::VIEW, self::EDIT], true);
344+
}
345+
346+
// this method returns true if the voter applies to the given object class/type;
347+
// if it returns false, Symfony won't call it again for that type of object
348+
public function supportsAttribute(string $attribute): bool
349+
{
350+
// you can't use a simple Post::class === $subjectType comparison
351+
// because the subject type might be a Doctrine proxy class
352+
return is_a($subjectType, Post::class, true);
353+
}
354+
}
355+
356+
.. _security-voters-change-message-and-status-code:
357+
358+
Changing the message and status code returned
359+
---------------------------------------------
360+
361+
By default, the ``#[IsGranted]`` attribute will throw a
362+
:class:`Symfony\\Component\\Security\\Core\\Exception\\AccessDeniedException`
363+
and return an http **403** status code with **Access Denied** as message.
364+
365+
However, you can change this behavior by specifying the message and status code returned::
366+
367+
// src/Controller/PostController.php
368+
369+
// ...
370+
use Symfony\Component\Security\Http\Attribute\IsGranted;
371+
372+
class PostController extends AbstractController
373+
{
374+
#[Route('/posts/{id}', name: 'post_show')]
375+
#[IsGranted('show', 'post', 'Post not found', 404)]
376+
public function show(Post $post): Response
377+
{
378+
// ...
379+
}
380+
}
381+
382+
.. tip::
383+
384+
If the status code is different than 403, an
385+
:class:`Symfony\\Component\\HttpKernel\\Exception\\HttpException`
386+
will be thrown instead.
387+
313388
.. _security-voters-change-strategy:
314389

315390
Changing the Access Decision Strategy
@@ -481,35 +556,3 @@ must implement the :class:`Symfony\\Component\\Security\\Core\\Authorization\\Ac
481556
// ...
482557
;
483558
};
484-
485-
.. _security-voters-change-message-and-status-code:
486-
487-
Changing the message and status code returned
488-
---------------------------------------------
489-
490-
By default, the ``#[IsGranted]`` attribute will throw a
491-
:class:`Symfony\\Component\\Security\\Core\\Exception\\AccessDeniedException`
492-
and return an http **403** status code with **Access Denied** as message.
493-
494-
However, you can change this behavior by specifying the message and status code returned::
495-
496-
// src/Controller/PostController.php
497-
498-
// ...
499-
use Symfony\Component\Security\Http\Attribute\IsGranted;
500-
501-
class PostController extends AbstractController
502-
{
503-
#[Route('/posts/{id}', name: 'post_show')]
504-
#[IsGranted('show', 'post', 'Post not found', 404)]
505-
public function show(Post $post): Response
506-
{
507-
// ...
508-
}
509-
}
510-
511-
.. tip::
512-
513-
If the status code is different than 403, an
514-
:class:`Symfony\\Component\\HttpKernel\\Exception\\HttpException`
515-
will be thrown instead.

0 commit comments

Comments
 (0)