Skip to content

Commit 2ef8e85

Browse files
committed
feature #6422 Documented the ArgumentResolver along the ControllerResolver (iltar)
This PR was submitted for the master branch but it was merged into the 3.1 branch instead (closes #6422). Discussion ---------- Documented the ArgumentResolver along the ControllerResolver | Q | A | ------------- | --- | Doc fix? | yes | New docs? | no ~ symfony/symfony#18308 | Applies to | 3.1 | Fixed tickets | ~ The ArgumentResolver is used now instead of the ControllerResolver. I have yet to document the extension point but first I want to have this page mention it. Commits ------- 11920e3 Documented the ArgumentResolver along the ControllerResolver
2 parents 5eca931 + 11920e3 commit 2ef8e85

7 files changed

+175
-97
lines changed

components/http_kernel/introduction.rst

+80-43
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ to the events discussed below::
9090
use Symfony\Component\HttpFoundation\Request;
9191
use Symfony\Component\HttpKernel\HttpKernel;
9292
use Symfony\Component\EventDispatcher\EventDispatcher;
93+
use Symfony\Component\HttpFoundation\RequestStack;
94+
use Symfony\Component\HttpKernel\Controller\ArgumentResolver;
9395
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
9496

9597
// create the Request object
@@ -98,10 +100,12 @@ to the events discussed below::
98100
$dispatcher = new EventDispatcher();
99101
// ... add some event listeners
100102

101-
// create your controller resolver
102-
$resolver = new ControllerResolver();
103+
// create your controller and argument resolvers
104+
$controllerResolver = new ControllerResolver();
105+
$argumentResolver = new ArgumentResolver();
106+
103107
// instantiate the kernel
104-
$kernel = new HttpKernel($dispatcher, $resolver);
108+
$kernel = new HttpKernel($dispatcher, $controllerResolver, new RequestStack(), $argumentResolver);
105109

106110
// actually execute the kernel, which turns the request into a response
107111
// by dispatching events, calling a controller, and returning the response
@@ -118,6 +122,14 @@ See ":ref:`http-kernel-working-example`" for a more concrete implementation.
118122
For general information on adding listeners to the events below, see
119123
:ref:`http-kernel-creating-listener`.
120124

125+
126+
.. caution::
127+
128+
As of 3.1 the :class:`Symfony\\Component\\Httpkernel\\HttpKernel` accepts a fourth argument, which
129+
must be an instance of :class:`Symfony\\Component\\Httpkernel\\Controller\\ArgumentResolverInterface`.
130+
In 4.0 this argument will become mandatory and the :class:`Symfony\\Component\\Httpkernel\\HttpKernel`
131+
will no longer be able to fall back to the :class:`Symfony\\Component\\Httpkernel\\Controller\\ControllerResolver`.
132+
121133
.. seealso::
122134

123135
There is a wonderful tutorial series on using the HttpKernel component and
@@ -225,13 +237,21 @@ This implementation is explained more in the sidebar below::
225237
public function getArguments(Request $request, $controller);
226238
}
227239

