@@ -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
@@ -292,6 +284,89 @@ If you're using the :ref:`default services.yaml configuration <service-container
292
284
you're done! Symfony will automatically pass the ``security.helper ``
293
285
service when instantiating your voter (thanks to autowiring).
294
286
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
+
295
370
.. _security-voters-change-strategy :
296
371
297
372
Changing the Access Decision Strategy
@@ -463,35 +538,3 @@ must implement the :class:`Symfony\\Component\\Security\\Core\\Authorization\\Ac
463
538
// ...
464
539
;
465
540
};
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