init without trunk

This commit is contained in:
Kevin Adametz 2020-07-09 12:49:32 +02:00
parent ed24ac4994
commit bb809e7233
14652 changed files with 177862 additions and 94817 deletions

View file

@ -0,0 +1,6 @@
/vendor
coverage
/Tests/Fixtures/cache
phpunit.xml
composer.lock
/.phpunit

View file

@ -0,0 +1,46 @@
language: php
sudo: false
cache:
directories:
- $HOME/.composer/cache/files
- .phpunit
php:
- 5.4
- 5.5
- 5.6
- 7.0
- 7.1
matrix:
include:
- php: 5.3
dist: precise
- php: 7.0
env: deps=low
# Use the newer stack for HHVM as HHVM does not support Precise anymore since a long time and so Precise has an outdated version
- php: hhvm-3.12
sudo: required
dist: trusty
group: edge
fast_finish: true
env:
global:
- deps=no
before_install:
- export SYMFONY_PHPUNIT_DIR="$(pwd)/.phpunit"
- if [[ ! $TRAVIS_PHP_VERSION = hhvm* ]]; then INI_FILE=~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; else INI_FILE=/etc/hhvm/php.ini; fi
- echo memory_limit = -1 >> $INI_FILE
- if [[ ! $TRAVIS_PHP_VERSION = hhvm* ]]; then phpenv config-rm xdebug.ini || echo "xdebug not available"; fi
install:
- if [[ "$TRAVIS_PHP_VERSION" == "5.3" ]]; then composer remove --dev --no-update symfony/psr-http-message-bridge zendframework/zend-diactoros; fi;
- if [ "$deps" = "no" ]; then composer install; fi;
- if [ "$deps" = "low" ]; then composer --prefer-lowest --prefer-stable update; fi;
script:
- ./vendor/bin/simple-phpunit

View file

@ -0,0 +1,25 @@
CHANGELOG
=========
3.0
---
* fixed the Doctrine param converter that sent 500 when an entity was not found under some circumstances
* ParamConverterInterface now uses ParamConverter as a type hint instead of ConfigurationInterface
* added support for @Security
* added support for HTTP validation cache in @Cache (ETag and LastModified)
2.2
---
* added the possibility to configure the repository method to use for the
Doctrine converter via the repository_method option.
* [BC break] When defining multiple @Cache, @Method or @Template annotations on
a controller class or method, the latter now overrules the former
2.1
---
* added the possibility to configure the id name for the Doctrine converter via the id option
* [BC break] The ParamConverterInterface::apply() method now must return a
Boolean value indicating if a conversion was done.

View file

@ -0,0 +1,248 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Configuration;
/**
* The Cache class handles the Cache annotation parts.
*
* @author Fabien Potencier <fabien@symfony.com>
* @Annotation
*/
class Cache extends ConfigurationAnnotation
{
/**
* The expiration date as a valid date for the strtotime() function.
*
* @var string
*/
protected $expires;
/**
* The number of seconds that the response is considered fresh by a private
* cache like a web browser.
*
* @var int
*/
protected $maxage;
/**
* The number of seconds that the response is considered fresh by a public
* cache like a reverse proxy cache.
*
* @var int
*/
protected $smaxage;
/**
* Whether the response is public or not.
*
* @var bool
*/
protected $public;
/**
* Additional "Vary:"-headers.
*
* @var array
*/
protected $vary;
/**
* An expression to compute the Last-Modified HTTP header.
*
* @var string
*/
protected $lastModified;
/**
* An expression to compute the ETag HTTP header.
*
* @var string
*/
protected $etag;
/**
* Returns the expiration date for the Expires header field.
*
* @return string
*/
public function getExpires()
{
return $this->expires;
}
/**
* Sets the expiration date for the Expires header field.
*
* @param string $expires A valid php date
*/
public function setExpires($expires)
{
$this->expires = $expires;
}
/**
* Sets the number of seconds for the max-age cache-control header field.
*
* @param int $maxage A number of seconds
*/
public function setMaxAge($maxage)
{
$this->maxage = $maxage;
}
/**
* Returns the number of seconds the response is considered fresh by a
* private cache.
*
* @return int
*/
public function getMaxAge()
{
return $this->maxage;
}
/**
* Sets the number of seconds for the s-maxage cache-control header field.
*
* @param int $smaxage A number of seconds
*/
public function setSMaxAge($smaxage)
{
$this->smaxage = $smaxage;
}
/**
* Returns the number of seconds the response is considered fresh by a
* public cache.
*
* @return int
*/
public function getSMaxAge()
{
return $this->smaxage;
}
/**
* Returns whether or not a response is public.
*
* @return bool
*/
public function isPublic()
{
return $this->public === true;
}
/**
* Returns whether or not a response is private.
*
* @return bool
*/
public function isPrivate()
{
return $this->public === false;
}
/**
* Sets a response public.
*
* @param bool $public A boolean value
*/
public function setPublic($public)
{
$this->public = (bool) $public;
}
/**
* Returns the custom "Vary"-headers.
*
* @return array
*/
public function getVary()
{
return $this->vary;
}
/**
* Add additional "Vary:"-headers.
*
* @param array $vary
*/
public function setVary($vary)
{
$this->vary = $vary;
}
/**
* Sets the "Last-Modified"-header expression.
*
* @param string $expression
*/
public function setLastModified($expression)
{
$this->lastModified = $expression;
}
/**
* Returns the "Last-Modified"-header expression.
*
* @return string
*/
public function getLastModified()
{
return $this->lastModified;
}
/**
* Sets the "ETag"-header expression.
*
* @param string $expression
*/
public function setETag($expression)
{
$this->etag = $expression;
}
/**
* Returns the "ETag"-header expression.
*
* @return string
*/
public function getETag()
{
return $this->etag;
}
/**
* Returns the annotation alias name.
*
* @return string
*
* @see ConfigurationInterface
*/
public function getAliasName()
{
return 'cache';
}
/**
* Only one cache directive is allowed.
*
* @return bool
*
* @see ConfigurationInterface
*/
public function allowArray()
{
return false;
}
}

View file

@ -0,0 +1,31 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Configuration;
/**
* Base configuration annotation.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
abstract class ConfigurationAnnotation implements ConfigurationInterface
{
public function __construct(array $values)
{
foreach ($values as $k => $v) {
if (!method_exists($this, $name = 'set'.$k)) {
throw new \RuntimeException(sprintf('Unknown key "%s" for annotation "@%s".', $k, get_class($this)));
}
$this->$name($v);
}
}
}

View file

@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Configuration;
/**
* ConfigurationInterface.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface ConfigurationInterface
{
/**
* Returns the alias name for an annotated configuration.
*
* @return string
*/
public function getAliasName();
/**
* Returns whether multiple annotations of this type are allowed.
*
* @return bool
*/
public function allowArray();
}

View file

@ -0,0 +1,82 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Configuration;
/**
* The Method class handles the Method annotation parts.
*
* @author Fabien Potencier <fabien@symfony.com>
* @Annotation
*/
class Method extends ConfigurationAnnotation
{
/**
* An array of restricted HTTP methods.
*
* @var array
*/
protected $methods = array();
/**
* Returns the array of HTTP methods.
*
* @return array
*/
public function getMethods()
{
return $this->methods;
}
/**
* Sets the HTTP methods.
*
* @param array|string $methods An HTTP method or an array of HTTP methods
*/
public function setMethods($methods)
{
$this->methods = is_array($methods) ? $methods : array($methods);
}
/**
* Sets the HTTP methods.
*
* @param array|string $methods An HTTP method or an array of HTTP methods
*/
public function setValue($methods)
{
$this->setMethods($methods);
}
/**
* Returns the annotation alias name.
*
* @return string
*
* @see ConfigurationInterface
*/
public function getAliasName()
{
return 'method';
}
/**
* Only one method directive is allowed.
*
* @return bool
*
* @see ConfigurationInterface
*/
public function allowArray()
{
return false;
}
}

View file

@ -0,0 +1,190 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Configuration;
/**
* The ParamConverter class handles the ParamConverter annotation parts.
*
* @author Fabien Potencier <fabien@symfony.com>
* @Annotation
*/
class ParamConverter extends ConfigurationAnnotation
{
/**
* The parameter name.
*
* @var string
*/
protected $name;
/**
* The parameter class.
*
* @var string
*/
protected $class;
/**
* An array of options.
*
* @var array
*/
protected $options = array();
/**
* Whether or not the parameter is optional.
*
* @var bool
*/
protected $optional = false;
/**
* Use explicitly named converter instead of iterating by priorities.
*
* @var string
*/
protected $converter;
/**
* Returns the parameter name.
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Sets the parameter name.
*
* @param string $name The parameter name
*/
public function setValue($name)
{
$this->setName($name);
}
/**
* Sets the parameter name.
*
* @param string $name The parameter name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* Returns the parameter class name.
*
* @return string $name
*/
public function getClass()
{
return $this->class;
}
/**
* Sets the parameter class name.
*
* @param string $class The parameter class name
*/
public function setClass($class)
{
$this->class = $class;
}
/**
* Returns an array of options.
*
* @return array
*/
public function getOptions()
{
return $this->options;
}
/**
* Sets an array of options.
*
* @param array $options An array of options
*/
public function setOptions($options)
{
$this->options = $options;
}
/**
* Sets whether or not the parameter is optional.
*
* @param bool $optional Whether the parameter is optional
*/
public function setIsOptional($optional)
{
$this->optional = (bool) $optional;
}
/**
* Returns whether or not the parameter is optional.
*
* @return bool
*/
public function isOptional()
{
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.
*
* @return string
*
* @see ConfigurationInterface
*/
public function getAliasName()
{
return 'converters';
}
/**
* Multiple ParamConverters are allowed.
*
* @return bool
*
* @see ConfigurationInterface
*/
public function allowArray()
{
return true;
}
}

View file

@ -0,0 +1,49 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Configuration;
use Symfony\Component\Routing\Annotation\Route as BaseRoute;
/**
* @author Kris Wallsmith <kris@symfony.com>
* @Annotation
*/
class Route extends BaseRoute
{
protected $service;
public function setService($service)
{
// avoid a BC notice in case of @Route(service="") with sf ^2.7
if (null === $this->getPath()) {
$this->setPath('');
}
$this->service = $service;
}
public function getService()
{
return $this->service;
}
/**
* Multiple route annotations are allowed.
*
* @return bool
*
* @see ConfigurationInterface
*/
public function allowArray()
{
return true;
}
}

View file

@ -0,0 +1,48 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Configuration;
/**
* The Security class handles the Security annotation.
*
* @author Fabien Potencier <fabien@symfony.com>
* @Annotation
*/
class Security extends ConfigurationAnnotation
{
protected $expression;
public function getExpression()
{
return $this->expression;
}
public function setExpression($expression)
{
$this->expression = $expression;
}
public function setValue($expression)
{
$this->setExpression($expression);
}
public function getAliasName()
{
return 'security';
}
public function allowArray()
{
return false;
}
}

View file

@ -0,0 +1,186 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Configuration;
use Symfony\Bundle\FrameworkBundle\Templating\TemplateReference;
/**
* The Template class handles the Template annotation parts.
*
* @author Fabien Potencier <fabien@symfony.com>
* @Annotation
*/
class Template extends ConfigurationAnnotation
{
/**
* The template reference.
*
* @var TemplateReference|string
*/
protected $template;
/**
* The template engine used when a specific template isn't specified.
*
* @var string
*/
protected $engine = 'twig';
/**
* The associative array of template variables.
*
* @var array
*/
protected $vars = array();
/**
* Should the template be streamed?
*
* @var bool
*/
protected $streamable = false;
/**
* The controller (+action) this annotation is set to.
*
* @var array
*/
private $owner;
/**
* Returns the array of templates variables.
*
* @return array
*/
public function getVars()
{
return $this->vars;
}
/**
* @param bool $streamable
*/
public function setIsStreamable($streamable)
{
$this->streamable = $streamable;
}
/**
* @return bool
*/
public function isStreamable()
{
return (bool) $this->streamable;
}
/**
* Sets the template variables.
*
* @param array $vars The template variables
*/
public function setVars($vars)
{
$this->vars = $vars;
}
/**
* Returns the engine used when guessing template names.
*
* @return string
*/
public function getEngine()
{
return $this->engine;
}
/**
* Sets the engine used when guessing template names.
*
* @param string
*/
public function setEngine($engine)
{
$this->engine = $engine;
}
/**
* Sets the template logic name.
*
* @param string $template The template logic name
*/
public function setValue($template)
{
$this->setTemplate($template);
}
/**
* Returns the template reference.
*
* @return TemplateReference
*/
public function getTemplate()
{
return $this->template;
}
/**
* Sets the template reference.
*
* @param TemplateReference|string $template The template reference
*/
public function setTemplate($template)
{
$this->template = $template;
}
/**
* Returns the annotation alias name.
*
* @return string
*
* @see ConfigurationInterface
*/
public function getAliasName()
{
return 'template';
}
/**
* Only one template directive is allowed.
*
* @return bool
*
* @see ConfigurationInterface
*/
public function allowArray()
{
return false;
}
/**
* @param array $owner
*/
public function setOwner(array $owner)
{
$this->owner = $owner;
}
/**
* The controller (+action) this annotation is attached to.
*
* @return array
*/
public function getOwner()
{
return $this->owner;
}
}

View file

@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
class AddExpressionLanguageProvidersPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if ($container->hasDefinition('sensio_framework_extra.security.expression_language.default')) {
$definition = $container->findDefinition('sensio_framework_extra.security.expression_language.default');
foreach ($container->findTaggedServiceIds('security.expression_language_provider') as $id => $attributes) {
$definition->addMethodCall('registerProvider', array(new Reference($id)));
}
}
}
}

View file

@ -0,0 +1,46 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* Adds tagged request.param_converter services to converter.manager service.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class AddParamConverterPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (false === $container->hasDefinition('sensio_framework_extra.converter.manager')) {
return;
}
$definition = $container->getDefinition('sensio_framework_extra.converter.manager');
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 === false) {
$priority = null;
}
$definition->addMethodCall('add', array(new Reference($id), $priority, $name));
}
}
}
}

View file

@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class LegacyPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (false === $container->hasDefinition('sensio_framework_extra.security.listener')) {
return;
}
$definition = $container->getDefinition('sensio_framework_extra.security.listener');
if ($container->hasDefinition('security.token_storage')) {
$definition->replaceArgument(0, null);
}
}
}

View file

@ -0,0 +1,88 @@
<?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\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\NodeInterface;
/**
* FrameworkExtraBundle configuration structure.
*
* @author Henrik Bjornskov <hb@peytz.dk>
*/
class Configuration implements ConfigurationInterface
{
/**
* Generates the configuration tree.
*
* @return NodeInterface
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('sensio_framework_extra', 'array');
$rootNode
->children()
->arrayNode('router')
->addDefaultsIfNotSet()
->children()
->booleanNode('annotations')->defaultTrue()->end()
->end()
->end()
->arrayNode('request')
->addDefaultsIfNotSet()
->children()
->booleanNode('converters')->defaultTrue()->end()
->booleanNode('auto_convert')->defaultTrue()->end()
->end()
->end()
->arrayNode('view')
->addDefaultsIfNotSet()
->children()
->booleanNode('annotations')->defaultTrue()->end()
->end()
->end()
->arrayNode('cache')
->addDefaultsIfNotSet()
->children()
->booleanNode('annotations')->defaultTrue()->end()
->end()
->end()
->arrayNode('security')
->addDefaultsIfNotSet()
->children()
->booleanNode('annotations')->defaultTrue()->end()
->scalarNode('expression_language')->defaultValue('sensio_framework_extra.security.expression_language.default')->end()
->end()
->end()
->arrayNode('psr_message')
->addDefaultsIfNotSet()
->children()
->booleanNode('enabled')->defaultValue(interface_exists('Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface') && class_exists('Zend\Diactoros\ServerRequestFactory'))->end()
->end()
->end()
->arrayNode('templating')
->fixXmlConfig('controller_pattern')
->children()
->arrayNode('controller_patterns')
->prototype('scalar')
->end()
->end()
->end()
->end()
;
return $treeBuilder;
}
}

View file

@ -0,0 +1,143 @@
<?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\DependencyInjection;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
/**
* SensioFrameworkExtraExtension.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SensioFrameworkExtraExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs);
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.xml');
if (!empty($config['templating']['controller_patterns'])) {
$container
->getDefinition('sensio_framework_extra.view.guesser')
->addArgument($config['templating']['controller_patterns']);
}
$annotationsToLoad = array();
if ($config['router']['annotations']) {
$annotationsToLoad[] = 'routing.xml';
if (PHP_VERSION_ID < 70000) {
$this->addClassesToCompile(array(
'Sensio\\Bundle\\FrameworkExtraBundle\\EventListener\\ControllerListener',
));
}
}
if ($config['request']['converters']) {
$annotationsToLoad[] = 'converters.xml';
if (PHP_VERSION_ID < 70000) {
$this->addClassesToCompile(array(
// cannot be added because it has some annotations
//'Sensio\\Bundle\\FrameworkExtraBundle\\Configuration\\ParamConverter',
'Sensio\\Bundle\\FrameworkExtraBundle\\EventListener\\ParamConverterListener',
'Sensio\\Bundle\\FrameworkExtraBundle\\Request\\ParamConverter\\DateTimeParamConverter',
'Sensio\\Bundle\\FrameworkExtraBundle\\Request\\ParamConverter\\DoctrineParamConverter',
'Sensio\\Bundle\\FrameworkExtraBundle\\Request\\ParamConverter\\ParamConverterInterface',
'Sensio\\Bundle\\FrameworkExtraBundle\\Request\\ParamConverter\\ParamConverterManager',
));
}
}
if ($config['view']['annotations']) {
$annotationsToLoad[] = 'view.xml';
if (PHP_VERSION_ID < 70000) {
$this->addClassesToCompile(array(
'Sensio\\Bundle\\FrameworkExtraBundle\\EventListener\\TemplateListener',
));
}
}
if ($config['cache']['annotations']) {
$annotationsToLoad[] = 'cache.xml';
if (PHP_VERSION_ID < 70000) {
$this->addClassesToCompile(array(
'Sensio\\Bundle\\FrameworkExtraBundle\\EventListener\\HttpCacheListener',
));
}
}
if ($config['security']['annotations']) {
$annotationsToLoad[] = 'security.xml';
if (class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage') && class_exists('Symfony\Component\Security\Core\Authorization\ExpressionLanguage')) {
$container->setAlias('sensio_framework_extra.security.expression_language', new Alias($config['security']['expression_language'], false));
} else {
$container->removeDefinition('sensio_framework_extra.security.expression_language.default');
}
if (PHP_VERSION_ID < 70000) {
$this->addClassesToCompile(array(
'Sensio\\Bundle\\FrameworkExtraBundle\\EventListener\\SecurityListener',
));
}
}
if ($annotationsToLoad) {
// must be first
$loader->load('annotations.xml');
foreach ($annotationsToLoad as $configFile) {
$loader->load($configFile);
}
if (PHP_VERSION_ID < 70000) {
$this->addClassesToCompile(array(
'Sensio\\Bundle\\FrameworkExtraBundle\\Configuration\\ConfigurationAnnotation',
));
}
if ($config['request']['converters']) {
$container->getDefinition('sensio_framework_extra.converter.listener')->replaceArgument(1, $config['request']['auto_convert']);
}
}
if ($config['psr_message']['enabled']) {
$loader->load('psr7.xml');
}
}
/**
* Returns the base path for the XSD files.
*
* @return string The XSD base path
*/
public function getXsdValidationBasePath()
{
return __DIR__.'/../Resources/config/schema';
}
public function getNamespace()
{
return 'http://symfony.com/schema/dic/symfony_extra';
}
}

