From baf07a13acd1270c23b615f8d3077ad560911a35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Bj=C3=B8rnskov?= Date: Tue, 21 Dec 2010 12:59:28 +0100 Subject: [PATCH] added converter manager and converter interface incl. tests --- .../Controller/ParamConverterListener.php | 79 ++++++++++++++++++ .../FrameworkExtension.php | 16 ++++ .../Converter/ConverterInterface.php | 24 ++++++ .../ParamConverter/ConverterManager.php | 81 +++++++++++++++++++ .../Resources/config/param_converter.xml | 23 ++++++ .../ParamConverter/ConverterManagerTest.php | 77 ++++++++++++++++++ .../Fixtures/ConvertableObject.php | 10 +++ 7 files changed, 310 insertions(+) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Controller/ParamConverterListener.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/ParamConverter/Converter/ConverterInterface.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/ParamConverter/ConverterManager.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Resources/config/param_converter.xml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/ParamConverter/ConverterManagerTest.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/ParamConverter/Fixtures/ConvertableObject.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/ParamConverterListener.php b/src/Symfony/Bundle/FrameworkBundle/Controller/ParamConverterListener.php new file mode 100644 index 000000000000..ce337e8fcfac --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/ParamConverterListener.php @@ -0,0 +1,79 @@ + + * @author Henrik Bjornskov + */ +class ParamConverterListener +{ + /** + * @var ConverterManager + */ + protected $manager; + + /** + * @param ConverterManager $manager + * @param ContainerInterface $container + */ + public function __construct(ConverterManager $manager, ContainerInterface $container) + { + foreach ($container->findTaggedServiceIds('param_converter.converter') as $id => $attributes) { + $priority = isset($attributes['priority']) ? (integer) $attributes['priority'] : 0; + $manager->add($container->get($id), $priority); + } + + $this->manager = $manager; + } + + /** + * @param EventDispatcher $dispatcher + * @param integer $priority = 0 + */ + public function register(EventDispatcher $dispatcher, $priority = 0) + { + $dispatcher->connect('core.controller', array($this, 'filterController'), $priority); + } + + /** + * @param Event $event + * @param mixed $controller + * @throws NotFoundHttpException + * @return mixed + */ + public function filterController(Event $event, $controller) + { + if (!is_array($controller)) { + return $controller; + } + + $request = $event->get('request'); + $method = new \ReflectionMethod($controller[0], $controller[1]); + + foreach ($method->getParameters() as $param) { + if (null !== $param->getClass() && false === $request->attributes->has($param->getName())) { + try { + $this->manager->apply($request, $param); + } catch (\InvalidArgumentException $e) { + if (false == $param->isOptional()) { + throw new NotFoundHttpException($e->getMessage()); + } + } + } + } + + return $controller; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 496785593dee..d25b760a44fc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -112,6 +112,10 @@ public function configLoad($config, ContainerBuilder $container) $this->registerTestConfiguration($config, $container); } + if (array_key_exists('param_converter', $config)) { + $this->registerParamConverterConfiguration($config, $container); + } + $this->registerSessionConfiguration($config, $container); $this->registerTranslatorConfiguration($config, $container); @@ -141,6 +145,18 @@ public function configLoad($config, ContainerBuilder $container) )); } + /** + * Loads the parameter converter configuration. + * + * @param array $config An array of configuration settings + * @param ContainerBuilder $container A ContainerBuilder instance + */ + protected function registerParamConverterConfiguration($config, ContainerBuilder $container) + { + $loader = new XmlFileLoader($container, __DIR__.'/../Resources/config'); + $loader->load('param_converter.xml'); + } + /** * Loads the templating configuration. * diff --git a/src/Symfony/Bundle/FrameworkBundle/ParamConverter/Converter/ConverterInterface.php b/src/Symfony/Bundle/FrameworkBundle/ParamConverter/Converter/ConverterInterface.php new file mode 100644 index 000000000000..0e1dac524011 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/ParamConverter/Converter/ConverterInterface.php @@ -0,0 +1,24 @@ + + * @author Henrik Bjornskov + */ +class ConverterManager +{ + /** + * @var array + */ + protected $converters = array(); + + /** + * Cycles through all converters and if a converter supports the class it applies + * the converter. If no converter matches the ReflectionParameters::getClass() value + * a InvalidArgumentException is thrown. + * + * @param Request $request + * @param array $reflectionParam An array of ReflectionParameter objects + * @throws InvalidArgumentException + */ + public function apply(Request $request, \ReflectionParameter $reflectionParam) + { + $converted = false; + $converters = $this->all(); + $reflectionClass = $reflectionParam->getClass(); + + foreach ($this->all() as $converter) { + if ($converter->supports($reflectionClass)) { + $converter->apply($request, $reflectionParam); + $converted = true; + } + } + + if (true !== $converted) { + throw new \InvalidArgumentException(sprintf('Could not convert attribute "%s" into an instance of "%s"', $reflectionParam->getName(), $reflectionClass->getName())); + } + } + + /** + * Add a converter + * + * @param ConverterInterface $converter + * @param integer $prioriry = 0 + */ + public function add(ConverterInterface $converter, $priority = 0) + { + if (!isset($this->converters[$priority])) { + $this->converters[$priority] = array(); + } + + $this->converters[$priority][] = $converter; + } + + /** + * Returns all converters sorted after their priorities + * + * @return array + */ + public function all() + { + $all = $this->converters; + $converters = array(); + krsort($this->converters); + + foreach ($all as $c) { + $converters = array_merge($converters, $c); + } + + return $converters; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/param_converter.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/param_converter.xml new file mode 100644 index 000000000000..b671a29eb65d --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/param_converter.xml @@ -0,0 +1,23 @@ + + + + + + Symfony\Bundle\FrameworkBundle\ParamConverter\ConverterManager + Symfony\Bundle\FrameworkBundle\Controller\ParamConverterListener + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/ParamConverter/ConverterManagerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/ParamConverter/ConverterManagerTest.php new file mode 100644 index 000000000000..e3adcbd732f3 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/ParamConverter/ConverterManagerTest.php @@ -0,0 +1,77 @@ +getConverterInterfaceMock(); + $lessImportantConverter = $this->getConverterInterfaceMock(); + + $manager->add($importantConverter, 10); + + $this->assertEquals($manager->all(), array($importantConverter)); + + $manager->add($lessImportantConverter); + + $this->assertEquals($manager->all(), array( + $importantConverter, + $lessImportantConverter, + )); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testManagerCantApplyConvertersAndThrowsException() + { + $request = new Request(); + $parameter = $this->getReflectionParameter(); + + $converter = $this->getConverterInterfaceMock(); + $converter->expects($this->once()) + ->method('supports') + ->with($parameter->getClass()) + ->will($this->returnValue(false)); + + $manager = new ConverterManager(); + $manager->add($converter); + $manager->apply($request, $parameter); + } + + public function testManagerWillApplyConvertersSuccessfully() + { + $request = new Request(); + $parameter = $this->getReflectionParameter(); + + $converter = $this->getConverterInterfaceMock(); + $converter->expects($this->once()) + ->method('supports') + ->with($parameter->getClass()) + ->will($this->returnValue(true)); + + $converter->expects($this->once()) + ->method('apply') + ->with($request, $parameter) + ->will($this->returnValue(null)); + + $manager = new ConverterManager(); + $manager->add($converter); + $manager->apply($request, $parameter); + } + + private function getReflectionParameter() + { + return new \ReflectionParameter(array('Symfony\Bundle\FrameworkBundle\Tests\ParamConverter\Fixtures\ConvertableObject', 'typehintedMethod'), 'object'); + } + + private function getConverterInterfaceMock() + { + return $this->getMock('Symfony\Bundle\FrameworkBundle\ParamConverter\Converter\ConverterInterface'); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/ParamConverter/Fixtures/ConvertableObject.php b/src/Symfony/Bundle/FrameworkBundle/Tests/ParamConverter/Fixtures/ConvertableObject.php new file mode 100644 index 000000000000..a33004ebbafb --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/ParamConverter/Fixtures/ConvertableObject.php @@ -0,0 +1,10 @@ +