Vendors update

Signed-off-by: Polonkai Gergely <polesz@w00d5t0ck.info>
This commit is contained in:
Polonkai Gergely
2012-07-31 09:40:41 +02:00
parent 33b90a5c9f
commit 623b78e939
234 changed files with 3120 additions and 1292 deletions

View File

@@ -49,6 +49,13 @@ class ParamConverter extends ConfigurationAnnotation
*/
protected $optional = false;
/**
* Use explicitly named converter instead of iterating by priorities.
*
* @var string
*/
protected $converter;
/**
* Returns the parameter name.
*
@@ -139,6 +146,26 @@ class ParamConverter extends ConfigurationAnnotation
return $this->optional;
}
/**
* Get explicit converter name.
*
* @return string
*/
public function getConverter()
{
return $this->converter;
}
/**
* Set explicit converter name
*
* @param string $converter
*/
public function setConverter($converter)
{
$this->converter = $converter;
}
/**
* Returns the annotation alias name.
*

View File

@@ -29,8 +29,18 @@ class AddParamConverterPass implements CompilerPassInterface
}
$definition = $container->getDefinition('sensio_framework_extra.converter.manager');
foreach ($container->findTaggedServiceIds('request.param_converter') as $id => $attributes) {
$definition->addMethodCall('add', array(new Reference($id), isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0));
foreach ($container->findTaggedServiceIds('request.param_converter') as $id => $converters) {
foreach ($converters as $converter) {
$name = isset($converter['converter']) ? $converter['converter'] : null;
$priority = isset($converter['priority']) ? $converter['priority'] : 0;
if ($priority === "false") {
$priority = null;
}
$definition->addMethodCall('add', array(new Reference($id), $priority, $name));
}
}
}
}

View File

@@ -5,6 +5,7 @@ namespace Sensio\Bundle\FrameworkExtraBundle\EventListener;
use Doctrine\Common\Annotations\Reader;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ConfigurationInterface;
use Doctrine\Common\Util\ClassUtils;
/*
* This file is part of the Symfony framework.
@@ -51,14 +52,33 @@ class ControllerListener
return;
}
$object = new \ReflectionObject($controller[0]);
$method = $object->getMethod($controller[1]);
$className = class_exists('Doctrine\Common\Util\ClassUtils') ? ClassUtils::getClass($controller[0]) : get_class($controller[0]);
$object = new \ReflectionClass($className);
$method = $object->getMethod($controller[1]);
$classConfigurations = $this->getConfigurations($this->reader->getClassAnnotations($object));
$methodConfigurations = $this->getConfigurations($this->reader->getMethodAnnotations($method));
$configurations = array_merge($classConfigurations, $methodConfigurations);
$request = $event->getRequest();
foreach (array_merge($this->reader->getClassAnnotations($object), $this->reader->getMethodAnnotations($method)) as $configuration) {
if ($configuration instanceof ConfigurationInterface) {
$request->attributes->set('_'.$configuration->getAliasName(), $configuration);
foreach ($configurations as $key => $attributes) {
if (is_array($attributes) && count($attributes) == 1) {
$attributes = $attributes[0];
}
$request->attributes->set($key, $attributes);
}
}
protected function getConfigurations(array $annotations)
{
$configurations = array();
foreach ($annotations as $configuration) {
if ($configuration instanceof ConfigurationInterface) {
$configurations['_'.$configuration->getAliasName()][] = $configuration;
}
}
return $configurations;
}
}

View File

@@ -63,7 +63,7 @@ class ParamConverterListener
// automatically apply conversion for non-configured objects
foreach ($r->getParameters() as $param) {
if (!$param->getClass()) {
if (!$param->getClass() || $param->getClass()->isInstance($request)) {
continue;
}
@@ -75,6 +75,8 @@ class ParamConverterListener
$configuration->setClass($param->getClass()->getName());
$configurations[$name] = $configuration;
} elseif (null === $configurations[$name]->getClass()) {
$configurations[$name]->setClass($param->getClass()->getName());
}
$configurations[$name]->setIsOptional($param->isOptional());

View File

@@ -0,0 +1,59 @@
<?php
/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ConfigurationInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use DateTime;
/**
* Convert DateTime instances from request attribute variable.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class DateTimeParamConverter implements ParamConverterInterface
{
public function apply(Request $request, ConfigurationInterface $configuration)
{
$param = $configuration->getName();
if (!$request->attributes->has($param)) {
return false;
}
$options = $configuration->getOptions();
$value = $request->attributes->get($param);
$date = isset($options['format'])
? DateTime::createFromFormat($options['format'], $value)
: new DateTime($value);
if (!$date) {
throw new NotFoundHttpException('Invalid date given.');
}
$request->attributes->set($param, $date);
return true;
}
public function supports(ConfigurationInterface $configuration)
{
if (null === $configuration->getClass()) {
return false;
}
return "DateTime" === $configuration->getClass();
}
}

View File

@@ -35,14 +35,19 @@ class DoctrineParamConverter implements ParamConverterInterface
public function apply(Request $request, ConfigurationInterface $configuration)
{
$class = $configuration->getClass();
$name = $configuration->getName();
$class = $configuration->getClass();
$options = $this->getOptions($configuration);
// find by identifier?
if (false === $object = $this->find($class, $request, $options)) {
if (false === $object = $this->find($class, $request, $options, $name)) {
// find by criteria
if (false === $object = $this->findOneBy($class, $request, $options)) {
throw new \LogicException('Unable to guess how to get a Doctrine instance from the request information.');
if ($configuration->isOptional()) {
$object = null;
} else {
throw new \LogicException('Unable to guess how to get a Doctrine instance from the request information.');
}
}
}
@@ -50,28 +55,72 @@ class DoctrineParamConverter implements ParamConverterInterface
throw new NotFoundHttpException(sprintf('%s object not found.', $class));
}
$request->attributes->set($configuration->getName(), $object);
$request->attributes->set($name, $object);
return true;
}
protected function find($class, Request $request, $options)
protected function find($class, Request $request, $options, $name)
{
$key = isset($options['id']) ? $options['id'] : 'id';
if (!$request->attributes->has($key)) {
if ($options['mapping'] || $options['exclude']) {
return false;
}
return $this->registry->getRepository($class, $options['entity_manager'])->find($request->attributes->get($key));
$id = $this->getIdentifier($request, $options, $name);
if (!$id) {
return false;
}
return $this->registry->getRepository($class, $options['entity_manager'])->find($id);
}
protected function getIdentifier(Request $request, $options, $name)
{
if (isset($options['id'])) {
if (!is_array($options['id'])) {
$name = $options['id'];
} elseif (is_array($options['id'])) {
$id = array();
foreach ($options['id'] as $field) {
$id[$field] = $request->attributes->get($field);
}
return $id;
}
}
if ($request->attributes->has($name)) {
return $request->attributes->get($name);
}
if ($request->attributes->has('id')) {
return $request->attributes->get('id');
}
return false;
}
protected function findOneBy($class, Request $request, $options)
{
if (!$options['mapping']) {
$keys = $request->attributes->keys();
$options['mapping'] = $keys ? array_combine($keys, $keys) : array();
}
foreach ($options['exclude'] as $exclude) {
unset($options['mapping'][$exclude]);
}
if (!$options['mapping']) {
return false;
}
$criteria = array();
$metadata = $this->registry->getManager($options['entity_manager'])->getClassMetadata($class);
foreach ($request->attributes->all() as $key => $value) {
if ($metadata->hasField($key)) {
$criteria[$key] = $value;
foreach ($options['mapping'] as $attribute => $field) {
if ($metadata->hasField($field) || ($metadata->hasAssociation($field) && $metadata->isSingleValuedAssociation($field))) {
$criteria[$field] = $request->attributes->get($attribute);
}
}
@@ -84,7 +133,8 @@ class DoctrineParamConverter implements ParamConverterInterface
public function supports(ConfigurationInterface $configuration)
{
if (null === $this->registry) {
// if there is no manager, this means that only Doctrine DBAL is configured
if (null === $this->registry || !count($this->registry->getManagers())) {
return false;
}
@@ -104,6 +154,8 @@ class DoctrineParamConverter implements ParamConverterInterface
{
return array_replace(array(
'entity_manager' => null,
'exclude' => array(),
'mapping' => array(),
), $configuration->getOptions());
}
}

View File

@@ -26,6 +26,11 @@ class ParamConverterManager
*/
protected $converters = array();
/**
* @var array
*/
protected $namedConverters = array();
/**
* Applies all converters to the passed configurations and stops when a
* converter is applied it will move on to the next configuration and so on.
@@ -40,19 +45,52 @@ class ParamConverterManager
}
foreach ($configurations as $configuration) {
// If the value is already an instance of the class we are trying to convert it into
// we should continue as no convertion is required
$value = $request->attributes->get($configuration->getName());
$className = $configuration->getClass();
if (is_object($value) && $value instanceof $className) {
continue;
$this->applyConverter($request, $configuration);
}
}
/**
* Apply converter on request based on the given configuration.
*
* @param Request $request
* @param ConfigurationInterface $configuration
*/
protected function applyConverter(Request $request, $configuration)
{
$value = $request->attributes->get($configuration->getName());
$className = $configuration->getClass();
// If the value is already an instance of the class we are trying to convert it into
// we should continue as no convertion is required
if (is_object($value) && $value instanceof $className) {
return;
}
if ($converterName = $configuration->getConverter()) {
if (!isset($this->namedConverters[$converterName])) {
throw new \RuntimeException(sprintf(
"No converter named '%s' found for conversion of parameter '%s'.",
$converterName, $configuration->getName()
));
}
foreach ($this->all() as $converter) {
if ($converter->supports($configuration)) {
if ($converter->apply($request, $configuration)) {
continue 2;
}
$converter = $this->namedConverters[$converterName];
if (!$converter->supports($configuration)) {
throw new \RuntimeException(sprintf(
"Converter '%s' does not support conversion of parameter '%s'.",
$converterName, $configuration->getName()
));
}
$converter->apply($request, $configuration);
return;
}
foreach ($this->all() as $converter) {
if ($converter->supports($configuration)) {
if ($converter->apply($request, $configuration)) {
return;
}
}
}
@@ -61,16 +99,28 @@ class ParamConverterManager
/**
* Adds a parameter converter.
*
* Converters match either explicitly via $name or by iteration over all
* converters with a $priority. If you pass a $priority = null then the
* added converter will not be part of the iteration chain and can only
* be invoked explicitly.
*
* @param ParamConverterInterface $converter A ParamConverterInterface instance
* @param integer $priority The priority (between -10 and 10)
* @param integer $priority The priority (between -10 and 10).
* @param string $name Name of the converter.
*/
public function add(ParamConverterInterface $converter, $priority = 0)
public function add(ParamConverterInterface $converter, $priority = 0, $name = null)
{
if (!isset($this->converters[$priority])) {
$this->converters[$priority] = array();
}
if ($priority !== null) {
if (!isset($this->converters[$priority])) {
$this->converters[$priority] = array();
}
$this->converters[$priority][] = $converter;
$this->converters[$priority][] = $converter;
}
if (null !== $name) {
$this->namedConverters[$name] = $converter;
}
}
/**

View File

@@ -8,6 +8,7 @@
<parameter key="sensio_framework_extra.converter.listener.class">Sensio\Bundle\FrameworkExtraBundle\EventListener\ParamConverterListener</parameter>
<parameter key="sensio_framework_extra.converter.manager.class">Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterManager</parameter>
<parameter key="sensio_framework_extra.converter.doctrine.class">Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\DoctrineParamConverter</parameter>
<parameter key="sensio_framework_extra.converter.datetime.class">Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\DateTimeParamConverter</parameter>
</parameters>
<services>
@@ -19,8 +20,12 @@
<service id="sensio_framework_extra.converter.manager" class="%sensio_framework_extra.converter.manager.class%" />
<service id="sensio_framework_extra.converter.doctrine.orm" class="%sensio_framework_extra.converter.doctrine.class%">
<tag name="request.param_converter" />
<tag name="request.param_converter" converter="doctrine.orm" />
<argument type="service" id="doctrine" on-invalid="ignore" />
</service>
<service id="sensio_framework_extra.converter.datetime" class="%sensio_framework_extra.converter.datetime.class%">
<tag name="request.param_converter" converter="datetime" />
</service>
</services>
</container>

View File

@@ -41,14 +41,42 @@ If you use type hinting as in the example above, you can even omit the
{
}
To detect which converter is run on a parameter the following process is run:
* If an explicit converter choice was made with
``@ParamConverter(converter="name")`` the converter with the given name is
chosen.
* Otherwise all registered parameter converters are iterated by priority.
The ``supports()`` method is invoked to check if a param converter can
convert the request into the required parameter. If it returns ``true``
the param converter is invoked.
Built-in Converters
-------------------
The bundle has only one built-in converter, the Doctrine one.
The bundle has two built-in converter, the Doctrine one and a DateTime
converter.
Doctrine Converter
~~~~~~~~~~~~~~~~~~
Converter Name: ``doctrine.orm``
The Doctrine Converter attempts to convert request attributes to Doctrine
entities fetched from the database. Two different approaches are possible:
- Fetch object by primary key
- Fetch object by one or several fields which contain unique values in the
database.
The following algorithm determines which operation will be performed.
- If an ``{id}`` parameter is present in the route, find object by primary key.
- If an option ``'id'`` is configured and matches route parameters, find object by primary key.
- If the previous rules do not apply, attempt to find one entity by matching
route parameters to entity fields. You can control this process by
configuring ``exclude`` parameters or a attribute to field name ``mapping``.
By default, the Doctrine converter uses the *default* entity manager. This can
be configured with the ``entity_manager`` option::
@@ -87,6 +115,55 @@ This also allows you to have multiple converters in one action::
In the example above, the post parameter is handled automatically, but the comment is
configured with the annotation since they can not both follow the default convention.
If you want to match an entity using multiple fields use ``mapping``::
/**
* @Route("/blog/{date}/{slug}/comments/{comment_slug}")
* @ParamConverter("post", options={"mapping": {"date": "date", "slug": "slug"})
* @ParamConverter("comment", options={"mapping": {"comment_slug": "slug"})
*/
public function showAction(Post $post, Comment $comment)
{
}
If you are matching an entity using several fields, but you want to exclude a
route parameter from being part of the criteria::
/**
* @Route("/blog/{date}/{slug}")
* @ParamConverter("post", options={"exclude": ["date"]})
*/
public function showAction(Post $post, \DateTime $date)
{
}
DateTime Converter
~~~~~~~~~~~~~~~~~~
Converter Name: ``datetime``
The datetime converter converts any route or request attribute into a datetime
instance::
/**
* @Route("/blog/archive/{start}/{end}")
*/
public function archiveAction(DateTime $start, DateTime $end)
{
}
By default any date format that can be parsed by the ``DateTime`` constructor
is accepted. You can be stricter with input given through the options::
/**
* @Route("/blog/archive/{start}/{end}")
* @ParamConverter("start", options={"format": "Y-m-d"})
* @ParamConverter("end", options={"format": "Y-m-d"})
*/
public function archiveAction(DateTime $start, DateTime $end)
{
}
Creating a Converter
--------------------
@@ -120,6 +197,21 @@ on the request attributes, it should set an attribute named
``$configuration->getName()``, which stores an object of class
``$configuration->getClass()``.
To register your converter service you must add a tag to your service
.. configuration-block::
.. code-block:: xml
<service id="my_converter" class="MyBundle/Request/ParamConverter/MyConverter">
<tag name="request.param_converter" priority="-2" name="my_converter" />
</service>
You can register a converter by priority, by name or both. If you don't
specifiy a priority or name the converter will be added to the converter stack
with a priority of `0`. To explicitly disable the registration by priority you
have to set `priority="false"` in your tag definition.
.. tip::
Use the ``DoctrineParamConverter`` class as a template for your own converters.

View File

@@ -40,6 +40,19 @@ case for the above example, you can even omit the annotation value::
return array('post' => $post);
}
..note::
If you are using PHP as a templating system, you need to make it
explicit::
/**
* @Template(engine="php")
*/
public function showAction($id)
{
// ...
}
And if the only parameters to pass to the template are method arguments, you
can use the ``vars`` attribute instead of returning an array. This is very
useful in combination with the ``@ParamConverter`` :doc:`annotation

View File

@@ -4,6 +4,7 @@ namespace Sensio\Bundle\FrameworkExtraBundle\Routing;
use Symfony\Component\Routing\Loader\AnnotationClassLoader;
use Symfony\Component\Routing\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route as FrameworkExtraBundleRoute;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
/*
@@ -37,7 +38,7 @@ class AnnotatedRouteControllerLoader extends AnnotationClassLoader
{
// controller
$classAnnot = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass);
if ($classAnnot && $service = $classAnnot->getService()) {
if ($classAnnot instanceof FrameworkExtraBundleRoute && $service = $classAnnot->getService()) {
$route->setDefault('_controller', $service.':'.$method->getName());
} else {
$route->setDefault('_controller', $class->getName().'::'.$method->getName());

View File

@@ -5,8 +5,7 @@ namespace Sensio\Bundle\FrameworkExtraBundle\Templating;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Templating\TemplateReference;
use CG\Core\ClassUtils;
use Doctrine\Common\Util\ClassUtils;
/*
* This file is part of the Symfony framework.
@@ -51,12 +50,7 @@ class TemplateGuesser
*/
public function guessTemplateName($controller, Request $request, $engine = 'twig')
{
$className = get_class($controller[0]);
// When JMSSecurityExtraBundle is used it generates Controller classes as MyAccountController__CG__
if (class_exists('CG\\Core\\ClassUtils')) {
$className = ClassUtils::getUserClass($className);
}
$className = class_exists('Doctrine\Common\Util\ClassUtils') ? ClassUtils::getClass($controller[0]) : get_class($controller[0]);
if (!preg_match('/Controller\\\(.+)Controller$/', $className, $matchController)) {
throw new \InvalidArgumentException(sprintf('The "%s" class does not look like a controller class (it must be in a "Controller" sub-namespace and the class name must end with "Controller")', get_class($controller[0])));

View File

@@ -64,6 +64,25 @@ class ControllerListenerTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(FooControllerCacheAtClassAndMethod::CLASS_SMAXAGE, $this->getReadedCache()->getSMaxAge());
}
public function testMultipleAnnotationsOnMethod()
{
$controller = new FooControllerCacheAtClassAndMethod();
$this->event = $this->getFilterControllerEvent(array($controller, 'bar3Action'), $this->request);
$this->listener->onKernelController($this->event);
$annotations = $this->getReadedCache();
$this->assertNotNull($annotations);
$this->assertArrayHasKey(0, $annotations);
$this->assertInstanceOf('Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache', $annotations[0]);
$this->assertEquals(FooControllerCacheAtClassAndMethod::METHOD_SMAXAGE, $annotations[0]->getSMaxAge());
$this->assertArrayHasKey(1, $annotations);
$this->assertInstanceOf('Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache', $annotations[1]);
$this->assertEquals(FooControllerCacheAtClassAndMethod::METHOD_SECOND_SMAXAGE, $annotations[1]->getSMaxAge());
$this->assertEquals(2, count($annotations));
}
protected function createRequest(Cache $cache = null)
{
return new Request(array(), array(), array(

View File

@@ -11,6 +11,7 @@ class FooControllerCacheAtClassAndMethod
{
const CLASS_SMAXAGE = 20;
const METHOD_SMAXAGE = 15;
const METHOD_SECOND_SMAXAGE = 25;
/**
* @Cache(smaxage="15")
@@ -22,4 +23,12 @@ class FooControllerCacheAtClassAndMethod
public function bar2Action()
{
}
/**
* @Cache(smaxage="15")
* @Cache(smaxage="25")
*/
public function bar3Action()
{
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterManager;
use Sensio\Bundle\FrameworkExtraBundle\EventListener\ParamConverterListener;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
class ParamConverterListenerTest extends \PHPUnit_Framework_TestCase
{
public function testRequestIsSkipped()
{
$kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface');
$request = new Request();
$manager = $this->getMock('Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterManager');
$manager->expects($this->once())
->method('apply')
->with($this->equalTo($request), $this->equalTo(array()));
$listener = new ParamConverterListener($manager);
$event = new FilterControllerEvent($kernel, array(new TestController(), 'execute'), $request, null);
$listener->onKernelController($event);
}
}
class TestController
{
public function execute(Request $request) {}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace Sensio\Bundle\FrameworkExtraBundle\Tests\Request\ParamConverter;
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\DateTimeParamConverter;
class DateTimeParamConverterTest extends \PHPUnit_Framework_TestCase
{
private $converter;
public function setUp()
{
$this->converter = new DateTimeParamConverter();
}
public function testSupports()
{
$config = $this->createConfiguration("DateTime");
$this->assertTrue($this->converter->supports($config));
$config = $this->createConfiguration(__CLASS__);
$this->assertFalse($this->converter->supports($config));
$config = $this->createConfiguration();
$this->assertFalse($this->converter->supports($config));
}
public function testApply()
{
$request = new Request(array(), array(), array('start' => '2012-07-21 00:00:00'));
$config = $this->createConfiguration("DateTime", "start");
$this->converter->apply($request, $config);
$this->assertInstanceOf("DateTime", $request->attributes->get('start'));
$this->assertEquals('2012-07-21', $request->attributes->get('start')->format('Y-m-d'));
}
public function testApplyWithFormatInvalidDate404Exception()
{
$request = new Request(array(), array(), array('start' => '2012-07-21'));
$config = $this->createConfiguration("DateTime", "start");
$config->expects($this->any())->method('getOptions')->will($this->returnValue(array('format' => 'd.m.Y')));
$this->setExpectedException('Symfony\Component\HttpKernel\Exception\NotFoundHttpException', 'Invalid date given.');
$this->converter->apply($request, $config);
}
public function createConfiguration($class = null, $name = null)
{
$config = $this->getMock(
'Sensio\Bundle\FrameworkExtraBundle\Configuration\ConfigurationInterface', array(
'getClass', 'getAliasName', 'getOptions', 'getName',
));
if ($name !== null) {
$config->expects($this->any())
->method('getName')
->will($this->returnValue($name));
}
if ($class !== null) {
$config->expects($this->any())
->method('getClass')
->will($this->returnValue($class));
}
return $config;
}
}

View File

@@ -27,11 +27,11 @@ class DoctrineParamConverterTest extends \PHPUnit_Framework_TestCase
$this->converter = new DoctrineParamConverter($this->manager);
}
public function createConfiguration($class = null, array $options = null)
public function createConfiguration($class = null, array $options = null, $name = 'arg', $isOptional = false)
{
$config = $this->getMock(
'Sensio\Bundle\FrameworkExtraBundle\Configuration\ConfigurationInterface', array(
'getClass', 'getAliasName', 'getOptions'
'getClass', 'getAliasName', 'getOptions', 'isOptional', 'getName',
));
if ($options !== null) {
$config->expects($this->once())
@@ -43,6 +43,12 @@ class DoctrineParamConverterTest extends \PHPUnit_Framework_TestCase
->method('getClass')
->will($this->returnValue($class));
}
$config->expects($this->any())
->method('getName')
->will($this->returnValue($name));
$config->expects($this->any())
->method('isOptional')
->will($this->returnValue($isOptional));
return $config;
}
@@ -53,13 +59,86 @@ class DoctrineParamConverterTest extends \PHPUnit_Framework_TestCase
$config = $this->createConfiguration(null, array());
$objectManager = $this->getMock('Doctrine\Common\Persistence\ObjectManager');
$this->manager->expects($this->never())->method('find');
$this->setExpectedException('LogicException');
$this->converter->apply($request, $config);
}
public function testApplyWithNoIdAndDataOptional()
{
$request = new Request();
$config = $this->createConfiguration(null, array(), 'arg', true);
$objectManager = $this->getMock('Doctrine\Common\Persistence\ObjectManager');
$ret = $this->converter->apply($request, $config);
$this->assertTrue($ret);
$this->assertNull($request->attributes->get('arg'));
}
public function testApplyWithId()
{
$request = new Request();
$request->attributes->set('id', 1);
$config = $this->createConfiguration('stdClass', array('id' => 'id'), 'arg');
$objectRepository = $this->getMock('Doctrine\Common\Persistence\ObjectRepository');
$this->manager->expects($this->once())
->method('getRepository')
->will($this->returnValue($objectRepository));
$objectRepository->expects($this->once())
->method('find')
->with($this->equalTo(1))
->will($this->returnValue($object =new \stdClass));
$ret = $this->converter->apply($request, $config);
$this->assertTrue($ret);
$this->assertSame($object, $request->attributes->get('arg'));
}
public function testApplyWithMappingAndExclude()
{
$request = new Request();
$request->attributes->set('foo', 1);
$request->attributes->set('bar', 2);
$config = $this->createConfiguration(
'stdClass',
array('mapping' => array('foo' => 'Foo'), 'exclude' => array('bar')),
'arg'
);
$objectManager = $this->getMock('Doctrine\Common\Persistence\ObjectManager');
$metadata = $this->getMock('Doctrine\Common\Persistence\Mapping\ClassMetadata');
$this->manager->expects($this->once())
->method('getManager')
->will($this->returnValue($objectManager));
$objectManager->expects($this->once())
->method('getClassMetadata')
->will($this->returnValue($metadata));
$this->setExpectedException('LogicException');
$this->converter->apply($request, $config);
$metadata->expects($this->once())
->method('hasField')
->with($this->equalTo('Foo'))
->will($this->returnValue(true));
$objectRepository = $this->getMock('Doctrine\Common\Persistence\ObjectRepository');
$this->manager->expects($this->once())
->method('getRepository')
->will($this->returnValue($objectRepository));
$objectRepository->expects($this->once())
->method('findOneBy')
->with($this->equalTo(array('Foo' => 1)))
->will($this->returnValue($object =new \stdClass));
$ret = $this->converter->apply($request, $config);
$this->assertTrue($ret);
$this->assertSame($object, $request->attributes->get('arg'));
}
public function testSupports()
@@ -76,6 +155,10 @@ class DoctrineParamConverterTest extends \PHPUnit_Framework_TestCase
->method('getMetadataFactory')
->will($this->returnValue($metadataFactory));
$this->manager->expects($this->once())
->method('getManagers')
->will($this->returnValue(array($objectManager)));
$this->manager->expects($this->once())
->method('getManager')
->will($this->returnValue($objectManager));

View File

@@ -65,6 +65,73 @@ class ParamConverterManagerTest extends \PHPUnit_Framework_TestCase
$this->manager->apply(new Request(), $configurations);
}
public function testApplyNamedConverter()
{
$converter = $this->createParamConverterMock();
$converter
->expects($this->any())
->method('supports')
->will($this->returnValue(True))
;
$converter
->expects($this->any())
->method('apply')
;
$this->manager->add($converter, 0, "test");
$request = new Request();
$request->attributes->set('param', '1234');
$configuration = new Configuration\ParamConverter(array(
'name' => 'param',
'class' => 'stdClass',
'converter' => 'test',
));
$this->manager->apply($request, array($configuration));
}
public function testApplyNamedConverterNotSupportsParameter()
{
$converter = $this->createParamConverterMock();
$converter
->expects($this->any())
->method('supports')
->will($this->returnValue(false))
;
$this->manager->add($converter, 0, "test");
$request = new Request();
$request->attributes->set('param', '1234');
$configuration = new Configuration\ParamConverter(array(
'name' => 'param',
'class' => 'stdClass',
'converter' => 'test',
));
$this->setExpectedException("RuntimeException", "Converter 'test' does not support conversion of parameter 'param'.");
$this->manager->apply($request, array($configuration));
}
public function testApplyNamedConverterNoConverter()
{
$request = new Request();
$request->attributes->set('param', '1234');
$configuration = new Configuration\ParamConverter(array(
'name' => 'param',
'class' => 'stdClass',
'converter' => 'test',
));
$this->setExpectedException("RuntimeException", "No converter named 'test' found for conversion of parameter 'param'.");
$this->manager->apply($request, array($configuration));
}
public function testApplyNotCalledOnAlreadyConvertedObjects()
{

View File

@@ -0,0 +1,50 @@
UPGRADE FROM 2.0 to 2.1
=======================
### DoctrineParamConverter: Request Attributes with same name as Arguments
Previously the DoctrineParamConverter defaulted to finding objects by 'id'
parameter. This is unintuitive and is now overwritten by a behavior where
the request attributes with the same name as entity arguments is matched
with higher priority.
This might cause problems if you are using this parameter for another object conversion.
### DoctrineParamConverter with multiple Arguments may clash
In 2.0 the parameter converter matched only entity fields against route parameters.
With 2.1, the matching now also includes single-valued associations. Depending
on fields in entities this might lead to clashes when you update to the latest version.
Example that may break with the latest (2.1) version:
/**
* @Route("/user/{email}/{address}")
* @ParamConverter("address", class="MyBundle:Address", options={"id": "address"})
*/
public function showAction(User $user, Address $address)
{
}
class User
{
/** @ORM\Column(type="string") */
public $email;
/** @ORM\ManyToOne(targetEntity="Address") */
public $address;
}
Since address exists as field in `User` and User is not searched by primary key but
by field, this scenario now adds `address` to the criteria to find a user instance.
In scenarios of related entities this might even (just) work, but you never know.
You can fix this by configuring explicit mapping for `User`:
/**
* @Route("/user/{email}/{address}")
* @ParamConverter("address", options={"id": "address"})
* @ParamConverter("email", options={"exclude": ["address"]})
*/
public function showAction(User $user, Address $address)
{
}

View File

@@ -22,5 +22,6 @@
"branch-alias": {
"dev-master": "2.1.x-dev"
}
}
},
"minimum-stability": "dev"
}