240+
.. caution::
241+
242+
The ``getArguments()`` method in the :class:`Symfony\\Component\\Httpkernel\\Controller\\ControllerResolver`
243+
and respective interface :class:`Symfony\\Component\\Httpkernel\\Controller\\ControllerResolverInterface`
244+
are deprecated as of 3.1 and will be removed in 4.0. You can use the
245+
:class:`Symfony\\Component\\Httpkernel\\Controller\\ArgumentResolver` which uses the
246+
:class:`Symfony\\Component\\Httpkernel\\Controller\\ArgumentResolverInterface` instead.
247+
228248
Internally, the ``HttpKernel::handle`` method first calls
229249
:method:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface::getController`
230250
on the controller resolver. This method is passed the ``Request`` and is responsible
231251
for somehow determining and returning a PHP callable (the controller) based
232252
on the request's information.
233253

234-
The second method, :method:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface::getArguments`,
254+
The second method, :method:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolverInterface::getArguments`,
235255
will be called after another event - ``kernel.controller`` - is dispatched.
236256

237257
.. sidebar:: Resolving the Controller in the Symfony Framework
@@ -310,11 +330,11 @@ on the event object that's passed to listeners on this event.
310330
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
311331

312332
Next, ``HttpKernel::handle`` calls
313-
:method:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface::getArguments`.
333+
:method:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolverInterface::getArguments`.
314334
Remember that the controller returned in ``getController`` is a callable.
315335
The purpose of ``getArguments`` is to return the array of arguments that
316336
should be passed to that controller. Exactly how this is done is completely
317-
up to your design, though the built-in :class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver`
337+
up to your design, though the built-in :class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver`
318338
is a good example.
319339

320340
.. image:: /images/components/http_kernel/07-controller-arguments.png
@@ -326,7 +346,7 @@ of arguments that should be passed when executing that callable.
326346
.. sidebar:: Getting the Controller Arguments in the Symfony Framework
327347

328348
Now that you know exactly what the controller callable (usually a method
329-
inside a controller object) is, the ``ControllerResolver`` uses `reflection`_
349+
inside a controller object) is, the ``ArgumentResolver`` uses `reflection`_
330350
on the callable to return an array of the *names* of each of the arguments.
331351
It then iterates over each of these arguments and uses the following tricks
332352
to determine which value should be passed for each argument:
@@ -338,8 +358,19 @@ of arguments that should be passed when executing that callable.
338358
from the ``RouterListener``).
339359

340360
b) If the argument in the controller is type-hinted with Symfony's
341-
:class:`Symfony\\Component\\HttpFoundation\\Request` object, then the
342-
``Request`` is passed in as the value.
361+
:class:`Symfony\\Component\\HttpFoundation\\Request` object, the
362+
``Request`` is passed in as the value. If you have a custom ``Request``
363+
class, it will be injected as long as you extend the Symfony ``Request``.
364+
365+
c) If the function or method argument is `variadic`_ and the ``Request``
366+
``attributes`` bag contains and array for that argument, they will all be
367+
available through the `variadic`_ argument.
368+
369+
This functionality is provided by resolvers implementing the
370+
:class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolverInterface`.
371+
There are four implementations which provide the default behavior of Symfony but
372+
customization is the key here. By implementing the ``ArgumentValueResolverInterface``
373+
yourself and passing this to the ``ArgumentResolver``, you can extend this functionality.
343374

344375
.. _component-http-kernel-calling-controller:
345376

@@ -612,47 +643,52 @@ A full Working Example
612643
----------------------
613644

