@@ -50,14 +50,6 @@ which makes creating a voter even easier::
5050
5151.. _how-to-use-the-voter-in-a-controller :
5252
53- .. tip ::
54-
55- Checking each voter several times can be time consuming for applications
56- that perform a lot of permission checks. To improve performance in those cases,
57- you can make your voters implement the :class: `Symfony\\ Component\\ Security\\ Core\\ Authorization\\ Voter\\ CacheableVoterInterface `.
58- This allows the access decision manager to remember the attribute and type
59- of subject supported by the voter, to only call the needed voters each time.
60-
6153Setup: Checking for Access in a Controller
6254------------------------------------------
6355
@@ -292,6 +284,89 @@ If you're using the :ref:`default services.yaml configuration <service-container
292284you're done! Symfony will automatically pass the ``security.helper ``
293285service when instantiating your voter (thanks to autowiring).
294286
287+ Improving Voter Performance
288+ ---------------------------
289+
290+ If your application defines many voters and checks permissions on many objects
291+ during a single request, this can impact performance. Most of the time, voters
292+ only care about specific permissions (attributes), such as ``EDIT_BLOG_POST ``,
293+ or specific object types, such as ``User `` or ``Invoice ``. That's why Symfony
294+ can cache the voter resolution (i.e. the decision to apply or skip a voter for
295+ a given attribute or object).
296+
297+ To enable this optimization, make your voter implement
298+ :class: `Symfony\\ Component\\ Security\\ Core\\ Authorization\\ Voter\\ CacheableVoterInterface `.
299+ This is already the case when extending the abstract ``Voter `` class shown above.
300+ Then, override one or both of the following methods::
301+
302+ use App\Entity\Post;
303+ use Symfony\Component\Security\Core\Authorization\Voter\Voter;
304+ // ...
305+
306+ class PostVoter extends Voter
307+ {
308+ const VIEW = 'view';
309+ const EDIT = 'edit';
310+
311+ protected function supports(string $attribute, mixed $subject): bool
312+ {
313+ // ...
314+ }
315+
316+ protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
317+ {
318+ // ...
319+ }
320+
321+ // this method returns true if the voter applies to the given attribute;
322+ // if it returns false, Symfony won't call it again for this attribute
323+ public function supportsAttribute(string $attribute): bool
324+ {
325+ return in_array($attribute, [self::VIEW, self::EDIT], true);
326+ }
327+
328+ // this method returns true if the voter applies to the given object class/type;
329+ // if it returns false, Symfony won't call it again for that type of object
330+ public function supportsAttribute(string $attribute): bool
331+ {
332+ // you can't use a simple Post::class === $subjectType comparison
333+ // because the subject type might be a Doctrine proxy class
334+ return is_a($subjectType, Post::class, true);
335+ }
336+ }
337+
338+ .. _security-voters-change-message-and-status-code :
339+
340+ Changing the message and status code returned
341+ ---------------------------------------------
342+
343+ By default, the ``#[IsGranted] `` attribute will throw a
344+ :class: `Symfony\\ Component\\ Security\\ Core\\ Exception\\ AccessDeniedException `
345+ and return an http **403 ** status code with **Access Denied ** as message.
346+
347+ However, you can change this behavior by specifying the message and status code returned::
348+
349+ // src/Controller/PostController.php
350+
351+ // ...
352+ use Symfony\Component\Security\Http\Attribute\IsGranted;
353+
354+ class PostController extends AbstractController
355+ {
356+ #[Route('/posts/{id}', name: 'post_show')]
357+ #[IsGranted('show', 'post', 'Post not found', 404)]
358+ public function show(Post $post): Response
359+ {
360+ // ...
361+ }
362+ }
363+
364+ .. tip ::
365+
366+ If the status code is different than 403, an
367+ :class: `Symfony\\ Component\\ HttpKernel\\ Exception\\ HttpException `
368+ will be thrown instead.
369+
295370.. _security-voters-change-strategy :
296371
297372Changing the Access Decision Strategy
@@ -463,35 +538,3 @@ must implement the :class:`Symfony\\Component\\Security\\Core\\Authorization\\Ac
463538 // ...
464539 ;
465540 };
466-
467- .. _security-voters-change-message-and-status-code :
468-
469- Changing the message and status code returned
470- ---------------------------------------------
471-
472- By default, the ``#[IsGranted] `` attribute will throw a
473- :class: `Symfony\\ Component\\ Security\\ Core\\ Exception\\ AccessDeniedException `
474- and return an http **403 ** status code with **Access Denied ** as message.
475-
476- However, you can change this behavior by specifying the message and status code returned::
477-
478- // src/Controller/PostController.php
479-
480- // ...
481- use Symfony\Component\Security\Http\Attribute\IsGranted;
482-
483- class PostController extends AbstractController
484- {
485- #[Route('/posts/{id}', name: 'post_show')]
486- #[IsGranted('show', 'post', 'Post not found', 404)]
487- public function show(Post $post): Response
488- {
489- // ...
490- }
491- }
492-
493- .. tip ::
494-
495- If the status code is different than 403, an
496- :class: `Symfony\\ Component\\ HttpKernel\\ Exception\\ HttpException `
497- will be thrown instead.
0 commit comments