8000 Merge branch '5.1' into 5.x · symfony/symfony-docs@24dcdfd · GitHub
[go: up one dir, main page]

Skip to content

Commit 24dcdfd

Browse files
committed
Merge branch '5.1' into 5.x
* 5.1: Clarify how workflow can be injected Update form_customization.rst add _failure_path option to reference Clarify authentication entry point and access denied 10000 handler
2 parents a36a9c8 + 3ed9810 commit 24dcdfd

File tree

5 files changed

+224
-33
lines changed

5 files changed

+224
-33
lines changed

form/bootstrap4.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ If you prefer to apply the Bootstrap styles on a form to form basis, include the
7777
{{ form(form) }}
7878
{% endblock %}
7979

80+
.. _reference-forms-bootstrap-error-messages:
81+
8082
Error Messages
8183
--------------
8284

form/form_customization.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,11 @@ Renders any errors for the given field.
255255
{# render any "global" errors not associated to any form field #}
256256
{{ form_errors(form) }}
257257
258+
.. caution::
259+
260+
In the Bootstrap 4 form theme, ``form_errors()`` is already included
261+
in ``form_label()``, see ":ref:`reference-forms-bootstrap-error-messages`"
262+
258263
.. _reference-forms-twig-widget:
259264

260265
form_widget(form_view, variables)

reference/configuration/security.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,14 @@ target_path_parameter
504504
When using a login form, if you include an HTML element to set the target path,
505505
this option lets you change the name of the HTML element itself.
506506

507+
failure_path_parameter
508+
......................
509+
510+
**type**: ``string`` **default**: ``_failure_path``
511+
512+
When using a login form, if you include an HTML element to set the failure path,
513+
this option lets you change the name of the HTML element itself.
514+
507515
use_referer
508516
...........
509517

security/access_denied_handler.rst

Lines changed: 166 additions & 15 deletions
10000
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,116 @@
11
.. index::
22
single: Security; Creating a Custom Access Denied Handler
33

4-
How to Create a Custom Access Denied Handler
5-
============================================
4+
How to Customize Access Denied Responses
5+
========================================
66

7-
When your application throws an ``AccessDeniedException``, you can handle this exception
8-
with a service to return a custom response.
7+
In Symfony, you can throw an
8+
:class:`Symfony\\Component\\Security\\Core\\Exception\\AccessDeniedException`
9+
to disallow access to the user. Symfony will handle this exception and
10+
generates a response based on the authentication state:
911

10-
First, create a class that implements
12+
* **If the user is not authenticated** (or authenticated anonymously), an
13+
authentication entry point is used to generated a response (typically
14+
a redirect to the login page or an *401 Unauthorized* response);
15+
* **If the user is authenticated, but does not have the required
16+
permissions**, a *403 Forbidden* response is generated.
17+
18+
Customize the Unauthorized Response
19+
-----------------------------------
20+
21+
You need to create a class that implements
22+
:class:`Symfony\\Component\\Security\\Http\\EntryPoint\\AuthenticationEntryPointInterface`.
23+
This interface has one method (``start()``) that is called whenever an
24+
unauthenticated user tries to access a protected resource::
25+
26+
// src/Security/AuthenticationEntryPoint.php
27+
namespace App\Security;
28+
29+
use Symfony\Component\HttpFoundation\RedirectResponse;
30+
use Symfony\Component\HttpFoundation\Request;
31+
use Symfony\Component\HttpFoundation\Session\SessionInterface;
32+
use Symfony\Component\Security\Core\Exception\AuthenticationException;
33+
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
34+
35+
class AuthenticationEntryPoint implements AuthenticationEntryPointInterface
36+
{
37+
private $urlGenerator;
38+
private $session;
39+
40+
public function __construct(UrlGeneratorInterface $urlGenerator, SessionInterface $session)
41+
{
42+
$this->urlGenerator = $urlGenerator;
43+
$this->session = $session;
44+
}
45+
46+
public function start(Request $request, AuthenticationException $authException = null): RedirectResponse
47+
{
48+
// add a custom flash message and redirect to the login page
49+
$this->session->getFlashBag()->add('note', 'You have to login in order to access this page.');
50+
51+
return new RedirectResponse($this->urlGenerator->generate('security_login'));
52+
}
53+
}
54+
55+
That's it if you're using the :ref:`default services.yaml configuration <service-container-services-load-example>`.
56+
Otherwise, you have to register this service in the container.
57+
58+
Now, configure this service ID as the entry point for the firewall:
59+
60+
.. configuration-block::
61+
62+
.. code-block:: yaml
63+
64+
# config/packages/security.yaml
65+
firewalls:
66+
# ...
67+
68+
main:
69+
# ...
70+
entry_point: App\Security\AuthenticationEntryPoint
71+
72+
.. code-block:: xml
73+
74+
<!-- config/packages/security.xml -->
75+
<?xml version="1.0" encoding="UTF-8"?>
76+
<srv:container xmlns="http://symfony.com/schema/dic/security"
77+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
78+
xmlns:srv="http://symfony.com/schema/dic/services"
79+
xsi:schemaLocation="http://symfony.com/schema/dic/services
80+
https://symfony.com/schema/dic/services/services-1.0.xsd">
81+
82+
<config>
83+
<firewall name="main"
84+
entry-point="App\Security\AuthenticationEntryPoint"
85+
>
86+
<!-- ... -->
87+
</firewall>
88+
</config>
89+
</srv:container>
90+
91+
.. code-block:: php
92+
93+
// config/packages/security.php
94+
use App\Security\AuthenticationEntryPoint;
95+
96+
$container->loadFromExtension('security', [
97+
'firewalls' => [
98+
'main' => [
99+
// ...
100+
'entry_point' => AuthenticationEntryPoint::class,
101+
],
102+
],
103+
]);
104+
105+
Customize the Forbidden Response
106+
--------------------------------
107+
108+
Create a class that implements
11109
:class:`Symfony\\Component\\Security\\Http\\Authorization\\AccessDeniedHandlerInterface`.
12-
This interface defines one method called ``handle()`` where you can implement whatever
13-
logic that should run when access is denied for the current user (e.g. send a
14-
mail, log a message, or generally return a custom response)::
110+
This interface defines one method called ``handle()`` where you can
111+
implement whatever logic that should execute when access is denied for the
112+
current user (e.g. send a mail, log a message, or generally return a custom
113+
response)::
15114

16115
// src/Security/AccessDeniedHandler.php
17116
namespace App\Security;
@@ -50,11 +149,21 @@ configure it under your firewall:
50149
.. code-block:: xml
51150
52151
<!-- config/packages/security.xml -->
53-
<config>
54-
<firewall name="main">
55-
<access-denied-handler>App\Security\AccessDeniedHandler</access-denied-handler>
56-
</firewall>
57-
</config>
152+
<?xml version="1.0" encoding="UTF-8"?>
153+
<srv:container xmlns="http://symfony.com/schema/dic/security"
154+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
155+
xmlns:srv="http://symfony.com/schema/dic/services"
156+
xsi:schemaLocation="http://symfony.com/schema/dic/services
157+
https://symfony.com/schema/dic/services/services-1.0.xsd">
158+
159+
<config>
160+
<firewall name="main"
161+
access-denied-handler="App\Security\AccessDeniedHandler"
162+
>
163+
<!-- ... -->
164+
</firewall>
165+
</config>
166+
</srv:container>
58167
59168
.. code-block:: php
60169
@@ -70,5 +179,47 @@ configure it under your firewall:
70179
],
71180
]);
72181
73-
That's it! Any ``AccessDeniedException`` thrown by code under the ``main`` firewall
74-
will now be handled by your service.
182+
Customizing All Access Denied Responses
183+
---------------------------------------
184+
185+
In some cases, you might want to customize both responses or do a specific
186+
action (e.g. logging) for each ``AccessDeniedException``. In this case,
187+
configure a :ref:`kernel.exception listener <use-kernel-exception-event>`::
188+
189+
// src/EventListener/AccessDeniedListener.php
190+
namespace App\EventListener;
191+
192+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
193+
use Symfony\Component\HttpFoundation\Response;
194+
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
195+
use Symfony\Component\HttpKernel\KernelEvents;
196+
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
197+
198+
class AccessDeniedListener implements EventSubscriberInterface
199+
{
200+
public static function getSubscribedEvents(): array
201+
{
202+
return [
203+
// the priority must be greater than the Security HTTP
204+
// ExceptionListener, to make sure it's called before
205+
// the default exception listener
206+
KernelEvents::EXCEPTION => ['onKernelException', 2],
207+
];
208+
}
209+
210+
public function onKernelException(ExceptionEvent $event): void
211+
{
212+
$exception = $event->getException();
213+
if (!$exception instanceof AccessDeniedException) {
214+
return;
215+
}
216+
217+
// ... perform some action (e.g. logging)
218+
219+
// optionally set the custom response
220+
$event->setResponse(new Response(null, 403));
221+
222+
// or stop propagation (prevents the next exception listeners from being called)
223+
//$event->stopPropagation();
224+
}
225+
}

