From c0331a0421f30d0129a1f114b969a1b822347c51 Mon Sep 17 00:00:00 2001 From: Iltar van der Berg Date: Wed, 16 Mar 2016 08:56:41 +0100 Subject: [PATCH 1/7] Extracting arg resolving from ControllerResolver --- .../FrameworkExtension.php | 4 +- .../Resources/config/debug.xml | 9 +- .../Resources/config/services.xml | 1 + .../FrameworkBundle/Resources/config/web.xml | 2 + .../Controller/ArgumentResolver.php | 70 +++++++++ .../Controller/ArgumentResolverInterface.php | 35 +++++ .../Controller/ControllerResolver.php | 46 ++---- .../ControllerResolverInterface.php | 12 -- .../Controller/TraceableArgumentResolver.php | 44 ++++++ .../TraceableControllerResolver.php | 14 -- .../Component/HttpKernel/HttpKernel.php | 22 ++- .../Tests/Controller/ArgumentResolverTest.php | 137 ++++++++++++++++++ .../Controller/ControllerResolverTest.php | 4 + .../Debug/TraceableEventDispatcherTest.php | 10 +- .../Fragment/InlineFragmentRendererTest.php | 11 +- 15 files changed, 341 insertions(+), 80 deletions(-) create mode 100644 src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php create mode 100644 src/Symfony/Component/HttpKernel/Controller/ArgumentResolverInterface.php create mode 100644 src/Symfony/Component/HttpKernel/Controller/TraceableArgumentResolver.php create mode 100644 src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolverTest.php diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 64aad65afa18b..1421369ed51b9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -150,9 +150,6 @@ public function load(array $configs, ContainerBuilder $container) $loader->load('debug.xml'); - $definition = $container->findDefinition('http_kernel'); - $definition->replaceArgument(1, new Reference('debug.controller_resolver')); - // replace the regular event_dispatcher service with the debug one $definition = $container->findDefinition('event_dispatcher'); $definition->setPublic(false); @@ -173,6 +170,7 @@ public function load(array $configs, ContainerBuilder $container) 'Symfony\\Component\\HttpKernel\\EventListener\\ResponseListener', 'Symfony\\Component\\HttpKernel\\EventListener\\RouterListener', 'Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver', + 'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver', 'Symfony\\Component\\HttpKernel\\Event\\KernelEvent', 'Symfony\\Component\\HttpKernel\\Event\\FilterControllerEvent', 'Symfony\\Component\\HttpKernel\\Event\\FilterResponseEvent', diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml index d5d7855a23904..3a3034181ab27 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml @@ -17,8 +17,13 @@ - - + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml index 6e8aeb90c5f6d..a5c0baba1b053 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml @@ -13,6 +13,7 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml index 7787e1df599f5..eab11f2dd76df 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/web.xml @@ -17,6 +17,8 @@ + + %kernel.charset% diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php new file mode 100644 index 0000000000000..3a06aae62bcf9 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\HttpFoundation\Request; + +/** + * Responsible for the creation of the action arguments. + * + * @author Iltar van der Berg + */ +class ArgumentResolver implements ArgumentResolverInterface +{ + /** + * {@inheritdoc} + */ + 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()); + } + + protected function doGetArguments(Request $request, $controller, array $parameters) + { + $attributes = $request->attributes->all(); + $arguments = array(); + foreach ($parameters as $param) { + if (array_key_exists($param->name, $attributes)) { + if (PHP_VERSION_ID >= 50600 && $param->isVariadic() && is_array($attributes[$param->name])) { + $arguments = array_merge($arguments, array_values($attributes[$param->name])); + } else { + $arguments[] = $attributes[$param->name]; + } + } elseif ($param->getClass() && $param->getClass()->isInstance($request)) { + $arguments[] = $request; + } elseif ($param->isDefaultValueAvailable()) { + $arguments[] = $param->getDefaultValue(); + } else { + if (is_array($controller)) { + $repr = sprintf('%s::%s()', get_class($controller[0]), $controller[1]); + } elseif (is_object($controller)) { + $repr = get_class($controller); + } else { + $repr = $controller; + } + + throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument (because there is no default value or because there is a non optional argument after this one).', $repr, $param->name)); + } + } + + return $arguments; + } +} diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolverInterface.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolverInterface.php new file mode 100644 index 0000000000000..4aca547b29234 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolverInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\HttpFoundation\Request; + +/** + * An ArgumentResolverInterface implementation knows how to determine the + * arguments for a specific action. + * + * @author Iltar van der Berg + */ +interface ArgumentResolverInterface +{ + /** + * Returns the arguments to pass to the controller. + * + * @param Request $request A Request instance + * @param callable $controller A PHP callable + * + * @return array An array of arguments to pass to the controller + * + * @throws \RuntimeException When value for argument given is not provided + */ + public function getArguments(Request $request, $controller); +} diff --git a/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php b/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php index 56d1b97afdd4b..ead07f702bee4 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php @@ -23,7 +23,7 @@ * * @author Fabien Potencier */ -class ControllerResolver implements ControllerResolverInterface +class ControllerResolver extends ArgumentResolver implements ControllerResolverInterface { private $logger; @@ -84,50 +84,24 @@ public function getController(Request $request) /** * {@inheritdoc} + * + * @deprecated this method is deprecated as of 3.1 and will be removed in 4.0. Implement the ArgumentResolverInterface or extend the ArgumentResolver instead. */ 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); - } + @trigger_error(sprintf('%s is deprecated as of 3.1 and will be removed in 4.0. Implement the %s or extend the %s and inject it in the HttpKernel instead.', __METHOD__, ArgumentResolverInterface::class, ArgumentResolver::class), E_USER_DEPRECATED); - return $this->doGetArguments($request, $controller, $r->getParameters()); + return parent::getArguments($request, $controller); } + /** + * @deprecated this method is deprecated as of 3.1 and will be removed in 4.0. Implement the ArgumentResolverInterface or extend the ArgumentResolver instead. + */ protected function doGetArguments(Request $request, $controller, array $parameters) { - $attributes = $request->attributes->all(); - $arguments = array(); - foreach ($parameters as $param) { - if (array_key_exists($param->name, $attributes)) { - if (PHP_VERSION_ID >= 50600 && $param->isVariadic() && is_array($attributes[$param->name])) { - $arguments = array_merge($arguments, array_values($attributes[$param->name])); - } else { - $arguments[] = $attributes[$param->name]; - } - } elseif ($param->getClass() && $param->getClass()->isInstance($request)) { - $arguments[] = $request; - } elseif ($param->isDefaultValueAvailable()) { - $arguments[] = $param->getDefaultValue(); - } else { - if (is_array($controller)) { - $repr = sprintf('%s::%s()', get_class($controller[0]), $controller[1]); - } elseif (is_object($controller)) { - $repr = get_class($controller); - } else { - $repr = $controller; - } - - throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument (because there is no default value or because there is a non optional argument after this one).', $repr, $param->name)); - } - } + @trigger_error(sprintf('%s is deprecated as of 3.1 and will be removed in 4.0. Implement the %s or extend the %s and inject it in the HttpKernel instead.', __METHOD__, ArgumentResolverInterface::class, ArgumentResolver::class), E_USER_DEPRECATED); - return $arguments; + return parent::doGetArguments($request, $controller, $parameters); } /** diff --git a/src/Symfony/Component/HttpKernel/Controller/ControllerResolverInterface.php b/src/Symfony/Component/HttpKernel/Controller/ControllerResolverInterface.php index f7b19ed1bdbac..ee2028c33d4b9 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ControllerResolverInterface.php +++ b/src/Symfony/Component/HttpKernel/Controller/ControllerResolverInterface.php @@ -42,16 +42,4 @@ interface ControllerResolverInterface * @throws \LogicException If the controller can't be found */ public function getController(Request $request); - - /** - * Returns the arguments to pass to the controller. - * - * @param Request $request A Request instance - * @param callable $controller A PHP callable - * - * @return array An array of arguments to pass to the controller - * - * @throws \RuntimeException When value for argument given is not provided - */ - public function getArguments(Request $request, $controller); } diff --git a/src/Symfony/Component/HttpKernel/Controller/TraceableArgumentResolver.php b/src/Symfony/Component/HttpKernel/Controller/TraceableArgumentResolver.php new file mode 100644 index 0000000000000..cc30ba7f3758a --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Controller/TraceableArgumentResolver.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\HttpFoundation\Request; + +/** + * @author Iltar van der Berg + */ +class TraceableArgumentResolver implements ArgumentResolverInterface +{ + private $resolver; + private $stopwatch; + + public function __construct(ArgumentResolverInterface $resolver, Stopwatch $stopwatch) + { + $this->resolver = $resolver; + $this->stopwatch = $stopwatch; + } + + /** + * {@inheritdoc} + */ + public function getArguments(Request $request, $controller) + { + $e = $this->stopwatch->start('controller.get_arguments'); + + $ret = $this->resolver->getArguments($request, $controller); + + $e->stop(); + + return $ret; + } +} diff --git a/src/Symfony/Component/HttpKernel/Controller/TraceableControllerResolver.php b/src/Symfony/Component/HttpKernel/Controller/TraceableControllerResolver.php index f8de31cf078c1..73671887bbfb1 100644 --- a/src/Symfony/Component/HttpKernel/Controller/TraceableControllerResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/TraceableControllerResolver.php @@ -49,18 +49,4 @@ public function getController(Request $request) return $ret; } - - /** - * {@inheritdoc} - */ - public function getArguments(Request $request, $controller) - { - $e = $this->stopwatch->start('controller.get_arguments'); - - $ret = $this->resolver->getArguments($request, $controller); - - $e->stop(); - - return $ret; - } } diff --git a/src/Symfony/Component/HttpKernel/HttpKernel.php b/src/Symfony/Component/HttpKernel/HttpKernel.php index 1600b2ce591dd..c8bb8cfb912ec 100644 --- a/src/Symfony/Component/HttpKernel/HttpKernel.php +++ b/src/Symfony/Component/HttpKernel/HttpKernel.php @@ -11,6 +11,8 @@ namespace Symfony\Component\HttpKernel; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; @@ -36,19 +38,29 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface protected $dispatcher; protected $resolver; protected $requestStack; + private $argumentResolver; /** * Constructor. * - * @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance - * @param ControllerResolverInterface $resolver A ControllerResolverInterface instance - * @param RequestStack $requestStack A stack for master/sub requests + * @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance + * @param ControllerResolverInterface $resolver A ControllerResolverInterface instance + * @param RequestStack $requestStack A stack for master/sub requests + * @param ArgumentResolverInterface $argumentResolver An ArgumentResolverInterface instance */ - public function __construct(EventDispatcherInterface $dispatcher, ControllerResolverInterface $resolver, RequestStack $requestStack = null) + public function __construct(EventDispatcherInterface $dispatcher, ControllerResolverInterface $resolver, RequestStack $requestStack = null, ArgumentResolverInterface $argumentResolver = null) { $this->dispatcher = $dispatcher; $this->resolver = $resolver; $this->requestStack = $requestStack ?: new RequestStack(); + + if (null === $argumentResolver) { + // fallback in case of deprecations + $argumentResolver = $resolver instanceof ArgumentResolverInterface ? $resolver : new ArgumentResolver(); + } + + $this->argumentResolver = $argumentResolver; + } /** @@ -133,7 +145,7 @@ private function handleRaw(Request $request, $type = self::MASTER_REQUEST) $controller = $event->getController(); // controller arguments - $arguments = $this->resolver->getArguments($request, $controller); + $arguments = $this->argumentResolver->getArguments($request, $controller); // call controller $response = call_user_func_array($controller, $arguments); diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolverTest.php new file mode 100644 index 0000000000000..4945b8a1a10ab --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolverTest.php @@ -0,0 +1,137 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Controller; + +use Symfony\Component\HttpKernel\Controller\ArgumentResolver; +use Symfony\Component\HttpKernel\Controller\ControllerResolver; +use Symfony\Component\HttpKernel\Tests\Fixtures\Controller\VariadicController; +use Symfony\Component\HttpFoundation\Request; + +class ArgumentResolverTest extends \PHPUnit_Framework_TestCase +{ + /** + * @group legacy + */ + public function testGetArguments() + { + $resolver = new ArgumentResolver(); + + $request = Request::create('/'); + $controller = array(new self(), 'testGetArguments'); + $this->assertEquals(array(), $resolver->getArguments($request, $controller), '->getArguments() returns an empty array if the method takes no arguments'); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = array(new self(), 'controllerMethod1'); + $this->assertEquals(array('foo'), $resolver->getArguments($request, $controller), '->getArguments() returns an array of arguments for the controller method'); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = array(new self(), 'controllerMethod2'); + $this->assertEquals(array('foo', null), $resolver->getArguments($request, $controller), '->getArguments() uses default values if present'); + + $request->attributes->set('bar', 'bar'); + $this->assertEquals(array('foo', 'bar'), $resolver->getArguments($request, $controller), '->getArguments() overrides default values if provided in the request attributes'); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = function ($foo) {}; + $this->assertEquals(array('foo'), $resolver->getArguments($request, $controller)); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = function ($foo, $bar = 'bar') {}; + $this->assertEquals(array('foo', 'bar'), $resolver->getArguments($request, $controller)); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = new self(); + $this->assertEquals(array('foo', null), $resolver->getArguments($request, $controller)); + $request->attributes->set('bar', 'bar'); + $this->assertEquals(array('foo', 'bar'), $resolver->getArguments($request, $controller)); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('foobar', 'foobar'); + $controller = 'Symfony\Component\HttpKernel\Tests\Controller\another_controller_function'; + $this->assertEquals(array('foo', 'foobar'), $resolver->getArguments($request, $controller)); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('foobar', 'foobar'); + $controller = array(new self(), 'controllerMethod3'); + + try { + $resolver->getArguments($request, $controller); + $this->fail('->getArguments() throws a \RuntimeException exception if it cannot determine the argument value'); + } catch (\Exception $e) { + $this->assertInstanceOf('\RuntimeException', $e, '->getArguments() throws a \RuntimeException exception if it cannot determine the argument value'); + } + + $request = Request::create('/'); + $controller = array(new self(), 'controllerMethod5'); + $this->assertEquals(array($request), $resolver->getArguments($request, $controller), '->getArguments() injects the request'); + } + + /** + * @requires PHP 5.6 + * @group legacy + */ + public function testGetVariadicArguments() + { + $resolver = new ControllerResolver(); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('bar', array('foo', 'bar')); + $controller = array(new VariadicController(), 'action'); + $this->assertEquals(array('foo', 'foo', 'bar'), $resolver->getArguments($request, $controller)); + } + + public function testCreateControllerCanReturnAnyCallable() + { + $mock = $this->getMock('Symfony\Component\HttpKernel\Controller\ControllerResolver', array('createController')); + $mock->expects($this->once())->method('createController')->will($this->returnValue('Symfony\Component\HttpKernel\Tests\Controller\another_controller_function')); + + $request = Request::create('/'); + $request->attributes->set('_controller', 'foobar'); + $mock->getController($request); + } + + public function __invoke($foo, $bar = null) + { + } + + public function controllerMethod1($foo) + { + } + + protected function controllerMethod2($foo, $bar = null) + { + } + + protected function controllerMethod3($foo, $bar, $foobar) + { + } + + protected static function controllerMethod4() + { + } + + protected function controllerMethod5(Request $request) + { + } +} + +function another_controller_function($foo, $foobar) +{ +} diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php index 7ba1ff6e451d4..1e5fde618aa96 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php @@ -137,6 +137,9 @@ public function getUndefinedControllers() ); } + /** + * @group legacy + */ public function testGetArguments() { $resolver = $this->createControllerResolver(); @@ -200,6 +203,7 @@ public function testGetArguments() /** * @requires PHP 5.6 + * @group legacy */ public function testGetVariadicArguments() { diff --git a/src/Symfony/Component/HttpKernel/Tests/Debug/TraceableEventDispatcherTest.php b/src/Symfony/Component/HttpKernel/Tests/Debug/TraceableEventDispatcherTest.php index f64d7247c0b48..5894ffbb8d744 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Debug/TraceableEventDispatcherTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Debug/TraceableEventDispatcherTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\HttpKernel\Tests\Debug; use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher; use Symfony\Component\HttpKernel\HttpKernel; use Symfony\Component\HttpFoundation\Request; @@ -108,10 +109,11 @@ public function testListenerCanRemoveItselfWhenExecuted() protected function getHttpKernel($dispatcher, $controller) { - $resolver = $this->getMock('Symfony\Component\HttpKernel\Controller\ControllerResolverInterface'); - $resolver->expects($this->once())->method('getController')->will($this->returnValue($controller)); - $resolver->expects($this->once())->method('getArguments')->will($this->returnValue(array())); + $controllerResolver = $this->getMock('Symfony\Component\HttpKernel\Controller\ControllerResolverInterface'); + $controllerResolver->expects($this->once())->method('getController')->will($this->returnValue($controller)); + $argumentResolver = $this->getMock('Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface'); + $argumentResolver->expects($this->once())->method('getArguments')->will($this->returnValue(array())); - return new HttpKernel($dispatcher, $resolver); + return new HttpKernel($dispatcher, $controllerResolver, new RequestStack(), $argumentResolver); } } diff --git a/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php b/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php index 4e487a478a600..a956229406db6 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\HttpKernel\Tests\Fragment; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpKernel\Controller\ControllerReference; use Symfony\Component\HttpKernel\HttpKernel; use Symfony\Component\HttpKernel\Fragment\InlineFragmentRenderer; @@ -142,8 +143,8 @@ private function getKernelExpectingRequest(Request $request) public function testExceptionInSubRequestsDoesNotMangleOutputBuffers() { - $resolver = $this->getMock('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface'); - $resolver + $controllerResolver = $this->getMock('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface'); + $controllerResolver ->expects($this->once()) ->method('getController') ->will($this->returnValue(function () { @@ -152,13 +153,15 @@ public function testExceptionInSubRequestsDoesNotMangleOutputBuffers() throw new \RuntimeException(); })) ; - $resolver + + $argumentResolver = $this->getMock('Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolverInterface'); + $argumentResolver ->expects($this->once()) ->method('getArguments') ->will($this->returnValue(array())) ; - $kernel = new HttpKernel(new EventDispatcher(), $resolver); + $kernel = new HttpKernel(new EventDispatcher(), $controllerResolver, new RequestStack(), $argumentResolver); $renderer = new InlineFragmentRenderer($kernel); // simulate a main request with output buffering From a57d6ae2b23d7c8c1fc3af2d84421070e6314520 Mon Sep 17 00:00:00 2001 From: Iltar van der Berg Date: Wed, 16 Mar 2016 11:28:09 +0100 Subject: [PATCH 2/7] FrameworkBundle now requires http-kernel 3.1 or higher --- src/Symfony/Bundle/FrameworkBundle/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 86752accf1f6a..689aa7d6b65f9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -23,7 +23,7 @@ "symfony/config": "~2.8|~3.0", "symfony/event-dispatcher": "~2.8|~3.0", "symfony/http-foundation": "~3.1", - "symfony/http-kernel": "~2.8|~3.0", + "symfony/http-kernel": "~3.1", "symfony/polyfill-mbstring": "~1.0", "symfony/filesystem": "~2.8|~3.0", "symfony/finder": "~2.8|~3.0", From 4c997f543871fc8a682ddc4058b0dd3b40d1c1b5 Mon Sep 17 00:00:00 2001 From: Iltar van der Berg Date: Wed, 16 Mar 2016 13:46:27 +0100 Subject: [PATCH 3/7] Fixed the authors --- .../Component/HttpKernel/Controller/ArgumentResolver.php | 2 +- .../HttpKernel/Controller/ArgumentResolverInterface.php | 2 +- .../HttpKernel/Controller/TraceableArgumentResolver.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php index 3a06aae62bcf9..a7f4b3c731dde 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolver.php @@ -16,7 +16,7 @@ /** * Responsible for the creation of the action arguments. * - * @author Iltar van der Berg + * @author Fabien Potencier */ class ArgumentResolver implements ArgumentResolverInterface { diff --git a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolverInterface.php b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolverInterface.php index 4aca547b29234..19d23aef841ae 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ArgumentResolverInterface.php +++ b/src/Symfony/Component/HttpKernel/Controller/ArgumentResolverInterface.php @@ -17,7 +17,7 @@ * An ArgumentResolverInterface implementation knows how to determine the * arguments for a specific action. * - * @author Iltar van der Berg + * @author Fabien Potencier */ interface ArgumentResolverInterface { diff --git a/src/Symfony/Component/HttpKernel/Controller/TraceableArgumentResolver.php b/src/Symfony/Component/HttpKernel/Controller/TraceableArgumentResolver.php index cc30ba7f3758a..6fb0fa66aca7a 100644 --- a/src/Symfony/Component/HttpKernel/Controller/TraceableArgumentResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/TraceableArgumentResolver.php @@ -15,7 +15,7 @@ use Symfony\Component\HttpFoundation\Request; /** - * @author Iltar van der Berg + * @author Fabien Potencier */ class TraceableArgumentResolver implements ArgumentResolverInterface { From 70c6341c4a80cce52db3f86f1ec3ff00c545dfdf Mon Sep 17 00:00:00 2001 From: Iltar van der Berg Date: Wed, 16 Mar 2016 14:04:23 +0100 Subject: [PATCH 4/7] Fixed a failing test due to partial mocking --- .../HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php b/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php index a956229406db6..ee25b83167496 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\HttpKernel\Tests\Fragment; use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver; use Symfony\Component\HttpKernel\Controller\ControllerReference; use Symfony\Component\HttpKernel\HttpKernel; use Symfony\Component\HttpKernel\Fragment\InlineFragmentRenderer; @@ -61,7 +62,7 @@ public function testRenderWithObjectsAsAttributesPassedAsObjectsInTheController( })) ; - $kernel = new HttpKernel(new EventDispatcher(), $resolver); + $kernel = new HttpKernel(new EventDispatcher(), $resolver, new RequestStack(), new ArgumentResolver()); $renderer = new InlineFragmentRenderer($kernel); $response = $renderer->render(new ControllerReference('main_controller', array('object' => new \stdClass(), 'object1' => new Bar()), array()), Request::create('/')); From b3ed1f08e5b49034310aece218d87813ab0d4231 Mon Sep 17 00:00:00 2001 From: Iltar van der Berg Date: Thu, 24 Mar 2016 11:24:07 +0100 Subject: [PATCH 5/7] Updated changelog/upgrade files --- UPGRADE-3.1.md | 4 ++++ src/Symfony/Component/HttpKernel/CHANGELOG.md | 3 +++ 2 files changed, 7 insertions(+) diff --git a/UPGRADE-3.1.md b/UPGRADE-3.1.md index 2bf81288c639e..3c6a3d851cde1 100644 --- a/UPGRADE-3.1.md +++ b/UPGRADE-3.1.md @@ -31,6 +31,10 @@ HttpKernel * Passing objects as URI attributes to the ESI and SSI renderers has been deprecated and will be removed in Symfony 4.0. The inline fragment renderer should be used with object attributes. + * The `ControllerResolver::getArguments()` method is deprecated and will be + removed in 4.0. If you have your own `ControllerResolverInterface` + implementation, you should replace this method by implementing the + `ArgumentResolverInterface` and injecting it in the HttpKernel. Serializer ---------- diff --git a/src/Symfony/Component/HttpKernel/CHANGELOG.md b/src/Symfony/Component/HttpKernel/CHANGELOG.md index c41184b2c7d20..61414eaf1ea50 100644 --- a/src/Symfony/Component/HttpKernel/CHANGELOG.md +++ b/src/Symfony/Component/HttpKernel/CHANGELOG.md @@ -4,6 +4,9 @@ CHANGELOG 3.1.0 ----- * deprecated passing objects as URI attributes to the ESI and SSI renderers + * Added an `ArgumentResolver` with `getArguments()` and the respective interface `ArgumentResolverInterface` + * Deprecated `ControllerResolver::getArguments()`, which uses the `ArgumentResolver` as BC layer by extending it + * The `HttpKernel` now accepts an additional argument for an `ArgumentResolver` 3.0.0 ----- From 750dae945a8cf2ee62813b2d4dc696846f6a5c9b Mon Sep 17 00:00:00 2001 From: Iltar van der Berg Date: Thu, 31 Mar 2016 09:12:49 +0200 Subject: [PATCH 6/7] Improved the BC layer --- .../Resources/config/debug.xml | 1 + .../ControllerResolverInterface.php | 14 +++++++ .../TraceableControllerResolver.php | 37 +++++++++++++++++-- 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml index 3a3034181ab27..b6e7c0599bc7e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml @@ -20,6 +20,7 @@ + diff --git a/src/Symfony/Component/HttpKernel/Controller/ControllerResolverInterface.php b/src/Symfony/Component/HttpKernel/Controller/ControllerResolverInterface.php index ee2028c33d4b9..0dd7cce96d905 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ControllerResolverInterface.php +++ b/src/Symfony/Component/HttpKernel/Controller/ControllerResolverInterface.php @@ -42,4 +42,18 @@ interface ControllerResolverInterface * @throws \LogicException If the controller can't be found */ public function getController(Request $request); + + /** + * Returns the arguments to pass to the controller. + * + * @param Request $request A Request instance + * @param callable $controller A PHP callable + * + * @return array An array of arguments to pass to the controller + * + * @throws \RuntimeException When value for argument given is not provided + * + * @deprecated This method is deprecated as of 3.1 and will be removed in 4.0. Please use the {@see ArgumentResolverInterface} instead. + */ + public function getArguments(Request $request, $controller); } diff --git a/src/Symfony/Component/HttpKernel/Controller/TraceableControllerResolver.php b/src/Symfony/Component/HttpKernel/Controller/TraceableControllerResolver.php index 73671887bbfb1..469668ce7f5e5 100644 --- a/src/Symfony/Component/HttpKernel/Controller/TraceableControllerResolver.php +++ b/src/Symfony/Component/HttpKernel/Controller/TraceableControllerResolver.php @@ -19,21 +19,28 @@ * * @author Fabien Potencier */ -class TraceableControllerResolver implements ControllerResolverInterface +class TraceableControllerResolver implements ControllerResolverInterface, ArgumentResolverInterface { private $resolver; private $stopwatch; + private $argumentResolver; /** * Constructor. * - * @param ControllerResolverInterface $resolver A ControllerResolverInterface instance - * @param Stopwatch $stopwatch A Stopwatch instance + * @param ControllerResolverInterface $resolver A ControllerResolverInterface instance + * @param Stopwatch $stopwatch A Stopwatch instance + * @param ArgumentResolverInterface $argumentResolver Only required for BC */ - public function __construct(ControllerResolverInterface $resolver, Stopwatch $stopwatch) + public function __construct(ControllerResolverInterface $resolver, Stopwatch $stopwatch, ArgumentResolverInterface $argumentResolver = null) { $this->resolver = $resolver; $this->stopwatch = $stopwatch; + $this->argumentResolver = $argumentResolver; + + if (null === $this->argumentResolver) { + $this->argumentResolver = $resolver; + } } /** @@ -49,4 +56,26 @@ public function getController(Request $request) return $ret; } + + /** + * {@inheritdoc} + * + * @deprecated This method is deprecated as of 3.1 and will be removed in 4.0. + */ + public function getArguments(Request $request, $controller) + { + @trigger_error(sprintf('This %s method is deprecated as of 3.1 and will be removed in 4.0. Please use the %s instead.', __METHOD__, TraceableArgumentResolver::class), E_USER_DEPRECATED); + + if ($this->argumentResolver instanceof TraceableArgumentResolver) { + return $this->argumentResolver->getArguments($request, $controller); + } + + $e = $this->stopwatch->start('controller.get_arguments'); + + $ret = $this->argumentResolver->getArguments($request, $controller); + + $e->stop(); + + return $ret; + } } From c20eb4bf521c3c7ec8f1f658b8f20852c4d688c9 Mon Sep 17 00:00:00 2001 From: Iltar van der Berg Date: Thu, 31 Mar 2016 09:37:25 +0200 Subject: [PATCH 7/7] Fixed the BC layer in HttpKernel --- src/Symfony/Component/HttpKernel/HttpKernel.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/HttpKernel.php b/src/Symfony/Component/HttpKernel/HttpKernel.php index c8bb8cfb912ec..c9b1d65227f4b 100644 --- a/src/Symfony/Component/HttpKernel/HttpKernel.php +++ b/src/Symfony/Component/HttpKernel/HttpKernel.php @@ -53,14 +53,13 @@ public function __construct(EventDispatcherInterface $dispatcher, ControllerReso $this->dispatcher = $dispatcher; $this->resolver = $resolver; $this->requestStack = $requestStack ?: new RequestStack(); + $this->argumentResolver = $argumentResolver; - if (null === $argumentResolver) { + if (null === $this->argumentResolver) { + @trigger_error(sprintf('As of 3.1 an %s is used to resolve arguments. In 4.0 the $argumentResolver becomes mandatory and the %s can no longer be used to resolve arguments.', ArgumentResolverInterface::class, ControllerResolverInterface::class), E_USER_DEPRECATED); // fallback in case of deprecations - $argumentResolver = $resolver instanceof ArgumentResolverInterface ? $resolver : new ArgumentResolver(); + $this->argumentResolver = $resolver; } - - $this->argumentResolver = $argumentResolver; - } /**