Procházet zdrojové kódy

added converter manager and converter interface incl. tests

Henrik Bjørnskov před 14 roky
rodič
revize
baf07a13ac

+ 79 - 0
src/Symfony/Bundle/FrameworkBundle/Controller/ParamConverterListener.php

@@ -0,0 +1,79 @@
+<?php
+
+namespace Symfony\Bundle\FrameworkBundle\Controller;
+
+use Symfony\Bundle\FrameworkBundle\ParamConverter\ConverterManager;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\EventDispatcher\Event;
+
+/**
+ * Converts \ReflectionParameters for Controller actions into Objects if the ReflectionParameter have a class
+ * (Typehinted).
+ *
+ * @author Fabien Potencier <fabien.potencier@symfony-project.org>
+ * @author Henrik Bjornskov <hb@peytz.dk>
+ */
+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;
+    }
+}

+ 16 - 0
src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

@@ -112,6 +112,10 @@ class FrameworkExtension extends Extension
             $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 @@ class FrameworkExtension extends Extension
         ));
     }
 
+    /**
+     * 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.
      *

+ 24 - 0
src/Symfony/Bundle/FrameworkBundle/ParamConverter/Converter/ConverterInterface.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace Symfony\Bundle\FrameworkBundle\ParamConverter\Converter;
+
+use Symfony\Component\HttpFoundation\Request;
+
+interface ConverterInterface
+{
+    /**
+     * Convert the \ReflectionPropertt to something else.
+     *
+     * @param Request              $request
+     * @param \ReflectionParameter $property
+     */
+    function apply(Request $request, \ReflectionParameter $parameter);
+
+    /**
+     * Returns boolean true if the ReflectionProperty is supported. Else false
+     *
+     * @param  \ReflectionParameter $parameter
+     * @return boolean
+     */
+    function supports(\ReflectionClass $class);
+}

+ 81 - 0
src/Symfony/Bundle/FrameworkBundle/ParamConverter/ConverterManager.php

@@ -0,0 +1,81 @@
+<?php
+
+namespace Symfony\Bundle\FrameworkBundle\ParamConverter;
+
+use Symfony\Bundle\FrameworkBundle\ParamConverter\Converter\ConverterInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * ConverterManager
+ * Keeps track of converters
+ *
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @author Henrik Bjornskov <hb@peytz.dk>
+ */
+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;
+    }
+}

+ 23 - 0
src/Symfony/Bundle/FrameworkBundle/Resources/config/param_converter.xml

@@ -0,0 +1,23 @@
+<?xml version="1.0" ?>
+
+<container xmlns="http://www.symfony-project.org/schema/dic/services"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.symfony-project.org/schema/dic/services http://www.symfony-project.org/schema/dic/services/services-1.0.xsd">
+
+    <parameters>
+        <parameter key="param_converter.converter_manager.class">Symfony\Bundle\FrameworkBundle\ParamConverter\ConverterManager</parameter>
+        <parameter key="param_converter.controller.param_converter_listener.class">Symfony\Bundle\FrameworkBundle\Controller\ParamConverterListener</parameter>
+    </parameters>
+
+    <services>
+        <!-- ConverterManager -->
+        <service id="param_converter.converter_manager" class="%param_converter.converter_manager.class%" />
+
+        <!-- ParamConverterListener -->
+        <service id="param_converter.controller.param_converter_listener" class="%param_converter.controller.param_converter_listener.class%">
+            <tag name="kernel.listener" />
+            <argument type="service" id="param_converter.converter_manager" />
+            <argument type="service" id="service_container" />
+        </service>
+    </services>
+</container>

+ 77 - 0
src/Symfony/Bundle/FrameworkBundle/Tests/ParamConverter/ConverterManagerTest.php

@@ -0,0 +1,77 @@
+<?php
+
+namespace Symfony\Bundle\FrameworkBundle\Tests\ParamConverter;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Bundle\FrameworkBundle\ParamConverter\ConverterManager;
+
+class ConverterManagerTest extends \PHPUnit_Framework_TestCase
+{
+    public function testManagerCanContainerConverters()
+    {
+        $manager = new ConverterManager();
+        $importantConverter = $this->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');
+    }
+}

+ 10 - 0
src/Symfony/Bundle/FrameworkBundle/Tests/ParamConverter/Fixtures/ConvertableObject.php

@@ -0,0 +1,10 @@
+<?php
+
+namespace Symfony\Bundle\FrameworkBundle\Tests\ParamConverter\Fixtures;
+
+class ConvertableObject
+{
+    public function typehintedMethod(ConvertableObject $object)
+    {
+    }
+}