-
-
Notifications
You must be signed in to change notification settings - Fork 5.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Documented the ArgumentResolver along the ControllerResolver #6422
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -90,6 +90,8 @@ to the events discussed below:: | |
use Symfony\Component\HttpFoundation\Request; | ||
use Symfony\Component\HttpKernel\HttpKernel; | ||
use Symfony\Component\EventDispatcher\EventDispatcher; | ||
use Symfony\Component\HttpFoundation\RequestStack; | ||
use Symfony\Component\HttpKernel\Controller\ArgumentResolver; | ||
use Symfony\Component\HttpKernel\Controller\ControllerResolver; | ||
|
||
// create the Request object | ||
|
@@ -98,10 +100,12 @@ to the events discussed below:: | |
$dispatcher = new EventDispatcher(); | ||
// ... add some event listeners | ||
|
||
// create your controller resolver | ||
$resolver = new ControllerResolver(); | ||
// create your controller and argument resolvers | ||
$controllerResolver = new ControllerResolver(); | ||
$argumentResolver = new ArgumentResolver(); | ||
|
||
// instantiate the kernel | ||
$kernel = new HttpKernel($dispatcher, $resolver); | ||
$kernel = new HttpKernel($dispatcher, $controllerResolver, new RequestStack(), $argumentResolver); | ||
|
||
// actually execute the kernel, which turns the request into a response | ||
// 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. | |
For general information on adding listeners to the events below, see | ||
:ref:`http-kernel-creating-listener`. | ||
|
||
|
||
.. caution:: | ||
|
||
As of 3.1 the :class:`Symfony\\Component\\Httpkernel\\HttpKernel` accepts a fourth argument, which | ||
must be an instance of :class:`Symfony\\Component\\Httpkernel\\Controller\\ArgumentResolverInterface`. | ||
In 4.0 this argument will become mandatory and the :class:`Symfony\\Component\\Httpkernel\\HttpKernel` | ||
will no longer be able to fall back to the :class:`Symfony\\Component\\Httpkernel\\Controller\\ControllerResolver`. | ||
|
||
.. seealso:: | ||
|
||
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:: | |
public function getArguments(Request $request, $controller); | ||
} | ||
|
||
.. caution:: | ||
|
||
The ``getArguments()`` method in the :class:`Symfony\\Component\\Httpkernel\\Controller\\ControllerResolver` | ||
and respective interface :class:`Symfony\\Component\\Httpkernel\\Controller\\ControllerResolverInterface` | ||
are deprecated as of 3.1 and will be removed in 4.0. You can use the | ||
:class:`Symfony\\Component\\Httpkernel\\Controller\\ArgumentResolver` which uses the | ||
:class:`Symfony\\Component\\Httpkernel\\Controller\\ArgumentResolverInterface` instead. | ||
|
||
Internally, the ``HttpKernel::handle`` method first calls | ||
:method:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface::getController` | ||
on the controller resolver. This method is passed the ``Request`` and is responsible | ||
for somehow determining and returning a PHP callable (the controller) based | ||
on the request's information. | ||
|
||
The second method, :method:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface::getArguments`, | ||
The second method, :method:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolverInterface::getArguments`, | ||
will be called after another event - ``kernel.controller`` - is dispatched. | ||
|
||
.. sidebar:: Resolving the Controller in the Symfony Framework | ||
|
@@ -310,11 +330,11 @@ on the event object that's passed to listeners on this event. | |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
Next, ``HttpKernel::handle`` calls | ||
:method:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface::getArguments`. | ||
:method:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolverInterface::getArguments`. | ||
Remember that the controller returned in ``getController`` is a callable. | ||
The purpose of ``getArguments`` is to return the array of arguments that | ||
should be passed to that controller. Exactly how this is done is completely | ||
up to your design, though the built-in :class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver` | ||
up to your design, though the built-in :class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver` | ||
is a good example. | ||
|
||
.. image:: /images/components/http_kernel/07-controller-arguments.png | ||
|
@@ -326,7 +346,7 @@ of arguments that should be passed when executing that callable. | |
.. sidebar:: Getting the Controller Arguments in the Symfony Framework | ||
|
||
Now that you know exactly what the controller callable (usually a method | ||
inside a controller object) is, the ``ControllerResolver`` uses `reflection`_ | ||
inside a controller object) is, the ``ArgumentResolver`` uses `reflection`_ | ||
on the callable to return an array of the *names* of each of the arguments. | ||
It then iterates over each of these arguments and uses the following tricks | ||
to determine which value should be passed for each argument: | ||
|
@@ -338,8 +358,19 @@ of arguments that should be passed when executing that callable. | |
from the ``RouterListener``). | ||
|
||
b) If the argument in the controller is type-hinted with Symfony's | ||
:class:`Symfony\\Component\\HttpFoundation\\Request` object, then the | ||
``Request`` is passed in as the value. | ||
:class:`Symfony\\Component\\HttpFoundation\\Request` object, the | ||
``Request`` is passed in as the value. If you have a custom ``Request`` | ||
class, it will be injected as long as you extend the Symfony ``Request``. | ||
|
||
c) If the function or method argument is `variadic`_ and the ``Request`` | ||
``attributes`` bag contains and array for that argument, they will all be | ||
available through the `variadic`_ argument. | ||
|
||
This functionality is provided by resolvers implementing the | ||
:class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentValueResolverInterface`. | ||
There are four implementations which provide the default behavior of Symfony but | ||
customization is the key here. By implementing the ``ArgumentValueResolverInterface`` | ||
yourself and passing this to the ``ArgumentResolver``, you can extend this functionality. | ||
|
||
.. _component-http-kernel-calling-controller: | ||
|
||
|
@@ -612,47 +643,52 @@ A full Working Example | |
---------------------- | ||
|
||
When using the HttpKernel component, you're free to attach any listeners | ||
to the core events and use any controller resolver that implements the | ||
:class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface`. | ||
However, the HttpKernel component comes with some built-in listeners and | ||
a built-in ControllerResolver that can be used to create a working example:: | ||
to the core events, use any controller resolver that implements the | ||
:class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface` and | ||
use any argument resolver that implements the | ||
:class:`Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolverInterface`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So could this one |
||
However, the HttpKernel component comes with some built-in listeners and everything | ||
else that can be used to create a working example:: | ||
|
||
use Symfony\Component\HttpFoundation\Request; | ||
use Symfony\Component\HttpFoundation\RequestStack; | ||
use Symfony\Component\HttpFoundation\Response; | ||
use Symfony\Component\HttpKernel\HttpKernel; | ||
use Symfony\Component\EventDispatcher\EventDispatcher; | ||
use Symfony\Component\HttpKernel\Controller\ControllerResolver; | ||
use Symfony\Component\HttpKernel\EventListener\RouterListener; | ||
use Symfony\Component\Routing\RouteCollection; | ||
use Symfony\Component\Routing\Route; | ||
use Symfony\Component\Routing\Matcher\UrlMatcher; | ||
use Symfony\Component\Routing\RequestContext; | ||
|
||
$routes = new RouteCollection(); | ||
$routes->add('hello', new Route('/hello/{name}', array( | ||
'_controller' => function (Request $request) { | ||
return new Response( | ||
sprintf("Hello %s", $request->get('name')) | ||
); | ||
} | ||
) | ||
)); | ||
use Symfony\Component\EventDispatcher\EventDispatcher; | ||
use Symfony\Component\HttpFoundation\Request; | ||
use Symfony\Component\HttpFoundation\RequestStack; | ||
use Symfony\Component\HttpFoundation\Response; | ||
use Symfony\Component\HttpKernel\Controller\ArgumentResolver; | ||
use Symfony\Component\HttpKernel\Controller\ControllerResolver; | ||
use Symfony\Component\HttpKernel\EventListener\RouterListener; | ||
use Symfony\Component\HttpKernel\HttpKernel; | ||
use Symfony\Component\Routing\Matcher\UrlMatcher; | ||
use Symfony\Component\Routing\RequestContext; | ||
use Symfony\Component\Routing\Route; | ||
use Symfony\Component\Routing\RouteCollection; | ||
|
||
$request = Request::createFromGlobals(); | ||
$routes = new RouteCollection(); | ||
$routes->add('hello', new Route('/hello/{name}', array( | ||
'_controller' => function (Request $request) { | ||
return new Response( | ||
sprintf("Hello %s", $request->get('name')) | ||
); | ||
}) | ||
)); | ||
|
||
$matcher = new UrlMatcher($routes, new RequestContext()); | ||
$request = Request::createFromGlobals(); | ||
|
||
$dispatcher = new EventDispatcher(); | ||
$dispatcher->addSubscriber(new RouterListener($matcher, new RequestStack())); | ||
$matcher = new UrlMatcher($routes, new RequestContext()); | ||
|
||
$resolver = new ControllerResolver(); | ||
$kernel = new HttpKernel($dispatcher, $resolver); | ||
$dispatcher = new EventDispatcher(); | ||
$dispatcher->addSubscriber(new RouterListener($matcher, new RequestStack())); | ||
|
||
$response = $kernel->handle($request); | ||
$response->send(); | ||
$controllerResolver = new ControllerResolver(); | ||
$argumentResolver = new ArgumentResolver(); | ||
|
||
$kernel = new HttpKernel($dispatcher, $controllerResolver, new RequestStack(), $argumentResolver); | ||
|
||
$response = $kernel->handle($request); | ||
$response->send(); | ||
|
||
$kernel->terminate($request, $response); | ||
|
||
$kernel->terminate($request, $response); | ||
|
||
.. _http-kernel-sub-requests: | ||
|
||
|
@@ -716,3 +752,4 @@ look like this:: | |
.. _`@ParamConverter`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html | ||
.. _`@Template`: https://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/view.html | ||
.. _`EmailSenderListener`: https://github.com/symfony/swiftmailer-bundle/blob/master/EventListener/EmailSenderListener.php | ||
.. _variadic: http://php.net/manual/en/functions.arguments.php |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,10 +43,10 @@ component: | |
|
||
$ composer require symfony/http-kernel | ||
|
||
The HttpKernel component has many interesting features, but the one we need | ||
right now is the *controller resolver*. A controller resolver knows how to | ||
determine the controller to execute and the arguments to pass to it, based on | ||
a Request object. All controller resolvers implement the following interface:: | ||
The HttpKernel component has many interesting features, but the ones we need | ||
right now are the *controller resolver* and *argument resolver*. A controller resolver knows how to | ||
determine the controller to execute and the argument resolver determines the arguments to pass to it, | ||
based on a Request object. All controller resolvers implement the following interface:: | ||
|
||
namespace Symfony\Component\HttpKernel\Controller; | ||
|
||
|
@@ -58,6 +58,14 @@ a Request object. All controller resolvers implement the following interface:: | |
function getArguments(Request $request, $controller); | ||
} | ||
|
||
.. caution:: | ||
|
||
The ``getArguments()`` method in the :class:`Symfony\\Component\\Httpkernel\\Controller\\ControllerResolver` | ||
and respective interface :class:`Symfony\\Component\\Httpkernel\\Controller\\ControllerResolverInterface` | ||
are deprecated as of 3.1 and will be removed in 4.0. You can use the | ||
:class:`Symfony\\Component\\Httpkernel\\Controller\\ArgumentResolver` which uses the | ||
:class:`Symfony\\Component\\Httpkernel\\Controller\\ArgumentResolverInterface` instead. | ||
|
||
The ``getController()`` method relies on the same convention as the one we | ||
have defined earlier: the ``_controller`` request attribute must contain the | ||
controller associated with the Request. Besides the built-in PHP callbacks, | ||
|
@@ -74,10 +82,11 @@ resolver from HttpKernel:: | |
|
||
use Symfony\Component\HttpKernel; | ||
|
||
$resolver = new HttpKernel\Controller\ControllerResolver(); | ||
$controllerResolver = new HttpKernel\Controller\ControllerResolver(); | ||
$argumentResolver = new HttpKernel\Controller\ArgumentResolver(); | ||
|
||
$controller = $resolver->getController($request); | ||
$arguments = $resolver->getArguments($request, $controller); | ||
$controller = $controllerResolver->getController($request); | ||
$arguments = $argumentResolver->getArguments($request, $controller); | ||
|
||
$response = call_user_func_array($controller, $arguments); | ||
|
||
|
@@ -140,14 +149,12 @@ method is not defined, an argument has no matching attribute, ...). | |
|
||
.. note:: | ||
|
||
With the great flexibility of the default controller resolver, you might | ||
wonder why someone would want to create another one (why would there be an | ||
interface if not?). Two examples: in Symfony, ``getController()`` is | ||
enhanced to support | ||
:doc:`controllers as services </cookbook/controller/service>`; and in | ||
`FrameworkExtraBundle`_, ``getArguments()`` is enhanced to support | ||
parameter converters, where request attributes are converted to objects | ||
automatically. | ||
With the great flexibility of the default controller resolver and argument | ||
resolver, you might wonder why someone would want to create another one | ||
(why would there be an interface if not?). Two examples: in Symfony, | ||
``getController()`` is enhanced to support :doc:`controllers as services </cookbook/controller/service>`; | ||
and ``getArguments()`` provides an extension point to alter or enhance | ||
the resolving of arguments. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you intend to re-document the param converters later, once the works in progress are done, we should maybe open an issue to not forget that ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Param converters will keep working the way they do, I think it's easier to re-document that when they change. The reason I have changed the notion of |
||
|
||
Let's conclude with the new version of our framework:: | ||
|
||
|
@@ -174,13 +181,18 @@ Let's conclude with the new version of our framework:: | |
$context = new Routing\RequestContext(); | ||
$context->fromRequest($request); | ||
$matcher = new Routing\Matcher\UrlMatcher($routes, $context); | ||
$resolver = new HttpKernel\Controller\ControllerResolver(); | ||
|
||
$controllerResolver = new HttpKernel\Controller\ControllerResolver(); | ||
$argumentResolver = new HttpKernel\Controller\ArgumentResolver(); | ||
|
||
$controller = $controllerResolver->getController($request); | ||
$arguments = $argumentResolver->getArguments($request, $controller); | ||
|
||
try { | ||
$request->attributes->add($matcher->match($request->getPathInfo())); | ||
|
||
$controller = $resolver->getController($request); | ||
$arguments = $resolver->getArguments($request, $controller); | ||
$controller = $controllerResolver->getController($request); | ||
$arguments = $argumentResolver->getArguments($request, $controller); | ||
|
||
$response = call_user_func_array($controller, $arguments); | ||
} catch (Routing\Exception\ResourceNotFoundException $e) { | ||
|
@@ -192,7 +204,7 @@ Let's conclude with the new version of our framework:: | |
$response->send(); | ||
|
||
Think about it once more: our framework is more robust and more flexible than | ||
ever and it still has less than 40 lines of code. | ||
ever and it still has less than 50 lines of code. | ||
|
||
.. _`reflection`: http://php.net/reflection | ||
.. _`FrameworkExtraBundle`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this interface be in the
Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver\\ArgumentValueResolverInterface
namespace with the other argument value resolvers ?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This interface is in the Controller namespace
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know :) I was asking in the wrong place if it was not the wrong namespace in the PR. It could make sense to keep this interface in the same namespace as the implementations, WDYT ?