I have started learning Symfony2. I came across a doubt: if I have this route:
# app/config/routing.yml
hello:
path: /hello/{name}
defaults: { _controller: AcmeHelloBundle:Hello:index }
And this controller:
// src/Acme/HelloBundle/Controller/HelloController.php
namespace Acme\HelloBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
class HelloController
{
public function indexAction($name)
{
return new Response('<html><body>Ciao '.$name.'!</body></html>');
}
}
Internally Symfony2 (inside app/bootstrap.php.cache) calls the call user_func_array() PHP built-in function:
$arguments = $this->resolver->getArguments($request, $controller);
$response = call_user_func_array($controller, $arguments);
And the call to the getArguments() method returns an array of arguments to pass to the action method. But if the controller were:
// src/Acme/HelloBundle/Controller/HelloController.php
namespace Acme\HelloBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
class HelloController
{
public function indexAction($n)
{
return new Response('<html><body>Ciao '.$n.'!</body></html>');
}
}
Symfony would have complained with a RuntimeException because no param $n is set.
My question is: how Symfony controls this behaviour, I mean, if a route has a {name} param, why the controller must have an action method with a $name parameter and the parameter must be called $name?
Cause in plain PHP, this would work:
$name = 'Alex';
function indexAction($n) {
echo $n;
}
$f = 'indexAction';
$arguments = array($name);
call_user_func_array($f, $arguments);
Even if the functions signature accepts a param named as $n and not as $name.
I hope that this question is understandable, if not, please tell me and I will make an edit.
Thanks for the attention!
It's all done in the Controller Resolver HttpKernel/Controller/ControllerResolver.php via getArguments()
& doArguments()
.
For a better understanding, you will find what you need in Getting The Controller Arguments
Edit: Answer comment.
Does Symfony use the ReflectionParameter class internally in order to keep track of the params of the method's signature and match them with the route params?
Yes, the ControllerResolver uses:
ReflectionMethod to keep track of the params of the method's signature if $controller
is a method
ReflectionObject if $controller
is an object
ReflectionFunction if $controller
is an function
Here is how:
public function getArguments(Request $request, $controller)
{
if (is_array($controller)) {
$r = new \ReflectionMethod($controller[0], $controller[1]);
} elseif (is_object($controller) && !$controller instanceof \Closure) {
$r = new \ReflectionObject($controller);
$r = $r->getMethod('__invoke');
} else {
$r = new \ReflectionFunction($controller);
}
return $this->doGetArguments($request, $controller, $r->getParameters());
}
In your example you only have one parameter on your action, so it's obvious to us that it needs to be populated from the route.
To extend your example, if you added another parameter to the route such as:
# app/config/routing.yml
hello:
path: /hello/{name}/{surname}
defaults: { _controller: AcmeHelloBundle:Hello:index }
And changed your controller to:
// src/Acme/HelloBundle/Controller/HelloController.php
namespace Acme\HelloBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
class HelloController
{
public function indexAction($s, $n)
{
return new Response('<html><body>Ciao '.$n.' '.$s.'!</body></html>');
}
}
Symfony wouldn't know which variable $s
or $n
to populate with which route parameter.
If you change your action:
public function indexAction($surname, $name)
{
return new Response('<html><body>Ciao '.$name.' '.$surname.'!</body></html>');
}
Now, Symfony can look at your argument names and map them to the route parameters. It also means you can have your indexAction
arguments in any order as long as their names match the route parameters. I believe Symfony internally uses the Reflection API to figure all this out.