@@ -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.
|
||||
*
|
||||
|
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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());
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
@@ -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());
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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>
|
||||
|
@@ -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.
|
||||
|
@@ -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
|
||||
|
@@ -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());
|
||||
|
@@ -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])));
|
||||
|
@@ -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(
|
||||
|
@@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@@ -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) {}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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));
|
||||
|
@@ -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()
|
||||
{
|
||||
|
||||
|
50
vendor/sensio/framework-extra-bundle/Sensio/Bundle/FrameworkExtraBundle/UPGRADE.md
vendored
Normal file
50
vendor/sensio/framework-extra-bundle/Sensio/Bundle/FrameworkExtraBundle/UPGRADE.md
vendored
Normal 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)
|
||||
{
|
||||
}
|
@@ -22,5 +22,6 @@
|
||||
"branch-alias": {
|
||||
"dev-master": "2.1.x-dev"
|
||||
}
|
||||
}
|
||||
},
|
||||
"minimum-stability": "dev"
|
||||
}
|
||||
|
Reference in New Issue
Block a user