View file

@ -0,0 +1,25 @@
<?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\EventListener;
/**
* CacheListener handles HTTP cache headers.
*
* It can be configured via the Cache, LastModified, and Etag annotations.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated Deprecated since 3.0, to be removed in 4.0. Use the HttpCacheListener instead.
*/
class CacheListener extends HttpCacheListener
{
}

View file

@ -0,0 +1,119 @@
<?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\EventListener;
use Doctrine\Common\Annotations\Reader;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ConfigurationInterface;
use Doctrine\Common\Util\ClassUtils;
/**
* The ControllerListener class parses annotation blocks located in
* controller classes.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ControllerListener implements EventSubscriberInterface
{
/**
* @var Reader
*/
protected $reader;
/**
* Constructor.
*
* @param Reader $reader An Reader instance
*/
public function __construct(Reader $reader)
{
$this->reader = $reader;
}
/**
* Modifies the Request object to apply configuration information found in
* controllers annotations like the template to render or HTTP caching
* configuration.
*
* @param FilterControllerEvent $event A FilterControllerEvent instance
*/
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
if (!is_array($controller) && method_exists($controller, '__invoke')) {
$controller = array($controller, '__invoke');
}
if (!is_array($controller)) {
return;
}
$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();
foreach (array_merge(array_keys($classConfigurations), array_keys($methodConfigurations)) as $key) {
if (!array_key_exists($key, $classConfigurations)) {
$configurations[$key] = $methodConfigurations[$key];
} elseif (!array_key_exists($key, $methodConfigurations)) {
$configurations[$key] = $classConfigurations[$key];
} else {
if (is_array($classConfigurations[$key])) {
if (!is_array($methodConfigurations[$key])) {
throw new \UnexpectedValueException('Configurations should both be an array or both not be an array');
}
$configurations[$key] = array_merge($classConfigurations[$key], $methodConfigurations[$key]);
} else {
// method configuration overrides class configuration
$configurations[$key] = $methodConfigurations[$key];
}
}
}
$request = $event->getRequest();
foreach ($configurations as $key => $attributes) {
$request->attributes->set($key, $attributes);
}
}
protected function getConfigurations(array $annotations)
{
$configurations = array();
foreach ($annotations as $configuration) {
if ($configuration instanceof ConfigurationInterface) {
if ($configuration->allowArray()) {
$configurations['_'.$configuration->getAliasName()][] = $configuration;
} elseif (!isset($configurations['_'.$configuration->getAliasName()])) {
$configurations['_'.$configuration->getAliasName()] = $configuration;
} else {
throw new \LogicException(sprintf('Multiple "%s" annotations are not allowed.', $configuration->getAliasName()));
}
}
}
return $configurations;
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::CONTROLLER => 'onKernelController',
);
}
}

View file

@ -0,0 +1,166 @@
<?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\EventListener;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
/**
* HttpCacheListener handles HTTP cache headers.
*
* It can be configured via the Cache annotation.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class HttpCacheListener implements EventSubscriberInterface
{
private $lastModifiedDates;
private $etags;
private $expressionLanguage;
public function __construct()
{
$this->lastModifiedDates = new \SplObjectStorage();
$this->etags = new \SplObjectStorage();
}
/**
* Handles HTTP validation headers.
*/
public function onKernelController(FilterControllerEvent $event)
{
$request = $event->getRequest();
if (!$configuration = $request->attributes->get('_cache')) {
return;
}
$response = new Response();
$lastModifiedDate = '';
if ($configuration->getLastModified()) {
$lastModifiedDate = $this->getExpressionLanguage()->evaluate($configuration->getLastModified(), $request->attributes->all());
$response->setLastModified($lastModifiedDate);
}
$etag = '';
if ($configuration->getETag()) {
$etag = hash('sha256', $this->getExpressionLanguage()->evaluate($configuration->getETag(), $request->attributes->all()));
$response->setETag($etag);
}
if ($response->isNotModified($request)) {
$event->setController(function () use ($response) {
return $response;
});
$event->stopPropagation();
} else {
if ($etag) {
$this->etags[$request] = $etag;
}
if ($lastModifiedDate) {
$this->lastModifiedDates[$request] = $lastModifiedDate;
}
}
}
/**
* Modifies the response to apply HTTP cache headers when needed.
*/
public function onKernelResponse(FilterResponseEvent $event)
{
$request = $event->getRequest();
if (!$configuration = $request->attributes->get('_cache')) {
return;
}
$response = $event->getResponse();
// http://tools.ietf.org/html/draft-ietf-httpbis-p4-conditional-12#section-3.1
if (!in_array($response->getStatusCode(), array(200, 203, 300, 301, 302, 304, 404, 410))) {
return;
}
if (null !== $age = $configuration->getSMaxAge()) {
if (!is_numeric($age)) {
$now = microtime(true);
$age = ceil(strtotime($configuration->getSMaxAge(), $now) - $now);
}
$response->setSharedMaxAge($age);
}
if (null !== $age = $configuration->getMaxAge()) {
if (!is_numeric($age)) {
$now = microtime(true);
$age = ceil(strtotime($configuration->getMaxAge(), $now) - $now);
}
$response->setMaxAge($age);
}
if (null !== $configuration->getExpires()) {
$date = \DateTime::createFromFormat('U', strtotime($configuration->getExpires()), new \DateTimeZone('UTC'));
$response->setExpires($date);
}
if (null !== $configuration->getVary()) {
$response->setVary($configuration->getVary());
}
if ($configuration->isPublic()) {
$response->setPublic();
}
if ($configuration->isPrivate()) {
$response->setPrivate();
}
if (isset($this->lastModifiedDates[$request])) {
$response->setLastModified($this->lastModifiedDates[$request]);
unset($this->lastModifiedDates[$request]);
}
if (isset($this->etags[$request])) {
$response->setETag($this->etags[$request]);
unset($this->etags[$request]);
}
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::CONTROLLER => 'onKernelController',
KernelEvents::RESPONSE => 'onKernelResponse',
);
}
private function getExpressionLanguage()
{
if (null === $this->expressionLanguage) {
if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
}
$this->expressionLanguage = new ExpressionLanguage();
}
return $this->expressionLanguage;
}
}

View file

@ -0,0 +1,129 @@
<?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\EventListener;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterManager;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* The ParamConverterListener handles the ParamConverter annotation.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ParamConverterListener implements EventSubscriberInterface
{
/**
* @var ParamConverterManager
*/
protected $manager;
protected $autoConvert;
/**
* @var bool
*/
private $isParameterTypeSupported;
/**
* Constructor.
*
* @param ParamConverterManager $manager A ParamConverterManager instance
* @param bool $autoConvert Auto convert non-configured objects
*/
public function __construct(ParamConverterManager $manager, $autoConvert = true)
{
$this->manager = $manager;
$this->autoConvert = $autoConvert;
$this->isParameterTypeSupported = method_exists('ReflectionParameter', 'getType');
}
/**
* Modifies the ParamConverterManager instance.
*
* @param FilterControllerEvent $event A FilterControllerEvent instance
*/
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
$request = $event->getRequest();
$configurations = array();
if ($configuration = $request->attributes->get('_converters')) {
foreach (is_array($configuration) ? $configuration : array($configuration) as $configuration) {
$configurations[$configuration->getName()] = $configuration;
}
}
if (is_array($controller)) {
$r = new \ReflectionMethod($controller[0], $controller[1]);
} elseif (is_object($controller) && is_callable($controller, '__invoke')) {
$r = new \ReflectionMethod($controller, '__invoke');
} else {
$r = new \ReflectionFunction($controller);
}
// automatically apply conversion for non-configured objects
if ($this->autoConvert) {
$configurations = $this->autoConfigure($r, $request, $configurations);
}
$this->manager->apply($request, $configurations);
}
private function autoConfigure(\ReflectionFunctionAbstract $r, Request $request, $configurations)
{
foreach ($r->getParameters() as $param) {
if ($param->getClass() && $param->getClass()->isInstance($request)) {
continue;
}
$name = $param->getName();
$class = $param->getClass();
$hasType = $this->isParameterTypeSupported && $param->hasType();
if ($class || $hasType) {
if (!isset($configurations[$name])) {
$configuration = new ParamConverter(array());
$configuration->setName($name);
$configurations[$name] = $configuration;
}
if ($class && null === $configurations[$name]->getClass()) {
$configurations[$name]->setClass($class->getName());
}
}
if (isset($configurations[$name])) {
$configurations[$name]->setIsOptional($param->isOptional() || $param->isDefaultValueAvailable() || $hasType && $param->getType()->allowsNull());
}
}
return $configurations;
}
/**
* Get subscribed events.
*
* @return array Subscribed events
*/
public static function getSubscribedEvents()
{
return array(
KernelEvents::CONTROLLER => 'onKernelController',
);
}
}

View file

@ -0,0 +1,63 @@
<?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\EventListener;
use Psr\Http\Message\ResponseInterface;
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
use Symfony\Bridge\PsrHttpMessage\HttpFoundationFactoryInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Converts PSR-7 Response to HttpFoundation Response using the bridge.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class PsrResponseListener implements EventSubscriberInterface
{
/**
* @var HttpFoundationFactoryInterface
*/
private $httpFoundationFactory;
public function __construct(HttpFoundationFactoryInterface $httpFoundationFactory)
{
$this->httpFoundationFactory = $httpFoundationFactory;
}
/**
* Do the conversion if applicable and update the response of the event.
*
* @param GetResponseForControllerResultEvent $event
*/
public function onKernelView(GetResponseForControllerResultEvent $event)
{
$controllerResult = $event->getControllerResult();
if (!$controllerResult instanceof ResponseInterface) {
return;
}
$event->setResponse($this->httpFoundationFactory->createResponse($controllerResult));
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
KernelEvents::VIEW => 'onKernelView',
);
}
}

View file

@ -0,0 +1,103 @@
<?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\EventListener;
use Sensio\Bundle\FrameworkExtraBundle\Security\ExpressionLanguage;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
/**
* SecurityListener handles security restrictions on controllers.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SecurityListener implements EventSubscriberInterface
{
private $tokenStorage;
private $authChecker;
private $language;
private $trustResolver;
private $roleHierarchy;
public function __construct(SecurityContextInterface $securityContext = null, ExpressionLanguage $language = null, AuthenticationTrustResolverInterface $trustResolver = null, RoleHierarchyInterface $roleHierarchy = null, TokenStorageInterface $tokenStorage = null, AuthorizationCheckerInterface $authChecker = null)
{
$this->tokenStorage = $tokenStorage ?: $securityContext;
$this->authChecker = $authChecker ?: $securityContext;
$this->language = $language;
$this->trustResolver = $trustResolver;
$this->roleHierarchy = $roleHierarchy;
}
public function onKernelController(FilterControllerEvent $event)
{
$request = $event->getRequest();
if (!$configuration = $request->attributes->get('_security')) {
return;
}
if (null === $this->tokenStorage || null === $this->trustResolver) {
throw new \LogicException('To use the @Security tag, you need to install the Symfony Security bundle.');
}
if (null === $this->tokenStorage->getToken()) {
throw new \LogicException('To use the @Security tag, your controller needs to be behind a firewall.');
}
if (null === $this->language) {
throw new \LogicException('To use the @Security tag, you need to use the Security component 2.4 or newer and install the ExpressionLanguage component.');
}
if (!$this->language->evaluate($configuration->getExpression(), $this->getVariables($request))) {
throw new AccessDeniedException(sprintf('Expression "%s" denied access.', $configuration->getExpression()));
}
}
// code should be sync with Symfony\Component\Security\Core\Authorization\Voter\ExpressionVoter
private function getVariables(Request $request)
{
$token = $this->tokenStorage->getToken();
if (null !== $this->roleHierarchy) {
$roles = $this->roleHierarchy->getReachableRoles($token->getRoles());
} else {
$roles = $token->getRoles();
}
$variables = array(
'token' => $token,
'user' => $token->getUser(),
'object' => $request,
'subject' => $request,
'request' => $request,
'roles' => array_map(function ($role) { return $role->getRole(); }, $roles),
'trust_resolver' => $this->trustResolver,
// needed for the is_granted expression function
'auth_checker' => $this->authChecker,
);
// controller variables should also be accessible
return array_merge($request->attributes->all(), $variables);
}
public static function getSubscribedEvents()
{
return array(KernelEvents::CONTROLLER => 'onKernelController');
}
}

View file

@ -0,0 +1,156 @@
<?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\EventListener;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
/**
* Handles the Template annotation for actions.
*
* Depends on pre-processing of the ControllerListener.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TemplateListener implements EventSubscriberInterface
{
/**
* @var ContainerInterface
*/
protected $container;
/**
* Constructor.
*
* @param ContainerInterface $container The service container instance
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* Guesses the template name to render and its variables and adds them to
* the request object.
*
* @param FilterControllerEvent $event A FilterControllerEvent instance
*/
public function onKernelController(FilterControllerEvent $event)
{
$request = $event->getRequest();
$template = $request->attributes->get('_template');
if (!$template instanceof Template) {
return;
}
$template->setOwner($controller = $event->getController());
// when no template has been given, try to resolve it based on the controller
if (null === $template->getTemplate()) {
$guesser = $this->container->get('sensio_framework_extra.view.guesser');
$template->setTemplate($guesser->guessTemplateName($controller, $request, $template->getEngine()));
}
}
/**
* Renders the template and initializes a new response object with the
* rendered template content.
*
* @param GetResponseForControllerResultEvent $event
*/
public function onKernelView(GetResponseForControllerResultEvent $event)
{
/* @var Template $template */
$request = $event->getRequest();
$template = $request->attributes->get('_template');
if (!$template instanceof Template) {
return;
}
$parameters = $event->getControllerResult();
$owner = $template->getOwner();
list($controller, $action) = $owner;
// when the annotation declares no default vars and the action returns
// null, all action method arguments are used as default vars
if (null === $parameters) {
$parameters = $this->resolveDefaultParameters($request, $template, $controller, $action);
}
// attempt to render the actual response
$templating = $this->container->get('templating');
if ($template->isStreamable()) {
$callback = function () use ($templating, $template, $parameters) {
return $templating->stream($template->getTemplate(), $parameters);
};
$event->setResponse(new StreamedResponse($callback));
} else {
$event->setResponse($templating->renderResponse($template->getTemplate(), $parameters));
}
// make sure the owner (controller+dependencies) is not cached or stored elsewhere
$template->setOwner(array());
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::CONTROLLER => array('onKernelController', -128),
KernelEvents::VIEW => 'onKernelView',
);
}
/**
* @param Request $request
* @param Template $template
* @param object $controller
* @param string $action
*
* @return array
*/
private function resolveDefaultParameters(Request $request, Template $template, $controller, $action)
{
$parameters = array();
$arguments = $template->getVars();
if (0 === count($arguments)) {
$r = new \ReflectionObject($controller);
$arguments = array();
foreach ($r->getMethod($action)->getParameters() as $param) {
$arguments[] = $param;
}
}
// fetch the arguments of @Template.vars or everything if desired
// and assign them to the designated template
foreach ($arguments as $argument) {
if ($argument instanceof \ReflectionParameter) {
$parameters[$name = $argument->getName()] = !$request->attributes->has($name) && $argument->isDefaultValueAvailable() ? $argument->getDefaultValue() : $request->attributes->get($name);
} else {
$parameters[$argument] = $request->attributes->get($argument);
}
}
return $parameters;
}
}

