@@ -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
@@ -296,6 +288,89 @@ If you're using the :ref:`default services.yaml configuration <service-container
296288you're done! Symfony will automatically pass the ``security.helper ``
297289service when instantiating your voter (thanks to autowiring).
298290
291+ Improving Voter Performance
292+ ---------------------------
293+
294+ If your application defines many voters and checks permissions on many objects
295+ during a single request, this can impact performance. Most of the time, voters
296+ only care about specific permissions (attributes), such as ``EDIT_BLOG_POST ``,
297+ or specific object types, such as ``User `` or ``Invoice ``. That's why Symfony
298+ can cache the voter resolution (i.e. the decision to apply or skip a voter for
299+ a given attribute or object).
300+
301+ To enable this optimization, make your voter implement
302+ :class: `Symfony\\ Component\\ Security\\ Core\\ Authorization\\ Voter\\ CacheableVoterInterface `.
303+ This is already the case when extending the abstract ``Voter `` class shown above.
304+ Then, override one or both of the following methods::
305+
306+ use App\Entity\Post;
307+ use Symfony\Component\Security\Core\Authorization\Voter\Voter;
308+ // ...
309+
310+ class PostVoter extends Voter
311+ {
312+ const VIEW = 'view';
313+ const EDIT = 'edit';
314+
315+ protected function supports(string $attribute, mixed $subject): bool
316+ {
317+ // ...
318+ }
319+
320+ protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
321+ {
322+ // ...
323+ }
324+
325+ // this method returns true if the voter applies to the given attribute;
326+ // if it returns false, Symfony won't call it again for this attribute
327+ public function supportsAttribute(string $attribute): bool
328+ {
329+ return in_array($attribute, [self::VIEW, self::EDIT], true);
330+ }
331+
332+ // this method returns true if the voter applies to the given object class/type;
333+ // if it returns false, Symfony won't call it again for that type of object
334+ public function supportsAttribute(string $attribute): bool
335+ {
336+ // you can't use a simple Post::class === $subjectType comparison
337+ // because the subject type might be a Doctrine proxy class
338+ return is_a($subjectType, Post::class, true);
339+ }
340+ }
341+
342+ .. _security-voters-change-message-and-status-code :
343+
344+ Changing the message and status code returned
345+ ---------------------------------------------
346+
347+ By default, the ``#[IsGranted] `` attribute will throw a
348+ :class: `Symfony\\ Component\\ Security\\ Core\\ Exception\\ AccessDeniedException `
349+ and return an http **403 ** status code with **Access Denied ** as message.
350+
351+ However, you can change this behavior by specifying the message and status code returned::
352+
353+ // src/Controller/PostController.php
354+
355+ // ...
356+ use Symfony\Component\Security\Http\Attribute\IsGranted;
357+
358+ class PostController extends AbstractController
359+ {
360+ #[Route('/posts/{id}', name: 'post_show')]
361+ #[IsGranted('show', 'post', 'Post not found', 404)]
362+ public function show(Post $post): Response
363+ {
364+ // ...
365+ }
366+ }
367+
368+ .. tip ::
369+
370+ If the status code is different than 403, an
371+ :class: `Symfony\\ Component\\ HttpKernel\\ Exception\\ HttpException `
372+ will be thrown instead.
373+
299374.. _security-voters-change-strategy :
300375
301376Changing the Access Decision Strategy
@@ -467,35 +542,3 @@ must implement the :class:`Symfony\\Component\\Security\\Core\\Authorization\\Ac
467542 // ...
468543 ;
469544 };
470-
471- .. _security-voters-change-message-and-status-code :
472-
473- Changing the message and status code returned
474- ---------------------------------------------
475-
476- By default, the ``#[IsGranted] `` attribute will throw a
477- :class: `Symfony\\ Component\\ Security\\ Core\\ Exception\\ AccessDeniedException `
478- and return an http **403 ** status code with **Access Denied ** as message.
479-
480- However, you can change this behavior by specifying the message and status code returned::
481-
482- // src/Controller/PostController.php
483-
484- // ...
485- use Symfony\Component\Security\Http\Attribute\IsGranted;
486-
487- class PostController extends AbstractController
488- {
489- #[Route('/posts/{id}', name: 'post_show')]
490- #[IsGranted('show', 'post', 'Post not found', 404)]
491- public function show(Post $post): Response
492- {
493- // ...
494- }
495- }
496-
497- .. tip ::
498-
499- If the status code is different than 403, an
500- :class: `Symfony\\ Component\\ HttpKernel\\ Exception\\ HttpException `
501- will be thrown instead.
0 commit comments