614645
When using the HttpKernel component, you're free to attach any listeners
615-
to the core events and use any controller resolver that implements the
616-
:class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface`.
617-
However, the HttpKernel component comes with some built-in listeners and
618-
a built-in ControllerResolver that can be used to create a working example::
646+
to the core events, use any controller resolver that implements the
647+
:class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface` and
648+
use any argument resolver that implements the
649+
:class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolverInterface`.
650+
However, the HttpKernel component comes with some built-in listeners and everything
651+
else that can be used to create a working example::
619652

620-
use Symfony\Component\HttpFoundation\Request;
621-
use Symfony\Component\HttpFoundation\RequestStack;
622-
use Symfony\Component\HttpFoundation\Response;
623-
use Symfony\Component\HttpKernel\HttpKernel;
624-
use Symfony\Component\EventDispatcher\EventDispatcher;
625-
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
626-
use Symfony\Component\HttpKernel\EventListener\RouterListener;
627-
use Symfony\Component\Routing\RouteCollection;
628-
use Symfony\Component\Routing\Route;
629-
use Symfony\Component\Routing\Matcher\UrlMatcher;
630-
use Symfony\Component\Routing\RequestContext;
631-
632-
$routes = new RouteCollection();
633-
$routes->add('hello', new Route('/hello/{name}', array(
634-
'_controller' => function (Request $request) {
635-
return new Response(
636-
sprintf("Hello %s", $request->get('name'))
637-
);
638-
}
639-
)
640-
));
653+
use Symfony\Component\EventDispatcher\EventDispatcher;
654+
use Symfony\Component\HttpFoundation\Request;
655+
use Symfony\Component\HttpFoundation\RequestStack;
656+
use Symfony\Component\HttpFoundation\Response;
657+
use Symfony\Component\HttpKernel\Controller\ArgumentResolver;
658+
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
659+
use Symfony\Component\HttpKernel\EventListener\RouterListener;
660+
use Symfony\Component\HttpKernel\HttpKernel;
661+
use Symfony\Component\Routing\Matcher\UrlMatcher;
662+
use Symfony\Component\Routing\RequestContext;
663+
use Symfony\Component\Routing\Route;
664+
use Symfony\Component\Routing\RouteCollection;
641665

642-
$request = Request::createFromGlobals();
666+
$routes = new RouteCollection();
667+
$routes->add('hello', new Route('/hello/{name}', array(
668+
'_controller' => function (Request $request) {
669+
return new Response(
670+
sprintf("Hello %s", $request->get('name'))
671+
);
672+
})
673+
));
643674

644-
$matcher = new UrlMatcher($routes, new RequestContext());
675+
$request = Request::createFromGlobals();
645676

646-
$dispatcher = new EventDispatcher();
647-
$dispatcher->addSubscriber(new RouterListener($matcher, new RequestStack()));
677+
$matcher = new UrlMatcher($routes, new RequestContext());
648678

649-
$resolver = new ControllerResolver();
650-
$kernel = new HttpKernel($dispatcher, $resolver);
679+
$dispatcher = new EventDispatcher();
680+
$dispatcher->addSubscriber(new RouterListener($matcher, new RequestStack()));
651681

652-
$response = $kernel->handle($request);
653-
$response->send();
682+
$controllerResolver = new ControllerResolver();
683+
$argumentResolver = new ArgumentResolver();
684+
685+
$kernel = new HttpKernel($dispatcher, $controllerResolver, new RequestStack(), $argumentResolver);
686+
687+
$response = $kernel->handle($request);
688+
$response->send();
689+
690+
$kernel->terminate($request, $response);
654691

655-
$kernel->terminate($request, $response);
656692

657693
.. _http-kernel-sub-requests:
658694

@@ -716,3 +752,4 @@ look like this::
716752
.. _`@ParamConverter`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
717753
.. _`@Template`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/view.html
718754
.. _`EmailSenderListener`: https://github.com/symfony/swiftmailer-bundle/blob/master/EventListener/EmailSenderListener.php
755+
.. _variadic: http://php.net/manual/en/functions.arguments.php

create_framework/dependency_injection.rst

+14-5
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,26 @@ to it::
99
// example.com/src/Simplex/Framework.php
1010
namespace Simplex;
1111

12+
use Symfony\Component\EventDispatcher\EventDispatcher;
1213
use Symfony\Component\Routing;
14+
use Symfony\Component\HttpFoundation;
1315
use Symfony\Component\HttpKernel;
14-
use Symfony\Component\EventDispatcher\EventDispatcher;
1516

1617
class Framework extends HttpKernel\HttpKernel
1718
{
1819
public function __construct($routes)
1920
{
2021
$context = new Routing\RequestContext();
2122
$matcher = new Routing\Matcher\UrlMatcher($routes, $context);
22-
$resolver = new HttpKernel\Controller\ControllerResolver();
23+
24+
$controllerResolver = new HttpKernel\Controller\ControllerResolver();
25+
$argumentResolver = new HttpKernel\Controller\ArgumentResolver();
2326

2427
$dispatcher = new EventDispatcher();
2528
$dispatcher->addSubscriber(new HttpKernel\EventListener\RouterListener($matcher));
2629
$dispatcher->addSubscriber(new HttpKernel\EventListener\ResponseListener('UTF-8'));
2730

28-
parent::__construct($dispatcher, $resolver);
31+
parent::__construct($dispatcher, $controllerResolver, new RequestStack(), $argumentResolver);
2932
}
3033
}
3134

@@ -101,7 +104,8 @@ Create a new file to host the dependency injection container configuration::
101104
->setArguments(array($routes, new Reference('context')))
102105
;
103106
$sc->register('request_stack', 'Symfony\Component\HttpFoundation\RequestStack');
104-
$sc->register('resolver', 'Symfony\Component\HttpKernel\Controller\ControllerResolver');
107+
$sc->register('controller_resolver', 'Symfony\Component\HttpKernel\Controller\ControllerResolver');
108+
$sc->register('argument_resolver', 'Symfony\Component\HttpKernel\Controller\ArgumentResolver');
105109

106110
$sc->register('listener.router', 'Symfony\Component\HttpKernel\EventListener\RouterListener')
107111
->setArguments(array(new Reference('matcher'), new Reference('request_stack')))
@@ -118,7 +122,12 @@ Create a new file to host the dependency injection container configuration::
118122
->addMethodCall('addSubscriber', array(new Reference('listener.exception')))
119123
;
120124
$sc->register('framework', 'Simplex\Framework')
121-
->setArguments(array(new Reference('dispatcher'), new Reference('resolver')))
125+
->setArguments(array(
126+
new Reference('dispatcher'),
127+
new Reference('controller_resolver'),
128+
new Reference('request_stack'),
129+
new Reference('argument_resolver'),
130+
))
122131
;
123132

124133
return $sc;

create_framework/event_dispatcher.rst

+13-10
Original file line numberDiff line numberDiff line change
@@ -36,24 +36,27 @@ the Response instance::
3636
// example.com/src/Simplex/Framework.php
3737
namespace Simplex;
3838

39+
use Symfony\Component\EventDispatcher\EventDispatcher;
3940
use Symfony\Component\HttpFoundation\Request;
4041
use Symfony\Component\HttpFoundation\Response;
41-
use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
42-
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
42+
use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface;
4343
use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
44-
use Symfony\Component\EventDispatcher\EventDispatcher;
44+
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
45+
use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
4546

4647
class Framework
4748
{
48-
private $matcher;
49-
private $resolver;
5049
private $dispatcher;
50+
private $matcher;
51+
private $controllerResolver;
52+
private $argumentResolver;
5153

52-
public function __construct(EventDispatcher $dispatcher, UrlMatcherInterface $matcher, ControllerResolverInterface $resolver)
54+
public function __construct(EventDispatcher $dispatcher, UrlMatcherInterface $matcher, ControllerResolverInterface $controllerResolver, ArgumentResolverInterface $argumentResolver)
5355
{
54-
$this->matcher = $matcher;
55-
$this->resolver = $resolver;
5656
$this->dispatcher = $dispatcher;
57+
$this->matcher = $matcher;
58+
$this->controllerResolver = $controllerResolver;
59+
$this->argumentResolver = $argumentResolver;
5760
}
5861

5962
public function handle(Request $request)
@@ -63,8 +66,8 @@ the Response instance::
6366
try {
6467
$request->attributes->add($this->matcher->match($request->getPathInfo()));
6568

66-
$controller = $this->resolver->getController($request);
67-
$arguments = $this->resolver->getArguments($request, $controller);
69+
$controller = $this->controllerResolver->getController($request);
70+
$arguments = $this->argumentResolver->getArguments($request, $controller);
6871

6972
$response = call_user_func_array($controller, $arguments);
7073
} catch (ResourceNotFoundException $e) {

create_framework/http_kernel_controller_resolver.rst

+31-19
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,10 @@ component:
4343
4444
$ composer require symfony/http-kernel
4545
46-
The HttpKernel component has many interesting features, but the one we need
47-
right now is the *controller resolver*. A controller resolver knows how to
48-
determine the controller to execute and the arguments to pass to it, based on
49-
a Request object. All controller resolvers implement the following interface::
46+
The HttpKernel component has many interesting features, but the ones we need
47+
right now are the *controller resolver* and *argument resolver*. A controller resolver knows how to
48+
determine the controller to execute and the argument resolver determines the arguments to pass to it,
49+
based on a Request object. All controller resolvers implement the following interface::
5050

5151
namespace Symfony\Component\HttpKernel\Controller;
5252

@@ -58,6 +58,14 @@ a Request object. All controller resolvers implement the following interface::
5858
function getArguments(Request $request, $controller);
5959
}
6060

61+
.. caution::
62+
63+
The ``getArguments()`` method in the :class:`Symfony\\Component\\Httpkernel\\Controller\\ControllerResolver`
64+
and respective interface :class:`Symfony\\Component\\Httpkernel\\Controller\\ControllerResolverInterface`
65+
are deprecated as of 3.1 and will be removed in 4.0. You can use the
66+
:class:`Symfony\\Component\\Httpkernel\\Controller\\ArgumentResolver` which uses the
67+
:class:`Symfony\\Component\\Httpkernel\\Controller\\ArgumentResolverInterface` instead.
68+
6169
The ``getController()`` method relies on the same convention as the one we
6270
have defined earlier: the ``_controller`` request attribute must contain the
6371
controller associated with the Request. Besides the built-in PHP callbacks,
@@ -74,10 +82,11 @@ resolver from HttpKernel::
7482

7583
use Symfony\Component\HttpKernel;
7684

77-
$resolver = new HttpKernel\Controller\ControllerResolver();
85+
$controllerResolver = new HttpKernel\Controller\ControllerResolver();
86+
$argumentResolver = new HttpKernel\Controller\ArgumentResolver();
7887

79-
$controller = $resolver->getController($request);
80-
$arguments = $resolver->getArguments($request, $controller);
88+
$controller = $controllerResolver->getController($request);
89+
$arguments = $argumentResolver->getArguments($request, $controller);
8190

8291
$response = call_user_func_array($controller, $arguments);
8392

@@ -140,14 +149,12 @@ method is not defined, an argument has no matching attribute, ...).
140149

141150
.. note::
142151

143-
With the great flexibility of the default controller resolver, you might
144-
wonder why someone would want to create another one (why would there be an
145-
interface if not?). Two examples: in Symfony, ``getController()`` is
146-
enhanced to support
147-
:doc:`controllers as services </cookbook/controller/service>`; and in
148-
`FrameworkExtraBundle`_, ``getArguments()`` is enhanced to support
149-
parameter converters, where request attributes are converted to objects
150-
automatically.
152+
With the great flexibility of the default controller resolver and argument
153+
resolver, you might wonder why someone would want to create another one
154+
(why would there be an interface if not?). Two examples: in Symfony,
155+
``getController()`` is enhanced to support :doc:`controllers as services </cookbook/controller/service>`;
156+
and ``getArguments()`` provides an extension point to alter or enhance
157+
the resolving of arguments.
151158

152159
Let's conclude with the new version of our framework::
153160

@@ -174,13 +181,18 @@ Let's conclude with the new version of our framework::
174181
$context = new Routing\RequestContext();
175182
$context->fromRequest($request);
176183
$matcher = new Routing\Matcher\UrlMatcher($routes, $context);
177-
$resolver = new HttpKernel\Controller\ControllerResolver();
184+
185+
$controllerResolver = new HttpKernel\Controller\ControllerResolver();
186+
$argumentResolver = new HttpKernel\Controller\ArgumentResolver();
187+
188+
$controller = $controllerResolver->getController($request);
189+
$arguments = $argumentResolver->getArguments($request, $controller);
178190

179191
try {
180192
$request->attributes->add($matcher->match($request->getPathInfo()));
181193

182-
$controller = $resolver->getController($request);
183-
$arguments = $resolver->getArguments($request, $controller);
194+
$controller = $controllerResolver->getController($request);
195+
$arguments = $argumentResolver->getArguments($request, $controller);
184196

185197
$response = call_user_func_array($controller, $arguments);
186198
} catch (Routing\Exception\ResourceNotFoundException $e) {
@@ -192,7 +204,7 @@ Let's conclude with the new version of our framework::
192204
$response->send();
193205

194206
Think about it once more: our framework is more robust and more flexible than
195-
ever and it still has less than 40 lines of code.
207+
ever and it still has less than 50 lines of code.
196208

197209
.. _`reflection`: http://php.net/reflection
198210
.. _`FrameworkExtraBundle`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html

0 commit comments

Comments
 (0)