View file

@ -0,0 +1,19 @@
Copyright (c) 2010-2017 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -0,0 +1,12 @@
SensioFrameworkExtraBundle
==========================
This bundle provides a way to configure your controllers with annotations.
Read about it on its [official homepage](http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/index.html).
As of v3.0.0 of the bundle, the release cycle is de-synchronized from the
framework's. It means you can just require "sensio/framework-extra-bundle":
"~3.0" in your composer.json and Composer will automatically pick the latest
version of the bundle that works with your current version of Symfony. The
minimum version of Symfony for this workflow is 2.3.

View file

@ -0,0 +1,76 @@
<?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\ParamConverter;
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
{
/**
* {@inheritdoc}
*
* @throws NotFoundHttpException When invalid date given
*/
public function apply(Request $request, ParamConverter $configuration)
{
$param = $configuration->getName();
if (!$request->attributes->has($param)) {
return false;
}
$options = $configuration->getOptions();
$value = $request->attributes->get($param);
if (!$value && $configuration->isOptional()) {
return false;
}
if (isset($options['format'])) {
$date = DateTime::createFromFormat($options['format'], $value);
if (!$date) {
throw new NotFoundHttpException(sprintf('Invalid date given for parameter "%s".', $param));
}
} else {
if (false === strtotime($value)) {
throw new NotFoundHttpException(sprintf('Invalid date given for parameter "%s".', $param));
}
$date = new DateTime($value);
}
$request->attributes->set($param, $date);
return true;
}
/**
* {@inheritdoc}
*/
public function supports(ParamConverter $configuration)
{
if (null === $configuration->getClass()) {
return false;
}
return 'DateTime' === $configuration->getClass();
}
}

View file

@ -0,0 +1,248 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\ORM\NoResultException;
/**
* DoctrineParamConverter.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DoctrineParamConverter implements ParamConverterInterface
{
/**
* @var ManagerRegistry
*/
protected $registry;
public function __construct(ManagerRegistry $registry = null)
{
$this->registry = $registry;
}
/**
* {@inheritdoc}
*
* @throws \LogicException When unable to guess how to get a Doctrine instance from the request information
* @throws NotFoundHttpException When object not found
*/
public function apply(Request $request, ParamConverter $configuration)
{
$name = $configuration->getName();
$class = $configuration->getClass();
$options = $this->getOptions($configuration);
if (null === $request->attributes->get($name, false)) {
$configuration->setIsOptional(true);
}
// find by identifier?
if (false === $object = $this->find($class, $request, $options, $name)) {
// find by criteria
if (false === $object = $this->findOneBy($class, $request, $options)) {
if ($configuration->isOptional()) {
$object = null;
} else {
throw new \LogicException(sprintf('Unable to guess how to get a Doctrine instance from the request information for parameter "%s".', $name));
}
}
}
if (null === $object && false === $configuration->isOptional()) {
throw new NotFoundHttpException(sprintf('%s object not found.', $class));
}
$request->attributes->set($name, $object);
return true;
}
protected function find($class, Request $request, $options, $name)
{
if ($options['mapping'] || $options['exclude']) {
return false;
}
$id = $this->getIdentifier($request, $options, $name);
if (false === $id || null === $id) {
return false;
}
if (isset($options['repository_method'])) {
$method = $options['repository_method'];
} else {
$method = 'find';
}
try {
return $this->getManager($options['entity_manager'], $class)->getRepository($class)->$method($id);
} catch (NoResultException $e) {
return;
}
}
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') && !isset($options['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;
}
// if a specific id has been defined in the options and there is no corresponding attribute
// return false in order to avoid a fallback to the id which might be of another object
if (isset($options['id']) && null === $request->attributes->get($options['id'])) {
return false;
}
$criteria = array();
$em = $this->getManager($options['entity_manager'], $class);
$metadata = $em->getClassMetadata($class);
$mapMethodSignature = isset($options['repository_method'])
&& isset($options['map_method_signature'])
&& $options['map_method_signature'] === true;
foreach ($options['mapping'] as $attribute => $field) {
if ($metadata->hasField($field)
|| ($metadata->hasAssociation($field) && $metadata->isSingleValuedAssociation($field))
|| $mapMethodSignature) {
$criteria[$field] = $request->attributes->get($attribute);
}
}
if ($options['strip_null']) {
$criteria = array_filter($criteria, function ($value) { return !is_null($value); });
}
if (!$criteria) {
return false;
}
if (isset($options['repository_method'])) {
$repositoryMethod = $options['repository_method'];
} else {
$repositoryMethod = 'findOneBy';
}
try {
if ($mapMethodSignature) {
return $this->findDataByMapMethodSignature($em, $class, $repositoryMethod, $criteria);
}
return $em->getRepository($class)->$repositoryMethod($criteria);
} catch (NoResultException $e) {
return;
}
}
private function findDataByMapMethodSignature($em, $class, $repositoryMethod, $criteria)
{
$arguments = array();
$repository = $em->getRepository($class);
$ref = new \ReflectionMethod($repository, $repositoryMethod);
foreach ($ref->getParameters() as $parameter) {
if (array_key_exists($parameter->name, $criteria)) {
$arguments[] = $criteria[$parameter->name];
} elseif ($parameter->isDefaultValueAvailable()) {
$arguments[] = $parameter->getDefaultValue();
} else {
throw new \InvalidArgumentException(sprintf('Repository method "%s::%s" requires that you provide a value for the "$%s" argument.', get_class($repository), $repositoryMethod, $parameter->name));
}
}
return $ref->invokeArgs($repository, $arguments);
}
/**
* {@inheritdoc}
*/
public function supports(ParamConverter $configuration)
{
// if there is no manager, this means that only Doctrine DBAL is configured
if (null === $this->registry || !count($this->registry->getManagers())) {
return false;
}
if (null === $configuration->getClass()) {
return false;
}
$options = $this->getOptions($configuration);
// Doctrine Entity?
$em = $this->getManager($options['entity_manager'], $configuration->getClass());
if (null === $em) {
return false;
}
return !$em->getMetadataFactory()->isTransient($configuration->getClass());
}
protected function getOptions(ParamConverter $configuration)
{
return array_replace(array(
'entity_manager' => null,
'exclude' => array(),
'mapping' => array(),
'strip_null' => false,
), $configuration->getOptions());
}
private function getManager($name, $class)
{
if (null === $name) {
return $this->registry->getManagerForClass($class);
}
return $this->registry->getManager($name);
}
}

View file

@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\HttpFoundation\Request;
/**
* Converts request parameters to objects and stores them as request
* attributes, so they can be injected as controller method arguments.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface ParamConverterInterface
{
/**
* Stores the object in the request.
*
* @param Request $request The request
* @param ParamConverter $configuration Contains the name, class and options of the object
*
* @return bool True if the object has been successfully set, else false
*/
public function apply(Request $request, ParamConverter $configuration);
/**
* Checks if the object is supported.
*
* @param ParamConverter $configuration Should be an instance of ParamConverter
*
* @return bool True if the object is supported, else false
*/
public function supports(ParamConverter $configuration);
}

View file

@ -0,0 +1,144 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter;
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ConfigurationInterface;
/**
* Managers converters.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Henrik Bjornskov <henrik@bjrnskov.dk>
*/
class ParamConverterManager
{
/**
* @var array
*/
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.
*
* @param Request $request
* @param array|object $configurations
*/
public function apply(Request $request, $configurations)
{
if (is_object($configurations)) {
$configurations = array($configurations);
}
foreach ($configurations as $configuration) {
$this->applyConverter($request, $configuration);
}
}
/**
* Apply converter on request based on the given configuration.
*
* @param Request $request
* @param ConfigurationInterface $configuration
*/
protected function applyConverter(Request $request, ConfigurationInterface $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 conversion 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()
));
}
$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;
}
}
}
}
/**
* 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 int $priority The priority (between -10 and 10).
* @param string $name Name of the converter.
*/
public function add(ParamConverterInterface $converter, $priority = 0, $name = null)
{
if ($priority !== null) {
if (!isset($this->converters[$priority])) {
$this->converters[$priority] = array();
}
$this->converters[$priority][] = $converter;
}
if (null !== $name) {
$this->namedConverters[$name] = $converter;
}
}
/**
* Returns all registered param converters.
*
* @return array An array of param converters
*/
public function all()
{
krsort($this->converters);
$converters = array();
foreach ($this->converters as $all) {
$converters = array_merge($converters, $all);
}
return $converters;
}
}

View file

@ -0,0 +1,62 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Converts HttpFoundation Request to PSR-7 ServerRequest using the bridge.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class PsrServerRequestParamConverter implements ParamConverterInterface
{
/**
* @var array
*/
private static $supportedTypes = array(
'Psr\Http\Message\ServerRequestInterface' => true,
'Psr\Http\Message\RequestInterface' => true,
'Psr\Http\Message\MessageInterface' => true,
);
/**
* @var HttpMessageFactoryInterface
*/
private $httpMessageFactory;
public function __construct(HttpMessageFactoryInterface $httpMessageFactory)
{
$this->httpMessageFactory = $httpMessageFactory;
}
/**
* {@inheritdoc}
*/
public function apply(Request $request, ParamConverter $configuration)
{
$request->attributes->set($configuration->getName(), $this->httpMessageFactory->createRequest($request));
}
/**
* {@inheritdoc}
*/
public function supports(ParamConverter $configuration)
{
if (null === $configuration->getClass()) {
return false;
}
return isset(self::$supportedTypes[$configuration->getClass()]);
}
}

View file

@ -0,0 +1,17 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="sensio_framework_extra.controller.listener.class">Sensio\Bundle\FrameworkExtraBundle\EventListener\ControllerListener</parameter>
</parameters>
<services>
<service id="sensio_framework_extra.controller.listener" class="%sensio_framework_extra.controller.listener.class%">
<tag name="kernel.event_subscriber" />
<argument type="service" id="annotation_reader" />
</service>
</services>
</container>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="sensio_framework_extra.cache.listener" class="Sensio\Bundle\FrameworkExtraBundle\EventListener\HttpCacheListener">
<tag name="kernel.event_subscriber" />
</service>
</services>
</container>

View file

@ -0,0 +1,32 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<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>
<service id="sensio_framework_extra.converter.listener" class="%sensio_framework_extra.converter.listener.class%">
<tag name="kernel.event_subscriber" />
<argument type="service" id="sensio_framework_extra.converter.manager" />
<argument>true</argument>
</service>
<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" 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

@ -0,0 +1,23 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="sensio_framework_extra.psr7.http_message_factory" class="Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory" public="false" />
<service id="sensio_framework_extra.psr7.http_foundation_factory" class="Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory" public="false" />
<service id="sensio_framework_extra.psr7.converter.server_request" class="Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\PsrServerRequestParamConverter">
<argument type="service" id="sensio_framework_extra.psr7.http_message_factory" />
<tag name="request.param_converter" converter="psr.server_request" />
</service>
<service id="sensio_framework_extra.psr7.listener.response" class="Sensio\Bundle\FrameworkExtraBundle\EventListener\PsrResponseListener">
<argument type="service" id="sensio_framework_extra.psr7.http_foundation_factory" />
<tag name="kernel.event_subscriber" />
</service>
</services>
</container>

View file

@ -0,0 +1,31 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="sensio_framework_extra.routing.loader.annot_dir.class">Symfony\Component\Routing\Loader\AnnotationDirectoryLoader</parameter>
<parameter key="sensio_framework_extra.routing.loader.annot_file.class">Symfony\Component\Routing\Loader\AnnotationFileLoader</parameter>
<parameter key="sensio_framework_extra.routing.loader.annot_class.class">Sensio\Bundle\FrameworkExtraBundle\Routing\AnnotatedRouteControllerLoader</parameter>
</parameters>
<services>
<service id="sensio_framework_extra.routing.loader.annot_class" class="%sensio_framework_extra.routing.loader.annot_class.class%" public="false">
<tag name="routing.loader" />
<argument type="service" id="annotation_reader" />
</service>
<service id="sensio_framework_extra.routing.loader.annot_dir" class="%sensio_framework_extra.routing.loader.annot_dir.class%" public="false">
<tag name="routing.loader" />
<argument type="service" id="file_locator" />
<argument type="service" id="sensio_framework_extra.routing.loader.annot_class" />
</service>
<service id="sensio_framework_extra.routing.loader.annot_file" class="%sensio_framework_extra.routing.loader.annot_file.class%" public="false">
<tag name="routing.loader" />
<argument type="service" id="file_locator" />
<argument type="service" id="sensio_framework_extra.routing.loader.annot_class" />
</service>
</services>
</container>

View file

@ -0,0 +1,20 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="sensio_framework_extra.security.listener" class="Sensio\Bundle\FrameworkExtraBundle\EventListener\SecurityListener">
<argument type="service" id="security.context" on-invalid="null" />
<argument type="service" id="sensio_framework_extra.security.expression_language" on-invalid="null" />
<argument type="service" id="security.authentication.trust_resolver" on-invalid="null" />
<argument type="service" id="security.role_hierarchy" on-invalid="null" />
<argument type="service" id="security.token_storage" on-invalid="null" />
<argument type="service" id="security.authorization_checker" on-invalid="null" />
<tag name="kernel.event_subscriber" />
</service>
<service id="sensio_framework_extra.security.expression_language.default" class="Sensio\Bundle\FrameworkExtraBundle\Security\ExpressionLanguage" public="false" />
</services>
</container>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="sensio_framework_extra.view.guesser.class">Sensio\Bundle\FrameworkExtraBundle\Templating\TemplateGuesser</parameter>
</parameters>
<services>
<service id="sensio_framework_extra.view.guesser" class="%sensio_framework_extra.view.guesser.class%" public="true">
<argument type="service" id="kernel" />
</service>
</services>
</container>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="sensio_framework_extra.view.listener.class">Sensio\Bundle\FrameworkExtraBundle\EventListener\TemplateListener</parameter>
</parameters>
<services>
<service id="sensio_framework_extra.view.listener" class="%sensio_framework_extra.view.listener.class%">
<tag name="kernel.event_subscriber" />
<argument type="service" id="service_container" />
</service>
</services>
</container>

View file

@ -0,0 +1,106 @@
@Cache
======
The ``@Cache`` annotation makes it easy to define HTTP caching headers for
expiration and validation.
HTTP Expiration Strategies
--------------------------
The ``@Cache`` annotation makes it easy to define HTTP caching::
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
/**
* @Cache(expires="tomorrow", public=true)
*/
public function indexAction()
{
}
You can also use the annotation on a class to define caching for all actions
of a controller::
/**
* @Cache(expires="tomorrow", public=true)
*/
class BlogController extends Controller
{
}
When there is a conflict between the class configuration and the method
configuration, the latter overrides the former::
/**
* @Cache(expires="tomorrow")
*/
class BlogController extends Controller
{
/**
* @Cache(expires="+2 days")
*/
public function indexAction()
{
}
}
.. note::
The ``expires`` attribute takes any valid date understood by the PHP
``strtotime()`` function.
HTTP Validation Strategies
--------------------------
The ``lastModified`` and ``ETag`` attributes manage the HTTP validation cache
headers. ``lastModified`` adds a ``Last-Modified`` header to Responses and
``ETag`` adds an ``ETag`` header.
Both automatically trigger the logic to return a 304 response when the
response is not modified (in this case, the controller is **not** called)::
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
/**
* @Cache(lastModified="post.getUpdatedAt()", ETag="'Post' ~ post.getId() ~ post.getUpdatedAt().getTimestamp()")
*/
public function indexAction(Post $post)
{
// your code
// won't be called in case of a 304
}
It's roughly doing the same as the following code::
public function myAction(Request $request, Post $post)
{
$response = new Response();
$response->setLastModified($post->getUpdatedAt());
if ($response->isNotModified($request)) {
return $response;
}
// your code
}
.. note::
The ETag HTTP header value is the result of the expression hashed with the
``sha256`` algorithm.
Attributes
----------
Here is a list of accepted attributes and their HTTP header equivalent:
======================================================================= ================================
Annotation Response Method
======================================================================= ================================
``@Cache(expires="tomorrow")`` ``$response->setExpires()``
``@Cache(smaxage="15")`` ``$response->setSharedMaxAge()``
``@Cache(maxage="15")`` ``$response->setMaxAge()``
``@Cache(vary={"Cookie"})`` ``$response->setVary()``
``@Cache(public=true)`` ``$response->setPublic()``
``@Cache(lastModified="post.getUpdatedAt()")`` ``$response->setLastModified()``
``@Cache(ETag="post.getId() ~ post.getUpdatedAt().getTimestamp()")`` ``$response->setETag()``
======================================================================= ================================

View file

