Skip to content

Commit

Permalink
Documented the ArgumentResolver along the ControllerResolver
Browse files Browse the repository at this point in the history
  • Loading branch information
Iltar van der Berg committed Apr 1, 2016
1 parent d7724dd commit 25f4985
Show file tree
Hide file tree
Showing 6 changed files with 250 additions and 99 deletions.
158 changes: 108 additions & 50 deletions components/http_kernel/introduction.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,22 @@ is really simple and involves creating an
:doc:`event dispatcher </components/event_dispatcher/introduction>` and a
:ref:`controller resolver <component-http-kernel-resolve-controller>` (explained
below). To complete your working kernel, you'll add more event listeners
to the events discussed below::
to the events discussed below

.. caution::

As of 3.1 the :class:`Symfony\\Component\\Httpkernel\\HttpKernel` accepts a fourth argument, which
should 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`.

.. code-block:: php
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
Expand All @@ -98,10 +109,16 @@ to the events discussed below::
$dispatcher = new EventDispatcher();
// ... add some event listeners
// create your controller resolver
$resolver = new ControllerResolver();
$valueResolvers = [
// ... add some implementations of ArgumentValueResolverInterface
];
// create your controller and argument resolver
$controllerResolver = new ControllerResolver();
$argumentResolver = new ArgumentResolver($argumentMetadataFactory, $valueResolvers);
// 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
Expand Down Expand Up @@ -212,7 +229,19 @@ Your job is to create a class that implements the interface and fill in its
two methods: ``getController`` and ``getArguments``. In fact, one default
implementation already exists, which you can use directly or learn from:
:class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver`.
This implementation is explained more in the sidebar below::
This implementation is explained more in the sidebar below


.. 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.


.. code-block:: php
namespace Symfony\Component\HttpKernel\Controller;
Expand All @@ -231,7 +260,7 @@ on the controller resolver. This method is passed the ``Request`` and is respons
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
Expand Down Expand Up @@ -310,11 +339,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
Expand All @@ -326,7 +355,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:
Expand All @@ -339,7 +368,18 @@ of arguments that should be passed when executing that callable.

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.
``Request`` is passed in as the value. If you have a custom class extending
the ``Request``, this is also accepted.

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:

