@@ -56,14 +56,6 @@ which makes creating a voter even easier::
56
56
57
57
.. _how-to-use-the-voter-in-a-controller :
58
58
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
-
67
59
Setup: Checking for Access in a Controller
68
60
------------------------------------------
69
61
@@ -310,6 +302,89 @@ If you're using the :ref:`default services.yaml configuration <service-container
310
302
you're done! Symfony will automatically pass the ``security.helper ``
311
303
service when instantiating your voter (thanks to autowiring).
312
304
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
+
313
388
.. _security-voters-change-strategy :
314
389
315
390
Changing the Access Decision Strategy
@@ -481,35 +556,3 @@ must implement the :class:`Symfony\\Component\\Security\\Core\\Authorization\\Ac
481
556
// ...
482
557
;
483
558
};
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