@ -0,0 +1,305 @@
@ParamConverter
===============
Usage
-----
The ``@ParamConverter`` annotation calls *converters* to convert request
parameters to objects. These objects are stored as request attributes and so
they can be injected as controller method arguments::
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
/**
* @Route("/blog/{id}")
* @ParamConverter("post", class="SensioBlogBundle:Post")
*/
public function showAction(Post $post)
{
}
Several things happen under the hood:
* The converter tries to get a ``SensioBlogBundle:Post`` object from the
request attributes (request attributes comes from route placeholders -- here
``id``);
* If no ``Post`` object is found, a ``404`` Response is generated;
* If a ``Post`` object is found, a new ``post`` request attribute is defined
(accessible via ``$request->attributes->get('post')``);
* As for other request attributes, it is automatically injected in the
controller when present in the method signature.
If you use type hinting as in the example above, you can even omit the
``@ParamConverter`` annotation altogether::
// automatic with method signature
public function showAction(Post $post)
{
}
.. tip::
You can disable the auto-conversion of type-hinted method arguments feature
by setting the ``auto_convert`` flag to ``false``:
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
sensio_framework_extra:
request:
converters: true
auto_convert: false
.. code-block:: xml
<sensio-framework-extra:config>
<request converters="true" auto-convert="true" />
</sensio-framework-extra:config>
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 two built-in converters, 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::
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
/**
* @Route("/blog/{id}")
* @ParamConverter("post", class="SensioBlogBundle:Post", options={"entity_manager" = "foo"})
*/
public function showAction(Post $post)
{
}
If the placeholder does not have the same name as the primary key, pass the ``id``
option::
/**
* @Route("/blog/{post_id}")
* @ParamConverter("post", class="SensioBlogBundle:Post", options={"id" = "post_id"})
*/
public function showAction(Post $post)
{
}
.. tip::
The ``id`` option specifies which placeholder from the route gets passed to the repository
method used. If no repository method is specified, ``find()`` is used by default.
This also allows you to have multiple converters in one action::
/**
* @Route("/blog/{id}/comments/{comment_id}")
* @ParamConverter("comment", class="SensioBlogBundle:Comment", options={"id" = "comment_id"})
*/
public function showAction(Post $post, Comment $comment)
{
}
In the example above, the ``$post`` parameter is handled automatically, but ``$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 the ``mapping`` hash
option: the key is route placeholder name and the value is the Doctrine
field name::
/**
* @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)
{
}
If you want to specify the repository method to use to find the entity (for example,
to add joins to the query), you can add the ``repository_method`` option::
/**
* @Route("/blog/{id}")
* @ParamConverter("post", class="SensioBlogBundle:Post", options={"repository_method" = "findWithJoins"})
*/
public function showAction(Post $post)
{
}
The specified repository method will be called with the criteria in an ``array``
as parameter. This is a good fit with Doctrine's ``findBy`` and ``findOneBy``
methods.
There are cases where you want to you use your own repository method and you
want to map the criteria to the method signature. This is possible when you set
the ``map_method_signature`` option to true. The default is false::
/**
* @Route("/user/{first_name}/{last_name}")
* @ParamConverter("user", class="AcmeBlogBundle:User", options={
* "repository_method" = "findByFullName",
* "mapping": {"first_name": "firstName", "last_name": "lastName"},
* "map_method_signature" = true
* })
*/
public function showAction(User $user)
{
}
class UserRepository
{
public function findByFullName($firstName, $lastName)
{
...
}
}
.. tip::
When ``map_method_signature`` is ``true``, the ``firstName`` and
``lastName`` parameters do not have to be Doctrine fields.
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
--------------------
All converters must implement the ``ParamConverterInterface``::
namespace Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\HttpFoundation\Request;
interface ParamConverterInterface
{
function apply(Request $request, ParamConverter $configuration);
function supports(ParamConverter $configuration);
}
The ``supports()`` method must return ``true`` when it is able to convert the
given configuration (a ``ParamConverter`` instance).
The ``ParamConverter`` instance has three pieces of information about the annotation:
* ``name``: The attribute name;
* ``class``: The attribute class name (can be any string representing a class
name);
* ``options``: An array of options.
The ``apply()`` method is called whenever a configuration is supported. Based
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:: yaml
# app/config/config.yml
services:
my_converter:
class: MyBundle\Request\ParamConverter\MyConverter
tags:
- { name: request.param_converter, priority: -2, converter: my_converter }
.. code-block:: xml
<service id="my_converter" class="MyBundle\Request\ParamConverter\MyConverter">
<tag name="request.param_converter" priority="-2" converter="my_converter" />
</service>
You can register a converter by priority, by name (attribute "converter"), or
both. If you don't specify a priority or a 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::
If you would like to inject services or additional arguments into a custom
param converter, the priority shouldn't be higher than ``1``. Otherwise, the
service wouldn't be loaded.
.. tip::
Use the ``DoctrineParamConverter`` class as a template for your own converters.

View file

@ -0,0 +1,184 @@
@Route and @Method
==================
Usage
-----
The ``@Route`` annotation maps a route pattern with a controller::
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
class PostController extends Controller
{
/**
* @Route("/")
*/
public function indexAction()
{
// ...
}
}
The ``index`` action of the ``Post`` controller is now mapped to the ``/``
URL. This is equivalent to the following YAML configuration:
.. code-block:: yaml
blog_home:
path: /
defaults: { _controller: SensioBlogBundle:Post:index }
Like any route pattern, you can define placeholders, requirements, and default
values::
/**
* @Route("/{id}", requirements={"id" = "\d+"}, defaults={"id" = 1})
*/
public function showAction($id)
{
}
You can also define the default value for a placeholder with
the PHP default value::
/**
* @Route("/{id}", requirements={"id" = "\d+"})
*/
public function showAction($id = 1)
{
}
You can also match more than one URL by defining additional ``@Route``
annotations::
/**
* @Route("/", defaults={"id" = 1})
* @Route("/{id}")
*/
public function showAction($id)
{
}
.. _frameworkextra-annotations-routing-activation:
Activation
----------
The routes need to be imported to be active as any other routing resources
(note the ``annotation`` type):
.. code-block:: yaml
# app/config/routing.yml
# import routes from a controller class
post:
resource: "@SensioBlogBundle/Controller/PostController.php"
type: annotation
You can also import a whole directory:
.. code-block:: yaml
# import routes from a controller directory
blog:
resource: "@SensioBlogBundle/Controller"
type: annotation
As for any other resource, you can "mount" the routes under a given prefix:
.. code-block:: yaml
post:
resource: "@SensioBlogBundle/Controller/PostController.php"
prefix: /blog
type: annotation
Route Name
----------
A route defined with the ``@Route`` annotation is given a default name composed
of the bundle name, the controller name and the action name. That would be
``sensio_blog_post_index`` for the above example;
The ``name`` attribute can be used to override this default route name::
/**
* @Route("/", name="blog_home")
*/
public function indexAction()
{
// ...
}
Route Prefix
------------
A ``@Route`` annotation on a controller class defines a prefix for all action
routes (note that you cannot have more than one ``@Route`` annotation on a
class)::
/**
* @Route("/blog")
*/
class PostController extends Controller
{
/**
* @Route("/{id}")
*/
public function showAction($id)
{
}
}
The ``show`` action is now mapped to the ``/blog/{id}`` pattern.
Route Method
------------
There is a shortcut ``@Method`` annotation to specify the HTTP method allowed
for the route. To use it, import the ``Method`` annotation namespace::
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
/**
* @Route("/blog")
*/
class PostController extends Controller
{
/**
* @Route("/edit/{id}")
* @Method({"GET", "POST"})
*/
public function editAction($id)
{
}
}
The ``edit`` action is now mapped to the ``/blog/edit/{id}`` pattern if the HTTP
method used is either GET or POST.
The ``@Method`` annotation is only considered when an action is annotated with
``@Route``.
Controller as Service
---------------------
The ``@Route`` annotation on a controller class can also be used to assign the
controller class to a service so that the controller resolver will instantiate
the controller by fetching it from the DI container instead of calling ``new
PostController()`` itself::
/**
* @Route(service="my_post_controller_service")
*/
class PostController
{
// ...
}
.. tip::
You can also omit the ``service`` option if your service ID is your controller
fully-qualified class name (FQCN).

View file

@ -0,0 +1,68 @@
@Security
=========
.. caution::
The ``@Security`` annotation only works as of Symfony 2.4.
Usage
-----
The ``@Security`` annotation restricts access on controllers::
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
class PostController extends Controller
{
/**
* @Security("has_role('ROLE_ADMIN')")
*/
public function indexAction()
{
// ...
}
}
The expression can use all functions that you can use in the ``access_control``
section of the security bundle configuration, with the addition of the
``is_granted()`` function.
The expression has access to the following variables:
* ``token``: The current security token;
* ``user``: The current user object;
* ``request``: The request instance;
* ``roles``: The user roles;
* and all request attributes.
The ``is_granted()`` function allows you to restrict access based on variables
passed to the controller::
/**
* @Security("is_granted('POST_SHOW', post)")
*/
public function showAction(Post $post)
{
}
Here is another example, making use of multiple functions in the expression::
/**
* @Security("is_granted('POST_SHOW', post) and has_role('ROLE_ADMIN')")
*/
public function showAction(Post $post)
{
}
.. note::
Defining a ``Security`` annotation has the same effect as defining an
access control rule, but it is more efficient as the check is only done
when this specific route is accessed. To create new access control
rules, please refer to `the Security Voters page`_.
.. tip::
You can also add a ``@Security`` annotation on a controller class.
.. _`the Security Voters page`: http://symfony.com/doc/current/cookbook/security/voters_data_permission.html

View file