workflow.rst

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -236,35 +236,62 @@ what actions are allowed on a blog post::
236236
Accessing the Workflow in a Class
237237
---------------------------------
238238

239-
To access workflow inside a class, use dependency injection and inject the
240-
registry in the constructor::
239+
You can use the workflow inside a class by using
240+
:doc:`service autowiring </service_container/autowiring>` and using
241+
``camelCased workflow name + Workflow`` as parameter name::
241242

242243
use App\Entity\BlogPost;
243-
use Symfony\Component\Workflow\Registry;
244+
use Symfony\Component\Workflow\WorkflowInterface;
244245

245246
class MyClass
246247
{
247-
private $workflowRegistry;
248+
private $blogPublishingWorkflow;
248249

249-
public function __construct(Registry $workflowRegistry)
250+
// this injects the blog_publishing workflow configured before
251+
public function __construct(WorkflowInterface $blogPublishingWorkflow)
250252
{
251-
$this->workflowRegistry = $workflowRegistry;
253+
$this->blogPublishingWorkflow = $blogPublishingWorkflow;
252254
}
253255

254256
public function toReview(BlogPost $post)
255257
{
256-
$workflow = $this->workflowRegistry->get($post);
257-
258258
// Update the currentState on the post
259259
try {
260-
$workflow->apply($post, 'to_review');
260+
$this->blogPublishingWorkflow->apply($post, 'to_review');
261261
} catch (LogicException $exception) {
262262
// ...
263263
}
264264
// ...
265265
}
266266
}
267267