Expand Down Expand Up @@ -612,47 +652,64 @@ 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`.
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\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\ArgumentValueResolver\ArgumentFromAttributeResolver;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolver\DefaultArgumentValueResolver;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolver\RequestResolver;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolver\VariadicArgumentValueResolver;
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory;
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;

$routes = new RouteCollection();
$routes->add('hello', new Route('/hello/{name}', array(
'_controller' => function (Request $request) {
return new Response(
sprintf("Hello %s", $request->get('name'))
);
})
));

$request = Request::createFromGlobals();

$matcher = new UrlMatcher($routes, new RequestContext());

$dispatcher = new EventDispatcher();
$dispatcher->addSubscriber(new RouterListener($matcher, new RequestStack()));

$argumentValueResolvers = array(
new ArgumentFromAttributeResolver(),
new VariadicArgumentValueResolver(),
new RequestResolver(),
new DefaultArgumentValueResolver(),
);

$controllerResolver = new ControllerResolver();
$argumentResolver = new ArgumentResolver(new ArgumentMetadataFactory(), $argumentValueResolvers);

$kernel = new HttpKernel($dispatcher, $controllerResolver, new RequestStack(), $argumentResolver);

$response = $kernel->handle($request);
$response->send();

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

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'))
);
}
)
));

$request = Request::createFromGlobals();

$matcher = new UrlMatcher($routes, new RequestContext());

$dispatcher = new EventDispatcher();
$dispatcher->addSubscriber(new RouterListener($matcher, new RequestStack()));

$resolver = new ControllerResolver();
$kernel = new HttpKernel($dispatcher, $resolver);

$response = $kernel->handle($request);
$response->send();

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

.. _http-kernel-sub-requests:

Expand Down Expand Up @@ -716,3 +773,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
43 changes: 38 additions & 5 deletions create_framework/dependency_injection.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,35 @@ to it::
// example.com/src/Simplex/Framework.php
namespace Simplex;

use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Routing;
use Symfony\Component\HttpFoundation;
use Symfony\Component\HttpKernel;
use Symfony\Component\EventDispatcher\EventDispatcher;

class Framework extends HttpKernel\HttpKernel
{
public function __construct($routes)
{
$context = new Routing\RequestContext();
$matcher = new Routing\Matcher\UrlMatcher($routes, $context);
$resolver = new HttpKernel\Controller\ControllerResolver();

$valueResolvers = [
new HttpKernel\Controller\ArgumentValueResolver\ArgumentFromAttributeResolver(),
new HttpKernel\Controller\ArgumentValueResolver\VariadicArgumentValueResolver(),
new HttpKernel\Controller\ArgumentValueResolver\RequestResolver(),
new HttpKernel\Controller\ArgumentValueResolver\DefaultArgumentValueResolver(),
];

$argumentMetadataFactory = new HttpKernel\ControllerMetadata\ArgumentMetadataFactory();

$controllerResolver = new HttpKernel\Controller\ControllerResolver();
$argumentResolver = new HttpKernel\Controller\ArgumentResolver($argumentMetadataFactory, $valueResolvers);

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

parent::__construct($dispatcher, $resolver);
parent::__construct($dispatcher, $controllerResolver, new RequestStack(), $argumentResolver);
}
}

Expand Down Expand Up @@ -101,7 +113,23 @@ Create a new file to host the dependency injection container configuration::
->setArguments(array($routes, new Reference('context')))
;
$sc->register('request_stack', 'Symfony\Component\HttpFoundation\RequestStack');
$sc->register('resolver', 'Symfony\Component\HttpKernel\Controller\ControllerResolver');
$sc->register('controller_resolver', 'Symfony\Component\HttpKernel\Controller\ControllerResolver');

$sc->register('attribute_value_resolver', 'Symfony\Component\HttpKernel\Controller\ArgumentValueResolver\ArgumentFromAttributeResolver');
$sc->register('request_resolver', 'Symfony\Component\HttpKernel\Controller\ArgumentValueResolver\RequestResolver');
$sc->register('default_argument_resolver', 'Symfony\Component\HttpKernel\Controller\ArgumentValueResolver\DefaultArgumentValueResolver');
$sc->register('variadic_value_resolver', 'Symfony\Component\HttpKernel\Controller\ArgumentValueResolver\VariadicArgumentValueResolver');

$sc->register('argument_metadata_factory', 'Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory');

$sc->register('argument_resolver', 'Symfony\Component\HttpKernel\Controller\ArgumentResolver')
->setArguments(array(new Reference('argument_metadata_factory'), array(
new Reference('attribute_value_resolver'),
new Reference('request_resolver'),
new Reference('default_argument_resolver'),
new Reference('variadic_value_resolver'),
)))
;

$sc->register('listener.router', 'Symfony\Component\HttpKernel\EventListener\RouterListener')
->setArguments(array(new Reference('matcher'), new Reference('request_stack')))
Expand All @@ -118,7 +146,12 @@ Create a new file to host the dependency injection container configuration::
->addMethodCall('addSubscriber', array(new Reference('listener.exception')))
;
$sc->register('framework', 'Simplex\Framework')
->setArguments(array(new Reference('dispatcher'), new Reference('resolver')))
->setArguments(array(
new Reference('dispatcher'),
new Reference('controller_resolver'),
new Reference('request_stack'),
new Reference('argument_resolver'),
))
;

return $sc;
Expand Down
23 changes: 13 additions & 10 deletions create_framework/event_dispatcher.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,24 +36,27 @@ the Response instance::
// example.com/src/Simplex/Framework.php
namespace Simplex;

use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface;
use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Matcher\UrlMatcherInterface;

class Framework
{
private $matcher;
private $resolver;
private $dispatcher;
private $matcher;
private $controllerResolver;
private $argumentResolver;

public function __construct(EventDispatcher $dispatcher, UrlMatcherInterface $matcher, ControllerResolverInterface $resolver)
public function __construct(EventDispatcher $dispatcher, UrlMatcherInterface $matcher, ControllerResolverInterface $controllerResolver, ArgumentResolverInterface $argumentResolver)
{
$this->matcher = $matcher;
$this->resolver = $resolver;
$this->dispatcher = $dispatcher;
$this->matcher = $matcher;
$this->controllerResolver = $controllerResolver;
$this->argumentResolver = $argumentResolver;
}

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

$controller = $this->resolver->getController($request);
$arguments = $this->resolver->getArguments($request, $controller);
$controller = $this->controllerResolver->getController($request);
$arguments = $this->argumentResolver->getArguments($request, $controller);

$response = call_user_func_array($controller, $arguments);
} catch (ResourceNotFoundException $e) {
Expand Down
Loading

0 comments on commit 25f4985

Please sign in to comment.