@ -0,0 +1,100 @@
@Template
=========
Usage
-----
The ``@Template`` annotation associates a controller with a template name::
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
/**
* @Template("SensioBlogBundle:Post:show.html.twig")
*/
public function showAction($id)
{
// get the Post
$post = ...;
return array('post' => $post);
}
When using the ``@Template`` annotation, the controller should return an
array of parameters to pass to the view instead of a ``Response`` object.
.. note::
If you want to stream your template, you can make it with the following configuration::
/**
* @Template(isStreamable=true)
*/
public function showAction($id)
{
// ...
}
.. tip::
If the action returns a ``Response`` object, the ``@Template``
annotation is simply ignored.
If the template is named after the controller and action names, which is the
case for the above example, you can even omit the annotation value::
/**
* @Template
*/
public function showAction($id)
{
// get the Post
$post = ...;
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
<converters>`::
/**
* @ParamConverter("post", class="SensioBlogBundle:Post")
* @Template("SensioBlogBundle:Post:show.html.twig", vars={"post"})
*/
public function showAction(Post $post)
{
}
which, thanks to conventions, is equivalent to the following configuration::
/**
* @Template(vars={"post"})
*/
public function showAction(Post $post)
{
}
You can make it even more concise as all method arguments are automatically
passed to the template if the method returns ``null`` and no ``vars``
attribute is defined::
/**
* @Template
*/
public function showAction(Post $post)
{
}

View file

@ -0,0 +1,231 @@
SensioFrameworkExtraBundle
==========================
The default Symfony ``FrameworkBundle`` implements a basic but robust and
flexible MVC framework. `SensioFrameworkExtraBundle`_ extends it to add sweet
conventions and annotations. It allows for more concise controllers.
Installation
------------
Before using this bundle in your project, add it to your ``composer.json`` file:
.. code-block:: bash
$ composer require sensio/framework-extra-bundle
Then, like for any other bundle, include it in your Kernel class::
public function registerBundles()
{
$bundles = array(
// ...
new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
);
// ...
}
.. _release-cycle-note:
.. note::
Since SensioFrameworkExtraBundle 3.0 its release cycle is out of sync
with Symfony's release cycle. This means that you can simply require
``sensio/framework-extra-bundle: ~3.0`` in your ``composer.json`` file
and Composer will automatically pick the latest bundle version for you.
You have to use Symfony 2.3 or later for this workflow. Before Symfony
2.3, the required version of the SensioFrameworkExtraBundle should be
the same as your Symfony version.
If you plan to use or create annotations for controllers, make sure to update
your ``autoload.php`` by adding the following line::
Doctrine\Common\Annotations\AnnotationRegistry::registerLoader(array($loader, 'loadClass'));
Configuration
-------------
All features provided by the bundle are enabled by default when the bundle is
registered in your Kernel class.
The default configuration is as follow:
.. configuration-block::
.. code-block:: yaml
sensio_framework_extra:
router: { annotations: true }
request: { converters: true, auto_convert: true }
view: { annotations: true }
cache: { annotations: true }
security: { annotations: true }
psr_message: { enabled: false } # Defaults to true if the PSR-7 bridge is installed
.. code-block:: xml
<!-- xmlns:sensio-framework-extra="http://symfony.com/schema/dic/symfony_extra" -->
<sensio-framework-extra:config>
<router annotations="true" />
<request converters="true" auto_convert="true" />
<view annotations="true" />
<cache annotations="true" />
<security annotations="true" />
<psr-message enabled="false" /> <!-- Defaults to true if the PSR-7 bridge is installed -->
</sensio-framework-extra:config>
.. code-block:: php
// load the profiler
$container->loadFromExtension('sensio_framework_extra', array(
'router' => array('annotations' => true),
'request' => array('converters' => true, 'auto_convert' => true),
'view' => array('annotations' => true),
'cache' => array('annotations' => true),
'security' => array('annotations' => true),
'psr_message' => array('enabled' => false), // Defaults to true if the PSR-7 bridge is installed
));
You can disable some annotations and conventions by defining one or more
settings to false.
Annotations for Controllers
---------------------------
Annotations are a great way to easily configure your controllers, from the
routes to the cache configuration.
Even if annotations are not a native feature of PHP, it still has several
advantages over the classic Symfony configuration methods:
* Code and configuration are in the same place (the controller class);
* Simple to learn and to use;
* Concise to write;
* Makes your Controller thin (as its sole responsibility is to get data from
the Model).
.. tip::
If you use view classes, annotations are a great way to avoid creating
view classes for simple and common use cases.
The following annotations are defined by the bundle:
.. toctree::
:maxdepth: 1
annotations/routing
annotations/converters
annotations/view
annotations/cache
annotations/security
This example shows all the available annotations in action::
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
/**
* @Route("/blog")
* @Cache(expires="tomorrow")
*/
class AnnotController
{
/**
* @Route("/")
* @Template
*/
public function indexAction()
{
$posts = ...;
return array('posts' => $posts);
}
/**
* @Route("/{id}")
* @Method("GET")
* @ParamConverter("post", class="SensioBlogBundle:Post")
* @Template("SensioBlogBundle:Annot:show.html.twig", vars={"post"})
* @Cache(smaxage="15", lastmodified="post.getUpdatedAt()", etag="'Post' ~ post.getId() ~ post.getUpdatedAt()")
* @Security("has_role('ROLE_ADMIN') and is_granted('POST_SHOW', post)")
*/
public function showAction(Post $post)
{
}
}
As the ``showAction`` method follows some conventions, you can omit some
annotations::
/**
* @Route("/{id}")
* @Cache(smaxage="15", lastModified="post.getUpdatedAt()", ETag="'Post' ~ post.getId() ~ post.getUpdatedAt()")
* @Security("has_role('ROLE_ADMIN') and is_granted('POST_SHOW', post)")
*/
public function showAction(Post $post)
{
}
The routes need to be imported to be active as any other routing resources, for
example:
.. code-block:: yaml
# app/config/routing.yml
# import routes from a controller directory
annot:
resource: "@AnnotRoutingBundle/Controller"
type: annotation
see :ref:`Annotated Routes Activation<frameworkextra-annotations-routing-activation>` for more details.
PSR-7 support
-------------
SensioFrameworkExtraBundle provides support for HTTP messages interfaces defined
in `PSR-7`_. It allows to inject instances of ``Psr\Http\Message\ServerRequestInterface``
and to return instances of ``Psr\Http\Message\ResponseInterface`` in controllers.
To enable this feature, `the HttpFoundation to PSR-7 bridge`_ and `Zend Diactoros`_ must be installed:
.. code-block:: bash
$ composer require symfony/psr-http-message-bridge zendframework/zend-diactoros
Then, PSR-7 messages can be used directly in controllers like in the following code
snippet::
namespace AppBundle\Controller;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Response;
class DefaultController
{
public function indexAction(ServerRequestInterface $request)
{
// Interact with the PSR-7 request
$response = new Response();
// Interact with the PSR-7 response
return $response;
}
}
Note that internally, Symfony always use :class:`Symfony\\Component\\HttpFoundation\\Request`
and :class:`Symfony\\Component\\HttpFoundation\\Response` instances.
.. _`SensioFrameworkExtraBundle`: https://github.com/sensiolabs/SensioFrameworkExtraBundle
.. _`PSR-7`: http://www.php-fig.org/psr/psr-7/
.. _`the HttpFoundation to PSR-7 bridge`: https://github.com/symfony/psr-http-message-bridge
.. _`Zend Diactoros`: https://github.com/zendframework/zend-diactoros

View file

@ -0,0 +1,95 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
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;
/**
* AnnotatedRouteControllerLoader is an implementation of AnnotationClassLoader
* that sets the '_controller' default based on the class and method names.
*
* It also parse the @Method annotation.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class AnnotatedRouteControllerLoader extends AnnotationClassLoader
{
/**
* Configures the _controller default parameter and eventually the HTTP method
* requirement of a given Route instance.
*
* @param Route $route A route instance
* @param \ReflectionClass $class A ReflectionClass instance
* @param \ReflectionMethod $method A ReflectionClass method
* @param mixed $annot The annotation class instance
*
* @throws \LogicException When the service option is specified on a method
*/
protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, $annot)
{
// controller
$classAnnot = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass);
if ($classAnnot instanceof FrameworkExtraBundleRoute && $service = $classAnnot->getService()) {
$route->setDefault('_controller', $service.':'.$method->getName());
} else {
$route->setDefault('_controller', $class->getName().'::'.$method->getName());
}
// requirements (@Method)
foreach ($this->reader->getMethodAnnotations($method) as $configuration) {
if ($configuration instanceof Method) {
$route->setMethods(implode('|', $configuration->getMethods()));
} elseif ($configuration instanceof FrameworkExtraBundleRoute && $configuration->getService()) {
throw new \LogicException('The service option can only be specified at class level.');
}
}
}
protected function getGlobals(\ReflectionClass $class)
{
$globals = parent::getGlobals($class);
foreach ($this->reader->getClassAnnotations($class) as $configuration) {
if ($configuration instanceof Method) {
$globals['methods'] = array_merge($globals['methods'], $configuration->getMethods());
}
}
return $globals;
}
/**
* Makes the default route name more sane by removing common keywords.
*
* @param \ReflectionClass $class A ReflectionClass instance
* @param \ReflectionMethod $method A ReflectionMethod instance
*
* @return string The default route name
*/
protected function getDefaultRouteName(\ReflectionClass $class, \ReflectionMethod $method)
{
$routeName = parent::getDefaultRouteName($class, $method);
return preg_replace(array(
'/(bundle|controller)_/',
'/action(_\d+)?$/',
'/__/',
), array(
'_',
'\\1',
'_',
), $routeName);
}
}

View file

@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Security;
use Symfony\Component\Security\Core\Authorization\ExpressionLanguage as BaseExpressionLanguage;
/**
* Adds some function to the default Symfony Security ExpressionLanguage.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ExpressionLanguage extends BaseExpressionLanguage
{
protected function registerFunctions()
{
parent::registerFunctions();
$this->register('is_granted', function ($attributes, $object = 'null') {
return sprintf('$auth_checker->isGranted(%s, %s)', $attributes, $object);
}, function (array $variables, $attributes, $object = null) {
return $variables['auth_checker']->isGranted($attributes, $object);
});
}
}

View file

@ -0,0 +1,33 @@
<?php
namespace Sensio\Bundle\FrameworkExtraBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Sensio\Bundle\FrameworkExtraBundle\DependencyInjection\Compiler\AddExpressionLanguageProvidersPass;
use Sensio\Bundle\FrameworkExtraBundle\DependencyInjection\Compiler\AddParamConverterPass;
use Sensio\Bundle\FrameworkExtraBundle\DependencyInjection\Compiler\LegacyPass;
/*
* 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.
*/
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class SensioFrameworkExtraBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new AddParamConverterPass());
$container->addCompilerPass(new LegacyPass());
$container->addCompilerPass(new AddExpressionLanguageProvidersPass());
}
}

View file

@ -0,0 +1,133 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Templating;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Templating\TemplateReference;
use Doctrine\Common\Util\ClassUtils;
/**
* The TemplateGuesser class handles the guessing of template name based on controller.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TemplateGuesser
{
/**
* @var KernelInterface
*/
protected $kernel;
/**
* @var string[]
*/
private $controllerPatterns;
/**
* Constructor.
*
* @param KernelInterface $kernel A KernelInterface instance
* @param string[] $controllerPatterns Regexps extracting the controller name from its FQN.
*/
public function __construct(KernelInterface $kernel, array $controllerPatterns = array())
{
$controllerPatterns[] = '/Controller\\\(.+)Controller$/';
$this->kernel = $kernel;
$this->controllerPatterns = $controllerPatterns;
}
/**
* Guesses and returns the template name to render based on the controller
* and action names.
*
* @param callable $controller An array storing the controller object and action method
* @param Request $request A Request instance
* @param string $engine
*
* @return TemplateReference template reference
*
* @throws \InvalidArgumentException
*/
public function guessTemplateName($controller, Request $request, $engine = 'twig')
{
if (is_object($controller) && method_exists($controller, '__invoke')) {
$controller = array($controller, '__invoke');
} elseif (!is_array($controller)) {
throw new \InvalidArgumentException(sprintf('First argument of %s must be an array callable or an object defining the magic method __invoke. "%s" given.', __METHOD__, gettype($controller)));
}
$className = class_exists('Doctrine\Common\Util\ClassUtils') ? ClassUtils::getClass($controller[0]) : get_class($controller[0]);
$matchController = null;
foreach ($this->controllerPatterns as $pattern) {
if (preg_match($pattern, $className, $tempMatch)) {
$matchController = $tempMatch;
break;
}
}
if (null === $matchController) {
throw new \InvalidArgumentException(sprintf('The "%s" class does not look like a controller class (its FQN must match one of the following regexps: "%s")', get_class($controller[0]), implode('", "', $this->controllerPatterns)));
}
if ($controller[1] === '__invoke') {
$matchAction = $matchController;
$matchController = null;
} elseif (!preg_match('/^(.+)Action$/', $controller[1], $matchAction)) {
$matchAction = array(null, $controller[1]);
}
$bundle = $this->getBundleForClass($className);
if ($bundle) {
while ($bundleName = $bundle->getName()) {
if (null === $parentBundleName = $bundle->getParent()) {
$bundleName = $bundle->getName();
break;
}
$bundles = $this->kernel->getBundle($parentBundleName, false);
$bundle = array_pop($bundles);
}
} else {
$bundleName = null;
}
return new TemplateReference($bundleName, $matchController[1], $matchAction[1], $request->getRequestFormat(), $engine);
}
/**
* Returns the Bundle instance in which the given class name is located.
*
* @param string $class A fully qualified controller class name
*
* @return Bundle|null $bundle A Bundle instance
*/
protected function getBundleForClass($class)
{
$reflectionClass = new \ReflectionClass($class);
$bundles = $this->kernel->getBundles();
do {
$namespace = $reflectionClass->getNamespaceName();
foreach ($bundles as $bundle) {
if (0 === strpos($namespace, $bundle->getNamespace())) {
return $bundle;
}
}
$reflectionClass = $reflectionClass->getParentClass();
} while ($reflectionClass);
}
}

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Tests\Configuration;
class ConfigurationAnnotationTest extends \PHPUnit_Framework_TestCase
{
/**
* @expectedException RuntimeException
*/
public function testUndefinedSetterThrowsException()
{
$this->getMockForAbstractClass('Sensio\Bundle\FrameworkExtraBundle\Configuration\ConfigurationAnnotation', array(
array(
'doesNotExists' => true,
),
));
}
}

View file

@ -0,0 +1,47 @@
<?php
namespace Sensio\Bundle\FrameworkExtraBundle\Tests\Configuration;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
/**
* @author Iltar van der Berg <ivanderberg@hostnet.nl>
*/
class RouteTest extends \PHPUnit_Framework_TestCase
{
public function testSetServiceWithoutPath()
{
$route = new Route(array());
$this->assertNull($route->getPath());
$this->assertNull($route->getService());
$route->setService('app.test');
$this->assertSame('', $route->getPath());
$this->assertSame('app.test', $route->getService());
}
public function testSetServiceWithPath()
{
$route = new Route(array());
$this->assertNull($route->getPath());
$this->assertNull($route->getService());
$route->setPath('/test/');
$route->setService('app.test');
$this->assertSame('/test/', $route->getPath());
$this->assertSame('app.test', $route->getService());
}
public function testSettersViaConstruct()
{
$route = new Route(array('service' => 'app.test'));
$this->assertSame('', $route->getPath());
$this->assertSame('app.test', $route->getService());
$route = new Route(array('service' => 'app.test', 'path' => '/test/'));
$this->assertSame('/test/', $route->getPath());
$this->assertSame('app.test', $route->getService());
}
}

View file

@ -0,0 +1,73 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Tests\DependencyInjection;
use Sensio\Bundle\FrameworkExtraBundle\DependencyInjection\Compiler\AddExpressionLanguageProvidersPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
class AddExpressionLanguageProvidersPassTest extends \PHPUnit_Framework_TestCase
{
/**
* @var AddExpressionLanguageProvidersPass
*/
private $pass;
/**
* @var ContainerBuilder
*/
private $container;
/**
* @var Definition
*/
private $expressionLangDefinition;
public function setUp()
{
$this->pass = new AddExpressionLanguageProvidersPass();
$this->container = new ContainerBuilder();
$this->expressionLangDefinition = new Definition();
$this->container->setDefinition('sensio_framework_extra.security.expression_language.default', $this->expressionLangDefinition);
}
public function testProcessNoOpNoExpressionLang()
{
$this->container->removeDefinition('sensio_framework_extra.security.expression_language.default');
$this->pass->process($this->container);
}
public function testProcessNoOpNoTaggedServices()
{
$this->pass->process($this->container);
$this->assertCount(0, $this->expressionLangDefinition->getMethodCalls());
}
public function testProcessAddsTaggedServices()
{
$provider = new Definition();
$provider->setTags(array(
'security.expression_language_provider' => array(
array(),
),
));
$this->container->setDefinition('provider', $provider);
$this->pass->process($this->container);
$methodCalls = $this->expressionLangDefinition->getMethodCalls();
$this->assertCount(1, $methodCalls);
$this->assertEquals(array('registerProvider', array(new Reference('provider'))), $methodCalls[0]);
}
}

View file

@ -0,0 +1,97 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Tests\DependencyInjection;
use Sensio\Bundle\FrameworkExtraBundle\DependencyInjection\Compiler\AddParamConverterPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
class AddParamConverterPassTest extends \PHPUnit_Framework_TestCase
{
/**
* @var AddParamConverterPass
*/
private $pass;
/**
* @var ContainerBuilder
*/
private $container;
/**
* @var Definition
*/
private $managerDefinition;
public function setUp()
{
$this->pass = new AddParamConverterPass();
$this->container = new ContainerBuilder();
$this->managerDefinition = new Definition();
$this->container->setDefinition('sensio_framework_extra.converter.manager', $this->managerDefinition);
}
public function testProcessNoOpNoManager()
{
$this->container->removeDefinition('sensio_framework_extra.converter.manager');
$this->pass->process($this->container);
}
public function testProcessNoOpNoTaggedServices()
{
$this->pass->process($this->container);
$this->assertCount(0, $this->managerDefinition->getMethodCalls());
}
public function testProcessAddsTaggedServices()
{
$paramConverter1 = new Definition();
$paramConverter1->setTags(array(
'request.param_converter' => array(
array(
'priority' => 'false',
),
),
));
$paramConverter2 = new Definition();
$paramConverter2->setTags(array(
'request.param_converter' => array(
array(
'converter' => 'foo',
),
),
));
$paramConverter3 = new Definition();
$paramConverter3->setTags(array(
'request.param_converter' => array(
array(
'priority' => 5,
),
),
));
$this->container->setDefinition('param_converter_one', $paramConverter1);
$this->container->setDefinition('param_converter_two', $paramConverter2);
$this->container->setDefinition('param_converter_three', $paramConverter3);
$this->pass->process($this->container);
$methodCalls = $this->managerDefinition->getMethodCalls();
$this->assertCount(3, $methodCalls);
$this->assertEquals(array('add', array(new Reference('param_converter_one'), 0, null)), $methodCalls[0]);
$this->assertEquals(array('add', array(new Reference('param_converter_two'), 0, 'foo')), $methodCalls[1]);
$this->assertEquals(array('add', array(new Reference('param_converter_three'), 5, null)), $methodCalls[2]);
}
}

View file

@ -0,0 +1,124 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Tests\DependencyInjection;
use Sensio\Bundle\FrameworkExtraBundle\DependencyInjection\SensioFrameworkExtraExtension;
use Sensio\Bundle\FrameworkExtraBundle\DependencyInjection\Compiler\LegacyPass;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
class SensioFrameworkExtraExtensionTest extends \PHPUnit_Framework_TestCase
{
/**
* @group legacy
*/
public function testLegacySecurityListener()
{
if (interface_exists('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface')) {
$this->markTestSkipped();
}
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
$container = new ContainerBuilder();
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../../Resources/config'));
$loader->load('security.xml');
$r = new \ReflectionClass('Symfony\Bundle\SecurityBundle\SecurityBundle');
$loader = new XmlFileLoader($container, new FileLocator(dirname($r->getFileName()).'/Resources/config'));
$loader->load('security.xml');
$this->registerLegacyPass($container);
$container->compile();
$securityContext = $container->getDefinition('sensio_framework_extra.security.listener')->getArgument(0);
$this->assertInstanceOf('Symfony\Component\DependencyInjection\Reference', $securityContext);
}
public function testSecurityListener()
{
if (!interface_exists('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface')) {
$this->markTestSkipped();
}
$container = new ContainerBuilder();
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../../Resources/config'));
$loader->load('security.xml');
$r = new \ReflectionClass('Symfony\Bundle\SecurityBundle\SecurityBundle');
$loader = new XmlFileLoader($container, new FileLocator(dirname($r->getFileName()).'/Resources/config'));
$loader->load('security.xml');
$this->registerLegacyPass($container);
$container->compile();
$this->assertNull($container->getDefinition('sensio_framework_extra.security.listener')->getArgument(0));
}
public function testDefaultExpressionLanguageConfig()
{
$container = new ContainerBuilder();
$extension = new SensioFrameworkExtraExtension();
$extension->load(array(), $container);
$this->assertAlias($container, 'sensio_framework_extra.security.expression_language.default', 'sensio_framework_extra.security.expression_language');
}
public function testOverrideExpressionLanguageConfig()
{
$container = new ContainerBuilder();
$extension = new SensioFrameworkExtraExtension();
$config = array(
'security' => array(
'expression_language' => 'acme.security.expression_language',
),
);
$container->setDefinition('acme.security.expression_language', new Definition());
$extension->load(array($config), $container);
$this->assertAlias($container, 'acme.security.expression_language', 'sensio_framework_extra.security.expression_language');
}
public function testTemplatingControllerPatterns()
{
$container = new ContainerBuilder();
$extension = new SensioFrameworkExtraExtension();
$config = array(
'templating' => array(
'controller_patterns' => $patterns = array('/foo/', '/bar/', '/foobar/'),
),
);
$extension->load(array($config), $container);
$this->assertEquals($patterns, $container->getDefinition('sensio_framework_extra.view.guesser')->getArgument(1));
}
private function assertAlias(ContainerBuilder $container, $value, $key)
{
$this->assertEquals($value, (string) $container->getAlias($key), sprintf('%s alias is correct', $key));
}
private function registerLegacyPass(ContainerBuilder $container)
{
$passConfig = $container->getCompiler()->getPassConfig();
$passConfig->setAfterRemovingPasses(array());
$passConfig->setBeforeOptimizationPasses(array());
$passConfig->setBeforeRemovingPasses(array());
$passConfig->setOptimizationPasses(array());
$passConfig->setRemovingPasses(array());
$container->addCompilerPass(new LegacyPass());
}
}

View file

@ -0,0 +1,141 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
use Sensio\Bundle\FrameworkExtraBundle\EventListener\ControllerListener;
use Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture\FooControllerCacheAtClass;
use Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture\FooControllerCacheAtClassAndMethod;
use Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture\FooControllerCacheAtMethod;
use Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture\FooControllerMultipleCacheAtClass;
use Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture\FooControllerMultipleCacheAtMethod;
use Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture\FooControllerParamConverterAtClassAndMethod;
use Doctrine\Common\Annotations\AnnotationReader;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
class ControllerListenerTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
$this->listener = new ControllerListener(new AnnotationReader());
$this->request = $this->createRequest();
// trigger the autoloading of the @Cache annotation
class_exists('Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache');
}
public function tearDown()
{
$this->listener = null;
$this->request = null;
}
public function testCacheAnnotationAtMethod()
{
$controller = new FooControllerCacheAtMethod();
$this->event = $this->getFilterControllerEvent(array($controller, 'barAction'), $this->request);
$this->listener->onKernelController($this->event);
$this->assertNotNull($this->getReadedCache());
$this->assertEquals(FooControllerCacheAtMethod::METHOD_SMAXAGE, $this->getReadedCache()->getSMaxAge());
}
public function testCacheAnnotationAtClass()
{
$controller = new FooControllerCacheAtClass();
$this->event = $this->getFilterControllerEvent(array($controller, 'barAction'), $this->request);
$this->listener->onKernelController($this->event);
$this->assertNotNull($this->getReadedCache());
$this->assertEquals(FooControllerCacheAtClass::CLASS_SMAXAGE, $this->getReadedCache()->getSMaxAge());
}
public function testCacheAnnotationAtClassAndMethod()
{
$controller = new FooControllerCacheAtClassAndMethod();
$this->event = $this->getFilterControllerEvent(array($controller, 'barAction'), $this->request);
$this->listener->onKernelController($this->event);
$this->assertNotNull($this->getReadedCache());
$this->assertEquals(FooControllerCacheAtClassAndMethod::METHOD_SMAXAGE, $this->getReadedCache()->getSMaxAge());
$this->event = $this->getFilterControllerEvent(array($controller, 'bar2Action'), $this->request);
$this->listener->onKernelController($this->event);
$this->assertNotNull($this->getReadedCache());
$this->assertEquals(FooControllerCacheAtClassAndMethod::CLASS_SMAXAGE, $this->getReadedCache()->getSMaxAge());
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage Multiple "cache" annotations are not allowed
*/
public function testMultipleAnnotationsOnClassThrowsExceptionUnlessConfigurationAllowsArray()
{
$controller = new FooControllerMultipleCacheAtClass();
$this->event = $this->getFilterControllerEvent(array($controller, 'barAction'), $this->request);
$this->listener->onKernelController($this->event);
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage Multiple "cache" annotations are not allowed
*/
public function testMultipleAnnotationsOnMethodThrowsExceptionUnlessConfigurationAllowsArray()
{
$controller = new FooControllerMultipleCacheAtMethod();
$this->event = $this->getFilterControllerEvent(array($controller, 'barAction'), $this->request);
$this->listener->onKernelController($this->event);
}
public function testMultipleParamConverterAnnotationsOnMethod()
{
$paramConverter = new \Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter(array());
$controller = new FooControllerParamConverterAtClassAndMethod();
$this->event = $this->getFilterControllerEvent(array($controller, 'barAction'), $this->request);
$this->listener->onKernelController($this->event);
$annotations = $this->request->attributes->get('_converters');
$this->assertNotNull($annotations);
$this->assertArrayHasKey(0, $annotations);
$this->assertInstanceOf('Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter', $annotations[0]);
$this->assertEquals('test', $annotations[0]->getName());
$this->assertArrayHasKey(1, $annotations);
$this->assertInstanceOf('Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter', $annotations[1]);
$this->assertEquals('test2', $annotations[1]->getName());
$this->assertEquals(2, count($annotations));
}
protected function createRequest(Cache $cache = null)
{
return new Request(array(), array(), array(
'_cache' => $cache,
));
}
protected function getFilterControllerEvent($controller, Request $request)
{
$mockKernel = $this->getMockForAbstractClass('Symfony\Component\HttpKernel\Kernel', array('', ''));
return new FilterControllerEvent($mockKernel, $controller, $request, HttpKernelInterface::MASTER_REQUEST);
}
protected function getReadedCache()
{
return $this->request->attributes->get('_cache');
}
}

View file

@ -0,0 +1,17 @@
<?php
namespace Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
/**
* @Cache(smaxage="20")
*/
class FooControllerCacheAtClass
{
const CLASS_SMAXAGE = 20;
public function barAction()
{
}
}

View file

@ -0,0 +1,25 @@
<?php
namespace Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
/**
* @Cache(smaxage="20")
*/
class FooControllerCacheAtClassAndMethod
{
const CLASS_SMAXAGE = 20;
const METHOD_SMAXAGE = 25;
/**
* @Cache(smaxage="25")
*/
public function barAction()
{
}
public function bar2Action()
{
}
}

View file

@ -0,0 +1,17 @@
<?php
namespace Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
class FooControllerCacheAtMethod
{
const METHOD_SMAXAGE = 15;
/**
* @Cache(smaxage="15")
*/
public function barAction()
{
}
}

View file

@ -0,0 +1,17 @@
<?php
namespace Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
/**
* @Cache()
* @Cache()
*/
class FooControllerMultipleCacheAtClass
{
public function barAction()
{
}
}

View file

@ -0,0 +1,17 @@
<?php
namespace Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
class FooControllerMultipleCacheAtMethod
{
/**
* @Cache()
* @Cache()
*/
public function barAction()
{
}
}

View file

@ -0,0 +1,18 @@
<?php
namespace Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture;
class FooControllerNullableParameter
{
public function requiredParamAction(\DateTime $param)
{
}
public function defaultParamAction(\DateTime $param = null)
{
}
public function nullableParamAction(?\DateTime $param)
{
}
}

View file

@ -0,0 +1,18 @@
<?php
namespace Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
/**
* @ParamConverter("test")
*/
class FooControllerParamConverterAtClassAndMethod
{
/**
* @ParamConverter("test2")
*/
public function barAction($test, $test2)
{
}
}

View file

@ -0,0 +1,260 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
use Sensio\Bundle\FrameworkExtraBundle\EventListener\HttpCacheListener;
class HttpCacheListenerTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
$this->listener = new HttpCacheListener();
$this->response = new Response();
$this->cache = new Cache(array());
$this->request = $this->createRequest($this->cache);
$this->event = $this->createEventMock($this->request, $this->response);
}
public function testWontReassignResponseWhenResponseIsUnsuccessful()
{
$this->event
->expects($this->never())
->method('setResponse')
;
$this->response->setStatusCode(500);
$this->assertInternalType('null', $this->listener->onKernelResponse($this->event));
}
public function testWontReassignResponseWhenNoConfigurationIsPresent()
{
$this->event
->expects($this->never())
->method('setResponse')
;
$this->request->attributes->remove('_cache');
$this->assertInternalType('null', $this->listener->onKernelResponse($this->event));
}
public function testResponseIsPublicIfConfigurationIsPublicTrue()
{
$request = $this->createRequest(new Cache(array(
'public' => true,
)));
$this->listener->onKernelResponse($this->createEventMock($request, $this->response));
$this->assertTrue($this->response->headers->hasCacheControlDirective('public'));
$this->assertFalse($this->response->headers->hasCacheControlDirective('private'));
}
public function testResponseIsPrivateIfConfigurationIsPublicFalse()
{
$request = $this->createRequest(new Cache(array(
'public' => false,
)));
$this->listener->onKernelResponse($this->createEventMock($request, $this->response));
$this->assertFalse($this->response->headers->hasCacheControlDirective('public'));
$this->assertTrue($this->response->headers->hasCacheControlDirective('private'));
}
public function testResponseVary()
{
$vary = array('foobar');
$request = $this->createRequest(new Cache(array('vary' => $vary)));
$this->listener->onKernelResponse($this->createEventMock($request, $this->response));
$this->assertTrue($this->response->hasVary());
$result = $this->response->getVary();
$this->assertEquals($vary, $result);
}
public function testResponseVaryWhenVaryNotSet()
{
$request = $this->createRequest(new Cache(array()));
$vary = array('foobar');
$this->response->setVary($vary);
$this->listener->onKernelResponse($this->createEventMock($request, $this->response));
$this->assertTrue($this->response->hasVary());
$result = $this->response->getVary();
$this->assertFalse(empty($result), 'Existing vary headers should not be removed');
$this->assertEquals($vary, $result, 'Vary header should not be changed');
}
public function testResponseIsPrivateIfConfigurationIsPublicNotSet()
{
$request = $this->createRequest(new Cache(array()));
$this->listener->onKernelResponse($this->createEventMock($request, $this->response));
$this->assertFalse($this->response->headers->hasCacheControlDirective('public'));
}
public function testConfigurationAttributesAreSetOnResponse()
{
$this->assertInternalType('null', $this->response->getMaxAge());
$this->assertInternalType('null', $this->response->getExpires());
$this->assertFalse($this->response->headers->hasCacheControlDirective('s-maxage'));
$this->request->attributes->set('_cache', new Cache(array(
'expires' => 'tomorrow',
'smaxage' => '15',
'maxage' => '15',
)));
$this->listener->onKernelResponse($this->event);
$this->assertEquals('15', $this->response->getMaxAge());
$this->assertEquals('15', $this->response->headers->getCacheControlDirective('s-maxage'));
$this->assertInstanceOf('DateTime', $this->response->getExpires());
}
public function testCacheMaxAgeSupportsStrtotimeFormat()
{
$this->request->attributes->set('_cache', new Cache(array(
'smaxage' => '1 day',
'maxage' => '1 day',
)));
$this->listener->onKernelResponse($this->event);
$this->assertEquals(60 * 60 * 24, $this->response->headers->getCacheControlDirective('s-maxage'));
$this->assertEquals(60 * 60 * 24, $this->response->getMaxAge());
}
public function testLastModifiedNotModifiedResponse()
{
$request = $this->createRequest(new Cache(array('lastModified' => 'test.getDate()')));
$request->attributes->set('test', new TestEntity());
$request->headers->add(array('If-Modified-Since' => 'Fri, 23 Aug 2013 00:00:00 GMT'));
$listener = new HttpCacheListener();
$controllerEvent = new FilterControllerEvent($this->getKernel(), function () { return new Response(500); }, $request, null);
$listener->onKernelController($controllerEvent);
$response = call_user_func($controllerEvent->getController());
$this->assertEquals(304, $response->getStatusCode());
}
public function testLastModifiedHeader()
{
$request = $this->createRequest(new Cache(array('lastModified' => 'test.getDate()')));
$request->attributes->set('test', new TestEntity());
$response = new Response();
$listener = new HttpCacheListener();
$controllerEvent = new FilterControllerEvent($this->getKernel(), function () { return new Response(); }, $request, null);
$listener->onKernelController($controllerEvent);
$responseEvent = new FilterResponseEvent($this->getKernel(), $request, null, call_user_func($controllerEvent->getController()));
$listener->onKernelResponse($responseEvent);
$response = $responseEvent->getResponse();
$this->assertEquals(200, $response->getStatusCode());
$this->assertTrue($response->headers->has('Last-Modified'));
$this->assertEquals('Fri, 23 Aug 2013 00:00:00 GMT', $response->headers->get('Last-Modified'));
}
public function testETagNotModifiedResponse()
{
$request = $this->createRequest(new Cache(array('etag' => 'test.getId()')));
$request->attributes->set('test', $entity = new TestEntity());
$request->headers->add(array('If-None-Match' => sprintf('"%s"', hash('sha256', $entity->getId()))));
$listener = new HttpCacheListener();
$controllerEvent = new FilterControllerEvent($this->getKernel(), function () { return new Response(500); }, $request, null);
$listener->onKernelController($controllerEvent);
$response = call_user_func($controllerEvent->getController());
$this->assertEquals(304, $response->getStatusCode());
}
public function testETagHeader()
{
$request = $this->createRequest(new Cache(array('ETag' => 'test.getId()')));
$request->attributes->set('test', $entity = new TestEntity());
$response = new Response();
$listener = new HttpCacheListener();
$controllerEvent = new FilterControllerEvent($this->getKernel(), function () { return new Response(); }, $request, null);
$listener->onKernelController($controllerEvent);
$responseEvent = new FilterResponseEvent($this->getKernel(), $request, null, call_user_func($controllerEvent->getController()));
$listener->onKernelResponse($responseEvent);
$response = $responseEvent->getResponse();
$this->assertEquals(200, $response->getStatusCode());
$this->assertTrue($response->headers->has('ETag'));
$this->assertContains(hash('sha256', $entity->getId()), $response->headers->get('ETag'));
}
private function createRequest(Cache $cache = null)
{
return new Request(array(), array(), array(
'_cache' => $cache,
));
}
private function createEventMock(Request $request, Response $response)
{
$event = $this
->getMockBuilder('Symfony\Component\HttpKernel\Event\FilterResponseEvent')
->disableOriginalConstructor()
->getMock();
$event
->expects($this->any())
->method('getRequest')
->will($this->returnValue($request))
;
$event
->expects($this->any())
->method('getResponse')
->will($this->returnValue($response))
;
return $event;
}
private function getKernel()
{
return $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock();
}
}
class TestEntity
{
public function getDate()
{
return new \DateTime('Fri, 23 Aug 2013 00:00:00 GMT');
}
public function getId()
{
return '12345';
}
}

View file

@ -0,0 +1,153 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\EventListener\ParamConverterListener;
use Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener\Fixture\FooControllerNullableParameter;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
class ParamConverterListenerTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider getControllerWithNoArgsFixtures
*/
public function testRequestIsSkipped($controllerCallable)
{
$kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock();
$request = new Request();
$listener = new ParamConverterListener($this->getParamConverterManager($request, array()));
$event = new FilterControllerEvent($kernel, $controllerCallable, $request, null);
$listener->onKernelController($event);
}
public function getControllerWithNoArgsFixtures()
{
return array(
array(array(new TestController(), 'noArgAction')),
array(new InvokableNoArgController()),
);
}
/**
* @dataProvider getControllerWithArgsFixtures
*/
public function testAutoConvert($controllerCallable)
{
$kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock();
$request = new Request(array(), array(), array('date' => '2014-03-14 09:00:00'));
$converter = new ParamConverter(array('name' => 'date', 'class' => 'DateTime'));
$listener = new ParamConverterListener($this->getParamConverterManager($request, array('date' => $converter)));
$event = new FilterControllerEvent($kernel, $controllerCallable, $request, null);
$listener->onKernelController($event);
}
/**
* @dataProvider settingOptionalParamProvider
* @requires PHP 7.1
*/
public function testSettingOptionalParam($function, $isOptional)
{
$kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock();
$request = new Request();
$converter = new ParamConverter(array('name' => 'param', 'class' => 'DateTime'));
$converter->setIsOptional($isOptional);
$listener = new ParamConverterListener($this->getParamConverterManager($request, array('param' => $converter)), true);
$event = new FilterControllerEvent(
$kernel,
array(
new FooControllerNullableParameter(),
$function,
),
$request,
null
);
$listener->onKernelController($event);
}
public function settingOptionalParamProvider()
{
return array(
array('requiredParamAction', false),
array('defaultParamAction', true),
array('nullableParamAction', true),
);
}
/**
* @dataProvider getControllerWithArgsFixtures
*/
public function testNoAutoConvert($controllerCallable)
{
$kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock();
$request = new Request(array(), array(), array('date' => '2014-03-14 09:00:00'));
$listener = new ParamConverterListener($this->getParamConverterManager($request, array()), false);
$event = new FilterControllerEvent($kernel, $controllerCallable, $request, null);
$listener->onKernelController($event);
}
public function getControllerWithArgsFixtures()
{
return array(
array(array(new TestController(), 'dateAction')),
array(new InvokableController()),
);
}
protected function getParamConverterManager(Request $request, $configurations)
{
$manager = $this->getMockBuilder('Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterManager')->getMock();
$manager
->expects($this->once())
->method('apply')
->with($this->equalTo($request), $this->equalTo($configurations))
;
return $manager;
}
}
class TestController
{
public function noArgAction(Request $request)
{
}
public function dateAction(\DateTime $date)
{
}
}
class InvokableNoArgController
{
public function __invoke(Request $request)
{
}
}
class InvokableController
{
public function __invoke(\DateTime $date)
{
}
}

View file

@ -0,0 +1,61 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener;
use Sensio\Bundle\FrameworkExtraBundle\EventListener\PsrResponseListener;
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
use Symfony\Bridge\PsrHttpMessage\Tests\Fixtures\Response;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
* @requires PHP 5.4
*/
class PsrResponseListenerTest extends \PHPUnit_Framework_TestCase
{
public function testConvertsControllerResult()
{
$listener = new PsrResponseListener(new HttpFoundationFactory());
$event = $this->createEventMock(new Response());
$event->expects($this->once())->method('setResponse')->with($this->isInstanceOf('Symfony\Component\HttpFoundation\Response'));
$listener->onKernelView($event);
}
public function testDoesNotConvertControllerResult()
{
$listener = new PsrResponseListener(new HttpFoundationFactory());
$event = $this->createEventMock(array());
$event->expects($this->never())->method('setResponse');
$listener->onKernelView($event);
$event = $this->createEventMock(null);
$event->expects($this->never())->method('setResponse');
$listener->onKernelView($event);
}
private function createEventMock($controllerResult)
{
$event = $this
->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent')
->disableOriginalConstructor()
->getMock()
;
$event
->expects($this->any())
->method('getControllerResult')
->will($this->returnValue($controllerResult))
;
return $event;
}
}

View file

@ -0,0 +1,96 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Tests\EventListener;
use Sensio\Bundle\FrameworkExtraBundle\Security\ExpressionLanguage;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Sensio\Bundle\FrameworkExtraBundle\EventListener\SecurityListener;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
class SecurityListenerTest extends \PHPUnit_Framework_TestCase
{
/**
* @expectedException \Symfony\Component\Security\Core\Exception\AccessDeniedException
* @group legacy
*/
public function testLegacyAccessDenied()
{
if (!interface_exists('Symfony\Component\Security\Core\SecurityContextInterface')) {
$this->markTestSkipped();
}
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
$token = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock();
$token->expects($this->once())->method('getRoles')->will($this->returnValue(array()));
$securityContext = $this->getMockBuilder('Symfony\Component\Security\Core\SecurityContextInterface')->getMock();
$securityContext->expects($this->once())->method('isGranted')->will($this->throwException(new AccessDeniedException()));
$securityContext->expects($this->exactly(2))->method('getToken')->will($this->returnValue($token));
$trustResolver = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface')->getMock();
$language = new ExpressionLanguage();
$listener = new SecurityListener($securityContext, $language, $trustResolver);
$request = $this->createRequest(new Security(array('expression' => 'has_role("ROLE_ADMIN") or is_granted("FOO")')));
$event = new FilterControllerEvent($this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(), function () { return new Response(); }, $request, null);
$listener->onKernelController($event);
}
/**
* @expectedException \Symfony\Component\Security\Core\Exception\AccessDeniedException
*/
public function testAccessDenied()
{
if (!interface_exists('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface')) {
$this->markTestSkipped();
}
$token = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock();
$token->expects($this->once())->method('getRoles')->will($this->returnValue(array()));
$tokenStorage = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface')->getMock();
$tokenStorage->expects($this->exactly(2))->method('getToken')->will($this->returnValue($token));
$authChecker = $this->getMockBuilder('Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface')->getMock();
$authChecker->expects($this->once())->method('isGranted')->will($this->throwException(new AccessDeniedException()));
$trustResolver = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface')->getMock();
$language = new ExpressionLanguage();
$listener = new SecurityListener(null, $language, $trustResolver, null, $tokenStorage, $authChecker);
$request = $this->createRequest(new Security(array('expression' => 'has_role("ROLE_ADMIN") or is_granted("FOO")')));
$event = new FilterControllerEvent($this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock(), function () { return new Response(); }, $request, null);
$listener->onKernelController($event);
}
private function createRequest(Security $security = null)
{
return new Request(array(), array(), array(
'_security' => $security,
));
}
private function getKernel()
{
return $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock();
}
}

View file

@ -0,0 +1,18 @@
<?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 Tests\Fixtures\ActionArgumentsBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class ActionArgumentsBundle extends Bundle
{
}

View file

@ -0,0 +1,47 @@
<?php
namespace Tests\Fixtures\ActionArgumentsBundle\Controller;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ServerRequestInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Response;
/**
* @Route("/nullable-arguments")
*/
class NullableArgumentsController
{
/**
* @Route("/invoke/")
*/
public function __invoke(RequestInterface $request, MessageInterface $message, ServerRequestInterface $serverRequest)
{
return new Response('<html><body>ok</body></html>');
}
/**
* @Route("/with-default")
*/
public function withDefaultAction(string $d = null)
{
return new Response(null === $d ? 'yes' : 'no');
}
/**
* @Route("/without-default")
*/
public function withoutDefaultAction(string $d)
{
return new Response(null === $d ? 'yes' : 'no');
}
/**
* @Route("/nullable")
*/
public function nullableAction(?string $d)
{
return new Response(null === $d ? 'yes' : 'no');
}
}

View file

@ -0,0 +1,32 @@
<?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 Tests\Fixtures\FooBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
/**
* @Route(service="test.invokable_class_level.predefined")
* @Template("FooBundle:Invokable:predefined.html.twig")
*/
class InvokableClassLevelController
{
/**
* @Route("/invokable/class-level/service/")
*/
public function __invoke()
{
return array(
'foo' => 'bar',
);
}
}

View file

@ -0,0 +1,61 @@
<?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 Tests\Fixtures\FooBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class InvokableContainerController extends Controller
{
/**
* @Route("/invokable/variable/container/{variable}/")
* @Template()
*/
public function variableAction($variable)
{
}
/**
* @Route("/invokable/another-variable/container/{variable}/")
* @Template("FooBundle:InvokableContainer:variable.html.twig")
*/
public function anotherVariableAction($variable)
{
return array(
'variable' => $variable,
);
}
/**
* @Route("/invokable/variable/container/{variable}/{another_variable}/")
* @Template("FooBundle:InvokableContainer:another_variable.html.twig")
*/
public function doubleVariableAction($variable, $another_variable)
{
return array(
'variable' => $variable,
'another_variable' => $another_variable,
);
}
/**
* @Route("/invokable/predefined/container/")
* @Template("FooBundle:Invokable:predefined.html.twig")
*/
public function __invoke()
{
return array(
'foo' => 'bar',
);
}
}

View file

@ -0,0 +1,32 @@
<?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 Tests\Fixtures\FooBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
/**
* @Route(service="test.invokable.predefined")
*/
class InvokableController
{
/**
* @Route("/invokable/predefined/service/")
* @Template("FooBundle:Invokable:predefined.html.twig")
*/
public function __invoke()
{
return array(
'foo' => 'bar',
);
}
}

View file

@ -0,0 +1,54 @@
<?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 Tests\Fixtures\FooBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
/**
* @Template("FooBundle:Invokable:predefined.html.twig")
*/
class MultipleActionsClassLevelTemplateController extends Controller
{
/**
* @Route("/multi/one-template/1/")
*/
public function firstAction()
{
return array(
'foo' => 'bar',
);
}
/**
* @Route("/multi/one-template/2/")
* @Route("/multi/one-template/3/")
*/
public function secondAction()
{
return array(
'foo' => 'bar',
);
}
/**
* @Route("/multi/one-template/4/")
* @Template("FooBundle::overwritten.html.twig")
*/
public function overwriteAction()
{
return array(
'foo' => 'foo bar baz',
);
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace Tests\Fixtures\FooBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Response;
/**
* @Route("/optional-arguments")
*/
class OptionalArgumentsController
{
/**
* @Route("/with-default-followed-by-mandatory", defaults={"e" = null})
* @ParamConverter(name="d", class="Tests\Fixtures\FooBundle\Entity\Foo")
*/
public function withDefaultFollowedByMandatory($d = null, $e)
{
return new Response(null === $d ? 'yes' : 'no');
}
}

View file

@ -0,0 +1,66 @@
<?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 Tests\Fixtures\FooBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Response;
/**
* @Route(service="test.simple.multiple")
*/
class SimpleController
{
/**
* @Route("/simple/multiple/", defaults={"a": "a", "b": "b"})
* @Template()
*/
public function someAction($a, $b, $c = 'c')
{
}
/**
* @Route("/simple/multiple/{a}/{b}/")
* @Template("FooBundle:Simple:some.html.twig")
*/
public function someMoreAction($a, $b, $c = 'c')
{
}
/**
* @Route("/simple/multiple-with-vars/", defaults={"a": "a", "b": "b"})
* @Template(vars={"a", "b"})
*/
public function anotherAction($a, $b, $c = 'c')
{
}
/**
* @Route("/no-listener/")
*/
public function noListenerAction()
{
return new Response('<html><body>I did not get rendered via twig</body></html>');
}
/**
* @Route("/streamed/")
* @Template(isStreamable=true)
*/
public function streamedAction()
{
return array(
'foo' => 'foo',
'bar' => 'bar',
);
}
}

View file

@ -0,0 +1,18 @@
<?php
namespace Tests\Fixtures\FooBundle\Entity;
use Doctrine\ORM\Mapping;
/**
* @Mapping\Entity
*/
class Foo
{
/**
* @Mapping\Column(type="integer")
* @Mapping\Id
* @Mapping\GeneratedValue(strategy="AUTO")
*/
private $id;
}

View file

@ -0,0 +1,18 @@
<?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 Tests\Fixtures\FooBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class FooBundle extends Bundle
{
}

View file

@ -0,0 +1 @@
<html><body>{{ foo }}</body></html>

View file

@ -0,0 +1 @@
<html><body>{{ variable }},{{ another_variable }}</body></html>

View file

@ -0,0 +1 @@
<html><body>{{ variable }}</body></html>

View file

@ -0,0 +1 @@
<html><body>{{ a }}, {{ b }}</body></html>

View file

@ -0,0 +1 @@
<html><body>{{ a }}, {{ b }}, {{ c }}</body></html>

View file

@ -0,0 +1 @@
<html><body>{{ foo }}, {{ bar }}</body></html>

View file

@ -0,0 +1 @@
<html><body>{{ foo }}</body></html>

View file

@ -0,0 +1,49 @@
<?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 Tests\Fixtures;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\HttpKernel\Kernel;
/**
* Used for functional tests.
*/
class TestKernel extends Kernel
{
public function registerBundles()
{
return array(
new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
new \Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
new \Symfony\Bundle\TwigBundle\TwigBundle(),
new \Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
new \Tests\Fixtures\FooBundle\FooBundle(),
new \Tests\Fixtures\ActionArgumentsBundle\ActionArgumentsBundle(),
);
}
public function registerContainerConfiguration(LoaderInterface $loader)
{
$loader->load(__DIR__.'/config/config.yml');
if (PHP_VERSION_ID >= 70100) {
$loader->load(__DIR__.'/config/nullable_type/config.yml');
}
}
public function getCacheDir()
{
return $this->rootDir.'/cache/'.$this->environment;
}
}
class_alias('Tests\Fixtures\TestKernel', 'TestKernel');

View file

@ -0,0 +1,24 @@
framework:
test: true
secret: test
templating:
engine: [twig, php]
router:
resource: "%kernel.root_dir%/config/routing.yml"
doctrine:
dbal:
driver: pdo_sqlite
path: "%kernel.root_dir%/data/db.sqlite"
orm:
auto_mapping: true
services:
test.invokable.predefined:
class: Tests\Fixtures\FooBundle\Controller\InvokableController
test.invokable_class_level.predefined:
class: Tests\Fixtures\FooBundle\Controller\InvokableClassLevelController
test.simple.multiple:
class: Tests\Fixtures\FooBundle\Controller\SimpleController

View file

@ -0,0 +1,3 @@
framework:
router:
resource: '%kernel.root_dir%/config/nullable_type/routing.yml'

View file

@ -0,0 +1,7 @@
foo_bundle:
resource: "@FooBundle/Controller"
type: annotation
action_arguments_bundle:
resource: "@ActionArgumentsBundle/Controller"
type: annotation

View file

@ -0,0 +1,3 @@
foo_bundle:
resource: "@FooBundle/Controller"
type: annotation

View file

@ -0,0 +1,33 @@
<?php
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
/**
* @requires PHP 7.1
*/
class NullableAnnotationTest extends WebTestCase
{
public function testMissingRequiredArgumentWillResultWithError()
{
$client = self::createClient();
$client->request('GET', '/nullable-arguments/without-default');
$this->assertSame(500, $client->getResponse()->getStatusCode());
}
public function testArgumentWithDefaultIsOptional()
{
$client = self::createClient();
$crawler = $client->request('GET', '/nullable-arguments/with-default');
$this->assertSame('yes', $crawler->text());
}
public function testNullableArgumentIsOptional()
{
$client = self::createClient();
$crawler = $client->request('GET', '/nullable-arguments/nullable');
$this->assertSame('yes', $crawler->text());
}
}

View file

@ -0,0 +1,14 @@
<?php
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class OptionalArgumentsTest extends WebTestCase
{
public function testArgumentWithDefaultFollowedByMandatoryIsOptional()
{
$client = self::createClient();
$crawler = $client->request('GET', '/optional-arguments/with-default-followed-by-mandatory');
$this->assertSame('yes', $crawler->text());
}
}

View file

@ -0,0 +1,61 @@
<?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.
*/
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\DomCrawler\Crawler;
class TemplateAnnotationTest extends WebTestCase
{
/**
* @dataProvider urlProvider
*/
public function testController($url, $checkHtml)
{
$client = self::createClient();
$crawler = $client->request('GET', $url);
$this->assertEquals($checkHtml, $crawler->filterXPath('//body')->html());
}
public static function urlProvider()
{
return array(
array('/multi/one-template/1/', 'bar'),
array('/multi/one-template/2/', 'bar'),
array('/multi/one-template/3/', 'bar'),
array('/multi/one-template/4/', 'foo bar baz'),
array('/invokable/predefined/service/', 'bar'),
array('/invokable/class-level/service/', 'bar'),
array('/simple/multiple/', 'a, b, c'),
array('/simple/multiple/henk/bar/', 'henk, bar, c'),
array('/simple/multiple-with-vars/', 'a, b'),
array('/invokable/predefined/container/', 'bar'),
array('/invokable/variable/container/the-var/', 'the-var'),
array('/invokable/another-variable/container/another-var/', 'another-var'),
array('/invokable/variable/container/the-var/another-var/', 'the-var,another-var'),
array('/no-listener/', 'I did not get rendered via twig'),
);
}
public function testStreamedControllerResponse()
{
$uri = '/streamed/';
ob_start();
$client = self::createClient();
$client->request('GET', $uri);
$crawler = new Crawler(null, $uri);
$crawler->addContent(ob_get_clean());
$this->assertEquals('foo, bar', $crawler->filterXPath('//body')->html());
}
}

View file

@ -0,0 +1,101 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
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 testApplyInvalidDate404Exception()
{
$request = new Request(array(), array(), array('start' => 'Invalid DateTime Format'));
$config = $this->createConfiguration('DateTime', 'start');
$this->setExpectedException('Symfony\Component\HttpKernel\Exception\NotFoundHttpException', 'Invalid date given for parameter "start".');
$this->converter->apply($request, $config);
}
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 for parameter "start".');
$this->converter->apply($request, $config);
}
public function testApplyOptionalWithEmptyAttribute()
{
$request = new Request(array(), array(), array('start' => null));
$config = $this->createConfiguration('DateTime', 'start');
$config->expects($this->once())
->method('isOptional')
->will($this->returnValue(true));
$this->assertFalse($this->converter->apply($request, $config));
$this->assertNull($request->attributes->get('start'));
}
public function createConfiguration($class = null, $name = null)
{
$config = $this
->getMockBuilder('Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter')
->setMethods(array('getClass', 'getAliasName', 'getOptions', 'getName', 'allowArray', 'isOptional'))
->disableOriginalConstructor()
->getMock();
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

@ -0,0 +1,468 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Tests\Request\ParamConverter;
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\DoctrineParamConverter;
use Doctrine\Common\Persistence\ManagerRegistry;
class DoctrineParamConverterTest extends \PHPUnit_Framework_TestCase
{
/**
* @var ManagerRegistry
*/
private $registry;
/**
* @var DoctrineParamConverter
*/
private $converter;
public function setUp()
{
$this->registry = $this->getMockBuilder('Doctrine\Common\Persistence\ManagerRegistry')->getMock();
$this->converter = new DoctrineParamConverter($this->registry);
}
public function createConfiguration($class = null, array $options = null, $name = 'arg', $isOptional = false)
{
$methods = array('getClass', 'getAliasName', 'getOptions', 'getName', 'allowArray');
if (null !== $isOptional) {
$methods[] = 'isOptional';
}
$config = $this
->getMockBuilder('Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter')
->setMethods($methods)
->disableOriginalConstructor()
->getMock();
if ($options !== null) {
$config->expects($this->once())
->method('getOptions')
->will($this->returnValue($options));
}
if ($class !== null) {
$config->expects($this->any())
->method('getClass')
->will($this->returnValue($class));
}
$config->expects($this->any())
->method('getName')
->will($this->returnValue($name));
if (null !== $isOptional) {
$config->expects($this->any())
->method('isOptional')
->will($this->returnValue($isOptional));
}
return $config;
}
public function testApplyWithNoIdAndData()
{
$request = new Request();
$config = $this->createConfiguration(null, array());
$objectManager = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
$this->setExpectedException('LogicException');
$this->converter->apply($request, $config);
}
public function testApplyWithNoIdAndDataOptional()
{
$request = new Request();
$config = $this->createConfiguration(null, array(), 'arg', true);
$objectManager = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
$ret = $this->converter->apply($request, $config);
$this->assertTrue($ret);
$this->assertNull($request->attributes->get('arg'));
}
public function testApplyWithStripNulls()
{
$request = new Request();
$request->attributes->set('arg', null);
$config = $this->createConfiguration('stdClass', array('mapping' => array('arg' => 'arg'), 'strip_null' => true), 'arg', true);
$classMetadata = $this->getMockBuilder('Doctrine\Common\Persistence\Mapping\ClassMetadata')->getMock();
$manager = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
$manager->expects($this->once())
->method('getClassMetadata')
->with('stdClass')
->will($this->returnValue($classMetadata));
$manager->expects($this->never())
->method('getRepository');
$this->registry->expects($this->once())
->method('getManagerForClass')
->with('stdClass')
->will($this->returnValue($manager));
$classMetadata->expects($this->once())
->method('hasField')
->with($this->equalTo('arg'))
->will($this->returnValue(true));
$this->converter->apply($request, $config);
$this->assertNull($request->attributes->get('arg'));
}
/**
* @dataProvider idsProvider
*/
public function testApplyWithId($id)
{
$request = new Request();
$request->attributes->set('id', $id);
$config = $this->createConfiguration('stdClass', array('id' => 'id'), 'arg');
$manager = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
$objectRepository = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectRepository')->getMock();
$this->registry->expects($this->once())
->method('getManagerForClass')
->with('stdClass')
->will($this->returnValue($manager));
$manager->expects($this->once())
->method('getRepository')
->with('stdClass')
->will($this->returnValue($objectRepository));
$objectRepository->expects($this->once())
->method('find')
->with($this->equalTo($id))
->will($this->returnValue($object = new \stdClass()));
$ret = $this->converter->apply($request, $config);
$this->assertTrue($ret);
$this->assertSame($object, $request->attributes->get('arg'));
}
public function testUsedProperIdentifier()
{
$request = new Request();
$request->attributes->set('id', 1);
$request->attributes->set('entity_id', null);
$request->attributes->set('arg', null);
$config = $this->createConfiguration('stdClass', array('id' => 'entity_id'), 'arg', null);
$ret = $this->converter->apply($request, $config);
$this->assertTrue($ret);
$this->assertNull($request->attributes->get('arg'));
}
public function idsProvider()
{
return array(
array(1),
array(0),
array('foo'),
);
}
public function testApplyGuessOptional()
{
$request = new Request();
$request->attributes->set('arg', null);
$config = $this->createConfiguration('stdClass', array(), 'arg', null);
$classMetadata = $this->getMockBuilder('Doctrine\Common\Persistence\Mapping\ClassMetadata')->getMock();
$manager = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
$manager->expects($this->once())
->method('getClassMetadata')
->with('stdClass')
->will($this->returnValue($classMetadata));
$objectRepository = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectRepository')->getMock();
$this->registry->expects($this->once())
->method('getManagerForClass')
->with('stdClass')
->will($this->returnValue($manager));
$manager->expects($this->never())->method('getRepository');
$objectRepository->expects($this->never())->method('find');
$objectRepository->expects($this->never())->method('findOneBy');
$ret = $this->converter->apply($request, $config);
$this->assertTrue($ret);
$this->assertNull($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'
);
$manager = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
$metadata = $this->getMockBuilder('Doctrine\Common\Persistence\Mapping\ClassMetadata')->getMock();
$repository = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectRepository')->getMock();
$this->registry->expects($this->once())
->method('getManagerForClass')
->with('stdClass')
->will($this->returnValue($manager));
$manager->expects($this->once())
->method('getClassMetadata')
->with('stdClass')
->will($this->returnValue($metadata));
$manager->expects($this->once())
->method('getRepository')
->with('stdClass')
->will($this->returnValue($repository));
$metadata->expects($this->once())
->method('hasField')
->with($this->equalTo('Foo'))
->will($this->returnValue(true));
$repository->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 testApplyWithRepositoryMethod()
{
$request = new Request();
$request->attributes->set('id', 1);
$config = $this->createConfiguration(
'stdClass',
array('repository_method' => 'getClassName'),
'arg'
);
$objectRepository = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectRepository')->getMock();
$manager = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
$manager->expects($this->once())
->method('getRepository')
->with('stdClass')
->will($this->returnValue($objectRepository));
$this->registry->expects($this->once())
->method('getManagerForClass')
->will($this->returnValue($manager));
$objectRepository->expects($this->once())
->method('getClassName')
->will($this->returnValue($className = 'ObjectRepository'));
$ret = $this->converter->apply($request, $config);
$this->assertTrue($ret);
$this->assertSame($className, $request->attributes->get('arg'));
}
public function testApplyWithRepositoryMethodAndMapping()
{
$request = new Request();
$request->attributes->set('id', 1);
$config = $this->createConfiguration(
'stdClass',
array('repository_method' => 'getClassName', 'mapping' => array('foo' => 'Foo')),
'arg'
);
$objectManager = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
$objectRepository = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectRepository')->getMock();
$metadata = $this->getMockBuilder('Doctrine\Common\Persistence\Mapping\ClassMetadata')->getMock();
$objectManager->expects($this->once())
->method('getRepository')
->with('stdClass')
->will($this->returnValue($objectRepository));
$this->registry->expects($this->once())
->method('getManagerForClass')
->will($this->returnValue($objectManager));
$metadata->expects($this->once())
->method('hasField')
->with($this->equalTo('Foo'))
->will($this->returnValue(true));
$objectManager->expects($this->once())
->method('getClassMetadata')
->will($this->returnValue($metadata));
$objectManager->expects($this->once())
->method('getRepository')
->with('stdClass')
->will($this->returnValue($objectRepository));
$objectRepository->expects($this->once())
->method('getClassName')
->will($this->returnValue($className = 'ObjectRepository'));
$ret = $this->converter->apply($request, $config);
$this->assertTrue($ret);
$this->assertSame($className, $request->attributes->get('arg'));
}
public function testApplyWithRepositoryMethodAndMapMethodSignature()
{
$request = new Request();
$request->attributes->set('first_name', 'Fabien');
$request->attributes->set('last_name', 'Potencier');
$config = $this->createConfiguration(
'stdClass',
array(
'repository_method' => 'findByFullName',
'mapping' => array('first_name' => 'firstName', 'last_name' => 'lastName'),
'map_method_signature' => true,
),
'arg'
);
$objectManager = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
$objectRepository = new TestUserRepository();
$metadata = $this->getMockBuilder('Doctrine\Common\Persistence\Mapping\ClassMetadata')->getMock();
$objectManager->expects($this->once())
->method('getRepository')
->with('stdClass')
->will($this->returnValue($objectRepository));
$this->registry->expects($this->once())
->method('getManagerForClass')
->will($this->returnValue($objectManager));
$objectManager->expects($this->once())
->method('getClassMetadata')
->will($this->returnValue($metadata));
$ret = $this->converter->apply($request, $config);
$this->assertTrue($ret);
$this->assertSame('Fabien Potencier', $request->attributes->get('arg'));
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Repository method "Sensio\Bundle\FrameworkExtraBundle\Tests\Request\ParamConverter\TestUserRepository::findByFullName" requires that you provide a value for the "$lastName" argument.
*/
public function testApplyWithRepositoryMethodAndMapMethodSignatureException()
{
$request = new Request();
$request->attributes->set('first_name', 'Fabien');
$request->attributes->set('last_name', 'Potencier');
$config = $this->createConfiguration(
'stdClass',
array(
'repository_method' => 'findByFullName',
'mapping' => array('first_name' => 'firstName', 'last_name' => 'lastNameXxx'),
'map_method_signature' => true,
),
'arg'
);
$objectManager = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
$objectRepository = new TestUserRepository();
$metadata = $this->getMockBuilder('Doctrine\Common\Persistence\Mapping\ClassMetadata')->getMock();
$objectManager->expects($this->once())
->method('getRepository')
->with('stdClass')
->will($this->returnValue($objectRepository));
$this->registry->expects($this->once())
->method('getManagerForClass')
->will($this->returnValue($objectManager));
$objectManager->expects($this->once())
->method('getClassMetadata')
->will($this->returnValue($metadata));
$this->converter->apply($request, $config);
}
public function testSupports()
{
$config = $this->createConfiguration('stdClass', array());
$metadataFactory = $this->getMockBuilder('Doctrine\Common\Persistence\Mapping\ClassMetadataFactory')->getMock();
$metadataFactory->expects($this->once())
->method('isTransient')
->with($this->equalTo('stdClass'))
->will($this->returnValue(false));
$objectManager = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
$objectManager->expects($this->once())
->method('getMetadataFactory')
->will($this->returnValue($metadataFactory));
$this->registry->expects($this->once())
->method('getManagers')
->will($this->returnValue(array($objectManager)));
$this->registry->expects($this->once())
->method('getManagerForClass')
->with('stdClass')
->will($this->returnValue($objectManager));
$ret = $this->converter->supports($config);
$this->assertTrue($ret, 'Should be supported');
}
public function testSupportsWithConfiguredEntityManager()
{
$config = $this->createConfiguration('stdClass', array('entity_manager' => 'foo'));
$metadataFactory = $this->getMockBuilder('Doctrine\Common\Persistence\Mapping\ClassMetadataFactory')->getMock();
$metadataFactory->expects($this->once())
->method('isTransient')
->with($this->equalTo('stdClass'))
->will($this->returnValue(false));
$objectManager = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
$objectManager->expects($this->once())
->method('getMetadataFactory')
->will($this->returnValue($metadataFactory));
$this->registry->expects($this->once())
->method('getManagers')
->will($this->returnValue(array($objectManager)));
$this->registry->expects($this->once())
->method('getManager')
->with('foo')
->will($this->returnValue($objectManager));
$ret = $this->converter->supports($config);
$this->assertTrue($ret, 'Should be supported');
}
}

View file

@ -0,0 +1,175 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Tests\Request\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterManager;
use Sensio\Bundle\FrameworkExtraBundle\Configuration;
use Symfony\Component\HttpFoundation\Request;
class ParamConverterManagerTest extends \PHPUnit_Framework_TestCase
{
public function testPriorities()
{
$manager = new ParamConverterManager();
$this->assertEquals(array(), $manager->all());
$high = $this->createParamConverterMock();
$low = $this->createParamConverterMock();
$manager->add($low);
$manager->add($high, 10);
$this->assertEquals(array($high, $low), $manager->all());
}
public function testApply()
{
$supported = $this->createParamConverterMock();
$supported
->expects($this->once())
->method('supports')
->will($this->returnValue(true))
;
$supported
->expects($this->once())
->method('apply')
->will($this->returnValue(false))
;
$invalid = $this->createParamConverterMock();
$invalid
->expects($this->once())
->method('supports')
->will($this->returnValue(false))
;
$invalid
->expects($this->never())
->method('apply')
;
$configurations = array(
new Configuration\ParamConverter(array(
'name' => 'var',
)),
);
$manager = new ParamConverterManager();
$manager->add($supported);
$manager->add($invalid);
$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')
;
$request = new Request();
$request->attributes->set('param', '1234');
$configuration = new Configuration\ParamConverter(array(
'name' => 'param',
'class' => 'stdClass',
'converter' => 'test',
));
$manager = new ParamConverterManager();
$manager->add($converter, 0, 'test');
$manager->apply($request, array($configuration));
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage Converter 'test' does not support conversion of parameter 'param'.
*/
public function testApplyNamedConverterNotSupportsParameter()
{
$converter = $this->createParamConverterMock();
$converter
->expects($this->any())
->method('supports')
->will($this->returnValue(false))
;
$request = new Request();
$request->attributes->set('param', '1234');
$configuration = new Configuration\ParamConverter(array(
'name' => 'param',
'class' => 'stdClass',
'converter' => 'test',
));
$manager = new ParamConverterManager();
$manager->add($converter, 0, 'test');
$manager->apply($request, array($configuration));
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage No converter named 'test' found for conversion of parameter 'param'.
*/
public function testApplyNamedConverterNoConverter()
{
$request = new Request();
$request->attributes->set('param', '1234');
$configuration = new Configuration\ParamConverter(array(
'name' => 'param',
'class' => 'stdClass',
'converter' => 'test',
));
$manager = new ParamConverterManager();
$manager->apply($request, array($configuration));
}
public function testApplyNotCalledOnAlreadyConvertedObjects()
{
$converter = $this->createParamConverterMock();
$converter
->expects($this->never())
->method('supports')
;
$converter
->expects($this->never())
->method('apply')
;
$request = new Request();
$request->attributes->set('converted', new \stdClass());
$configuration = new Configuration\ParamConverter(array(
'name' => 'converted',
'class' => 'stdClass',
));
$manager = new ParamConverterManager();
$manager->add($converter);
$manager->apply($request, array($configuration));
}
protected function createParamConverterMock()
{
return $this->getMockBuilder('Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterInterface')->getMock();
}
}

View file

@ -0,0 +1,84 @@
<?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\Tests\Request\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\PsrServerRequestParamConverter;
use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
use Symfony\Component\HttpFoundation\Request;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
* @requires PHP 5.4
*/
class PsrServerRequestParamConverterTest extends \PHPUnit_Framework_TestCase
{
public function testSupports()
{
$converter = new PsrServerRequestParamConverter(new DiactorosFactory());
$config = $this->createConfiguration('Psr\Http\Message\ServerRequestInterface');
$this->assertTrue($converter->supports($config));
$config = $this->createConfiguration('Psr\Http\Message\RequestInterface');
$this->assertTrue($converter->supports($config));
$config = $this->createConfiguration('Psr\Http\Message\MessageInterface');
$this->assertTrue($converter->supports($config));
$config = $this->createConfiguration(__CLASS__);
$this->assertFalse($converter->supports($config));
$config = $this->createConfiguration();
$this->assertFalse($converter->supports($config));
}
public function testApply()
{
$converter = new PsrServerRequestParamConverter(new DiactorosFactory());
$request = new Request(
array('foo' => 'bar'),
array(),
array(),
array(),
array(),
array('HTTP_HOST' => 'dunglas.fr')
);
$config = $this->createConfiguration('Psr\Http\Message\ServerRequestInterface', 'request');
$converter->apply($request, $config);
$this->assertInstanceOf('Psr\Http\Message\ServerRequestInterface', $request->attributes->get('request'));
$this->assertEquals('bar', $request->query->get('foo'));
}
private function createConfiguration($class = null, $name = null)
{
$config = $this
->getMockBuilder('Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter')
->setMethods(array('getClass', 'getAliasName', 'getOptions', 'getName', 'allowArray', 'isOptional'))
->disableOriginalConstructor()
->getMock()
;
if (null !== $name) {
$config->expects($this->any())
->method('getName')
->will($this->returnValue($name));
}
if (null !== $class) {
$config->expects($this->any())
->method('getClass')
->will($this->returnValue($class));
}
return $config;
}
}

View file

@ -0,0 +1,23 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Tests\Request\ParamConverter;
/*
* This class is a helper Repository for DoctrineParamConverter's map_method_signature functionality
*/
class TestUserRepository
{
public function findByFullName($firstName, $lastName)
{
return $firstName.' '.$lastName;
}
}

View file

@ -0,0 +1,141 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sensio\Bundle\FrameworkExtraBundle\Tests\Routing;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Routing\AnnotatedRouteControllerLoader;
class AnnotatedRouteControllerLoaderTest extends \PHPUnit_Framework_TestCase
{
public function testServiceOptionIsAllowedOnClass()
{
$route = $this->getMockBuilder('Symfony\Component\Routing\Route')
->setMethods(array('setDefault'))
->disableOriginalConstructor()
->getMock()
;
$route
->expects($this->once())
->method('setDefault')
->with('_controller', 'service:testServiceOptionIsAllowedOnClass')
;
$annotation = new Route(array());
$annotation->setService('service');
$reader = $this->getMockBuilder('Doctrine\Common\Annotations\Reader')
->setMethods(array('getClassAnnotation', 'getMethodAnnotations'))
->disableOriginalConstructor()
->getMockForAbstractClass()
;
$reader
->expects($this->once())
->method('getClassAnnotation')
->will($this->returnValue($annotation))
;
$reader
->expects($this->once())
->method('getMethodAnnotations')
->will($this->returnValue(array()))
;
$loader = $this->getMockBuilder('Sensio\Bundle\FrameworkExtraBundle\Routing\AnnotatedRouteControllerLoader')
->setConstructorArgs(array($reader))
->getMock()
;
$r = new \ReflectionMethod($loader, 'configureRoute');
$r->setAccessible(true);
$r->invoke(
$loader,
$route,
new \ReflectionClass($this),
new \ReflectionMethod($this, 'testServiceOptionIsAllowedOnClass'),
null
);
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage The service option can only be specified at class level.
*/
public function testServiceOptionIsNotAllowedOnMethod()
{
$route = $this->getMockBuilder('Symfony\Component\Routing\Route')
->disableOriginalConstructor()
->getMock()
;
$reader = $this->getMockBuilder('Doctrine\Common\Annotations\Reader')
->setMethods(array('getClassAnnotation', 'getMethodAnnotations'))
->disableOriginalConstructor()
->getMockForAbstractClass()
;
$annotation = new Route(array());
$annotation->setService('service');
$reader
->expects($this->once())
->method('getClassAnnotation')
->will($this->returnValue(null))
;
$reader
->expects($this->once())
->method('getMethodAnnotations')
->will($this->returnValue(array($annotation)))
;
$loader = $this->getMockBuilder('Sensio\Bundle\FrameworkExtraBundle\Routing\AnnotatedRouteControllerLoader')
->setConstructorArgs(array($reader))
->getMock()
;
$r = new \ReflectionMethod($loader, 'configureRoute');
$r->setAccessible(true);
$r->invoke(
$loader,
$route,
new \ReflectionClass($this),
new \ReflectionMethod($this, 'testServiceOptionIsNotAllowedOnMethod'),
null
);
}
public function testLoad()
{
$loader = new AnnotatedRouteControllerLoader(new AnnotationReader());
AnnotationRegistry::registerLoader('class_exists');
$rc = $loader->load('Sensio\Bundle\FrameworkExtraBundle\Tests\Routing\Fixtures\FoobarController');
$this->assertInstanceOf('Symfony\Component\Routing\RouteCollection', $rc);
$this->assertCount(2, $rc);
$this->assertInstanceOf('Symfony\Component\Routing\Route', $rc->get('index'));
// depending on the Symfony version, it can return GET or an empty array (on 2.3)
// which has the same behavior anyway
$methods = $rc->get('index')->getMethods();
$this->assertTrue(empty($methods) || array('GET') == $methods);
$this->assertInstanceOf('Symfony\Component\Routing\Route', $rc->get('new'));
$this->assertEquals(array('POST'), $rc->get('new')->getMethods());
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace Sensio\Bundle\FrameworkExtraBundle\Tests\Routing\Fixtures;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
/**
* @Route("/base")
* @Method("GET")
*/
class FoobarController
{
/**
* @Route("/", name="index")
*/
public function indexAction()
{
}
/**
* @Route("/new", name="new")
* @Method("POST")
*/
public function newAction()
{
}
}

View file

@ -0,0 +1,7 @@
<?php
namespace Sensio\Bundle\FrameworkExtraBundle\Tests\Templating\Fixture\BarBundle\Controller;
class BarController
{
}

View file

@ -0,0 +1,7 @@
<?php
namespace Sensio\Bundle\FrameworkExtraBundle\Tests\Templating\Fixture\Controller;
class OutOfBundleController
{
}

View file

@ -0,0 +1,7 @@
<?php
namespace Sensio\Bundle\FrameworkExtraBundle\Tests\Templating\Fixture\FooBarBundle\Controller;
class FooBarController
{
}

Some files were not shown because too many files have changed in this diff Show more