Skip to content

Commit 967ecfb

Browse files
committed
Merge branch '6.4' into 7.2
* 6.4: [Security] Update the main voters article
2 parents 5cd2d73 + 7ed9a6f commit 967ecfb

File tree

1 file changed

+83
-40
lines changed

1 file changed

+83
-40
lines changed

security/voters.rst

Lines changed: 83 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -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-
6153
Setup: Checking for Access in a Controller
6254
------------------------------------------
6355

@@ -292,6 +284,89 @@ If you're using the :ref:`default services.yaml configuration <service-container
292284
you're done! Symfony will automatically pass the ``security.helper``
293285
service 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

297372
Changing 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

Comments
 (0)