@@ -50,14 +50,6 @@ which makes creating a voter even easier::
50
50
51
51
.. _how-to-use-the-voter-in-a-controller :
52
52
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
-
61
53
Setup: Checking for Access in a Controller
62
54
------------------------------------------
63
55
@@ -296,6 +288,89 @@ If you're using the :ref:`default services.yaml configuration <service-container
296
288
you're done! Symfony will automatically pass the ``security.helper ``
297
289
service when instantiating your voter (thanks to autowiring).
298
290
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
+
299
374
.. _security-voters-change-strategy :
300
375
301
376
Changing the Access Decision Strategy
@@ -467,35 +542,3 @@ must implement the :class:`Symfony\\Component\\Security\\Core\\Authorization\\Ac
467
542
// ...
468
543
;
469
544
};
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