Skip to content

Commit

Permalink
added converter manager and converter interface incl. tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Henrik Bjørnskov authored and fabpot committed Dec 22, 2010
1 parent 763ef35 commit baf07a1
Show file tree
Hide file tree
Showing 7 changed files with 310 additions and 0 deletions.
@@ -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;
}
}
Expand Up @@ -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);
Expand Down Expand Up @@ -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.
*
Expand Down
@@ -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);
}
@@ -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;
}
}
@@ -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>
@@ -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');
}
}
@@ -0,0 +1,10 @@
<?php

namespace Symfony\Bundle\FrameworkBundle\Tests\ParamConverter\Fixtures;

class ConvertableObject
{
public function typehintedMethod(ConvertableObject $object)
{
}
}

0 comments on commit baf07a1

Please sign in to comment.