268+
Alternatively, use the registry::
269+
270+
use App\Entity\BlogPost;
271+
use Symfony\Component\Workflow\Registry;
272+
273+
class MyClass
274+
{
275+
private $workflowRegistry;
276+
277+
public function __construct(Registry $workflowRegistry)
278+
{
279+
$this->workflowRegistry = $workflowRegistry;
280+
}
281+
282+
public function toReview(BlogPost $post)
283+
{
284+
$blogPublishingWorkflow = $this->workflowRegistry->get($post);
285+
286+
// ...
287+
}
288+
}
289+
290+
.. tip::
291+
292+
You can find the list of available workflow services with the
293+
``php bin/console debug:autowiring workflow`` command.
294+
268295
Using Events
269296
------------
270297

@@ -928,25 +955,23 @@ Then you can access this metadata in your controller as follows::
928955

929956
// src/App/Controller/BlogPostController.php
930957
use App\Entity\BlogPost;
931-
use Symfony\Component\Workflow\Registry;
958+
use Symfony\Component\Workflow\WorkflowInterface;
932959
// ...
933960

934-
public function myAction(Registry $registry, BlogPost $post)
961+
public function myAction(WorkflowInterface $blogPublishingWorkflow, BlogPost $post)
935962
{
936-
$workflow = $registry->get($post);
937-
938-
$title = $workflow
963+
$title = $blogPublishingWorkflow
939964
->getMetadataStore()
940965
->getWorkflowMetadata()['title'] ?? 'Default title'
941966
;
942967

943-
$maxNumOfWords = $workflow
968+
$maxNumOfWords = $blogPublishingWorkflow
944969
->getMetadataStore()
945970
->getPlaceMetadata('draft')['max_num_of_words'] ?? 500
946971
;
947972

948-
$aTransition = $workflow->getDefinition()->getTransitions()[0];
949-
$priority = $workflow
973+
$aTransition = $blogPublishingWorkflow->getDefinition()->getTransitions()[0];
974+
$priority = $blogPublishingWorkflow
950975
->getMetadataStore()
951976
->getTransitionMetadata($aTransition)['priority'] ?? 0
952977
;
@@ -969,7 +994,7 @@ In a :ref:`flash message <flash-messages>` in your controller::
969994

970995
// $transition = ...; (an instance of Transition)
971996

972-
// $workflow is a Workflow instance retrieved from the Registry (see above)
997+
// $workflow is a Workflow instance retrieved from the Registry or injected directly (see above)
973998
$title = $workflow->getMetadataStore()->getMetadata('title', $transition);
974999
$this->addFlash('info', "You have successfully applied the transition with title: '$title'");
9751000 341A

0 commit comments

Comments
 (0)
0