Initial commit

This commit is contained in:
2020-10-07 10:37:15 +02:00
commit ce5f440392
28157 changed files with 4429172 additions and 0 deletions

View File

@@ -0,0 +1,299 @@
<?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
*/
private $expires;
/**
* The number of seconds that the response is considered fresh by a private
* cache like a web browser.
*
* @var int
*/
private $maxage;
/**
* The number of seconds that the response is considered fresh by a public
* cache like a reverse proxy cache.
*
* @var int
*/
private $smaxage;
/**
* Whether the response is public or not.
*
* @var bool
*/
private $public;
/**
* Whether or not the response must be revalidated.
*
* @var bool
*/
private $mustRevalidate;
/**
* Additional "Vary:"-headers.
*
* @var array
*/
private $vary;
/**
* An expression to compute the Last-Modified HTTP header.
*
* @var string
*/
private $lastModified;
/**
* An expression to compute the ETag HTTP header.
*
* @var string
*/
private $etag;
/**
* max-stale Cache-Control header
* It can be expressed in seconds or with a relative time format (1 day, 2 weeks, ...).
*
* @var int|string
*/
private $maxStale;
/**
* 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 true === $this->public;
}
/**
* @return bool
*/
public function mustRevalidate()
{
return true === $this->mustRevalidate;
}
/**
* Forces a response to be revalidated.
*
* @param bool $mustRevalidate
*/
public function setMustRevalidate($mustRevalidate)
{
$this->mustRevalidate = (bool) $mustRevalidate;
}
/**
* Returns whether or not a response is private.
*
* @return bool
*/
public function isPrivate()
{
return false === $this->public;
}
/**
* 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;
}
/**
* @return int|string
*/
public function getMaxStale()
{
return $this->maxStale;
}
/**
* Sets the number of seconds for the max-stale cache-control header field.
*
* @param int|string $maxStale A number of seconds
*/
public function setMaxStale($maxStale)
{
$this->maxStale = $maxStale;
}
/**
* 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,29 @@
<?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;
/**
* Doctrine-specific ParamConverter with an easier syntax.
*
* @author Ryan Weaver <ryan@knpuniversity.com>
* @Annotation
*/
class Entity extends ParamConverter
{
public function setExpr($expr)
{
$options = $this->getOptions();
$options['expr'] = $expr;
$this->setOptions($options);
}
}

View File

@@ -0,0 +1,107 @@
<?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 Ryan Weaver <ryan@knpuniversity.com>
* @Annotation
*/
class IsGranted extends ConfigurationAnnotation
{
/**
* Sets the first argument that will be passed to isGranted().
*
* @var mixed
*/
private $attributes;
/**
* Sets the second argument passed to isGranted().
*
* @var mixed
*/
private $subject;
/**
* The message of the exception - has a nice default if not set.
*
* @var string
*/
private $message;
/**
* If set, will throw Symfony\Component\HttpKernel\Exception\HttpException
* with the given $statusCode.
* If null, Symfony\Component\Security\Core\Exception\AccessDeniedException.
* will be used.
*
* @var int|null
*/
private $statusCode;
public function setAttributes($attributes)
{
$this->attributes = $attributes;
}
public function getAttributes()
{
return $this->attributes;
}
public function setSubject($subject)
{
$this->subject = $subject;
}
public function getSubject()
{
return $this->subject;
}
public function getMessage()
{
return $this->message;
}
public function setMessage($message)
{
$this->message = $message;
}
public function getStatusCode()
{
return $this->statusCode;
}
public function setStatusCode($statusCode)
{
$this->statusCode = $statusCode;
}
public function setValue($value)
{
$this->setAttributes($value);
}
public function getAliasName()
{
return 'is_granted';
}
public function allowArray()
{
return true;
}
}

View File

@@ -0,0 +1,86 @@
<?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;
@trigger_error(sprintf('The "%s" annotation is deprecated since version 5.2. Use "%s" instead.', Method::class, \Symfony\Component\Routing\Annotation\Route::class), E_USER_DEPRECATED);
/**
* The Method class handles the Method annotation parts.
*
* @author Fabien Potencier <fabien@symfony.com>
* @Annotation
*
* @deprecated since version 5.2
*/
class Method extends ConfigurationAnnotation
{
/**
* An array of restricted HTTP methods.
*
* @var array
*/
private $methods = [];
/**
* 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 : [$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
*/
private $name;
/**
* The parameter class.
*
* @var string
*/
private $class;
/**
* An array of options.
*
* @var array
*/
private $options = [];
/**
* Whether or not the parameter is optional.
*
* @var bool
*/
private $isOptional = false;
/**
* Use explicitly named converter instead of iterating by priorities.
*
* @var string
*/
private $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->isOptional = (bool) $optional;
}
/**
* Returns whether or not the parameter is optional.
*
* @return bool
*/
public function isOptional()
{
return $this->isOptional;
}
/**
* 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,53 @@
<?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;
@trigger_error(sprintf('The "%s" annotation is deprecated since version 5.2. Use "%s" instead.', Route::class, BaseRoute::class), E_USER_DEPRECATED);
/**
* @author Kris Wallsmith <kris@symfony.com>
* @Annotation
*
* @deprecated since version 5.2
*/
class Route extends BaseRoute
{
private $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,90 @@
<?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
{
/**
* The expression evaluated to allow or deny access.
*
* @var string
*/
private $expression;
/**
* If set, will throw Symfony\Component\HttpKernel\Exception\HttpException
* with the given $statusCode.
* If null, Symfony\Component\Security\Core\Exception\AccessDeniedException.
* will be used.
*
* @var int|null
*/
protected $statusCode;
/**
* The message of the exception.
*
* @var string
*/
protected $message = 'Access denied.';
public function getExpression()
{
return $this->expression;
}
public function setExpression($expression)
{
$this->expression = $expression;
}
public function getStatusCode()
{
return $this->statusCode;
}
public function setStatusCode($statusCode)
{
$this->statusCode = $statusCode;
}
public function getMessage()
{
return $this->message;
}
public function setMessage($message)
{
$this->message = $message;
}
public function setValue($expression)
{
$this->setExpression($expression);
}
public function getAliasName()
{
return 'security';
}
public function allowArray()
{
return true;
}
}

View File

@@ -0,0 +1,157 @@
<?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 Template class handles the Template annotation parts.
*
* @author Fabien Potencier <fabien@symfony.com>
* @Annotation
*/
class Template extends ConfigurationAnnotation
{
/**
* The template.
*
* @var string
*/
protected $template;
/**
* The associative array of template variables.
*
* @var array
*/
private $vars = [];
/**
* Should the template be streamed?
*
* @var bool
*/
private $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;
}
/**
* Sets the template logic name.
*
* @param string $template The template logic name
*/
public function setValue($template)
{
$this->setTemplate($template);
}
/**
* Returns the template.
*
* @return string
*/
public function getTemplate()
{
return $this->template;
}
/**
* Sets the template.
*
* @param string $template The template
*/
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', [new Reference($id)]);
}
}
}
}

View File

@@ -0,0 +1,53 @@
<?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');
$disabled = $container->getParameter('sensio_framework_extra.disabled_converters');
$container->getParameterBag()->remove('sensio_framework_extra.disabled_converters');
foreach ($container->findTaggedServiceIds('request.param_converter') as $id => $converters) {
foreach ($converters as $converter) {
$name = isset($converter['converter']) ? $converter['converter'] : null;
if (null !== $name && \in_array($name, $disabled)) {
continue;
}
$priority = isset($converter['priority']) ? $converter['priority'] : 0;
if ('false' === $priority || false === $priority) {
$priority = null;
}
$definition->addMethodCall('add', [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;
/**
* Optimizes the container by removing unneeded listeners.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class OptimizerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('security.token_storage')) {
$container->removeDefinition('sensio_framework_extra.security.listener');
}
if (!$container->hasDefinition('twig')) {
$container->removeDefinition('sensio_framework_extra.view.listener');
}
}
}

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\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('sensio_framework_extra');
if (method_exists($treeBuilder, 'getRootNode')) {
$rootNode = $treeBuilder->getRootNode();
} else {
// BC layer for symfony/config 4.1 and older
$rootNode = $treeBuilder->root('sensio_framework_extra');
}
$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()
->arrayNode('disable')->prototype('scalar')->end()->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,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\DependencyInjection;
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterInterface;
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;
use Symfony\Component\Config\Resource\ClassExistenceResource;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\Security\Core\Authorization\ExpressionLanguage as SecurityExpressionLanguage;
use Zend\Diactoros\ServerRequestFactory;
/**
* @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'));
$annotationsToLoad = [];
$definitionsToRemove = [];
if ($config['router']['annotations']) {
@trigger_error(sprintf('Enabling the "sensio_framework_extra.router.annotations" configuration is deprecated since version 5.2. Set it to false and use the "%s" annotation from Symfony itself.', \Symfony\Component\Routing\Annotation\Route::class), E_USER_DEPRECATED);
$annotationsToLoad[] = 'routing.xml';
if (\PHP_VERSION_ID < 70000) {
$this->addClassesToCompile([
'Sensio\\Bundle\\FrameworkExtraBundle\\EventListener\\ControllerListener',
]);
}
}
if ($config['request']['converters']) {
$annotationsToLoad[] = 'converters.xml';
$container->registerForAutoconfiguration(ParamConverterInterface::class)
->addTag('request.param_converter');
$container->setParameter('sensio_framework_extra.disabled_converters', \is_string($config['request']['disable']) ? implode(',', $config['request']['disable']) : $config['request']['disable']);
$container->addResource(new ClassExistenceResource(ExpressionLanguage::class));
if (class_exists(ExpressionLanguage::class)) {
$container->setAlias('sensio_framework_extra.converter.doctrine.orm.expression_language', new Alias('sensio_framework_extra.converter.doctrine.orm.expression_language.default', false));
} else {
$definitionsToRemove[] = 'sensio_framework_extra.converter.doctrine.orm.expression_language.default';
}
if (\PHP_VERSION_ID < 70000) {
$this->addClassesToCompile([
// 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([
'Sensio\\Bundle\\FrameworkExtraBundle\\EventListener\\TemplateListener',
]);
}
}
if ($config['cache']['annotations']) {
$annotationsToLoad[] = 'cache.xml';
if (\PHP_VERSION_ID < 70000) {
$this->addClassesToCompile([
'Sensio\\Bundle\\FrameworkExtraBundle\\EventListener\\HttpCacheListener',
]);
}
}
if ($config['security']['annotations']) {
$annotationsToLoad[] = 'security.xml';
$container->addResource(new ClassExistenceResource(ExpressionLanguage::class));
if (class_exists(ExpressionLanguage::class)) {
// this resource can only be added if ExpressionLanguage exists (to avoid a fatal error)
$container->addResource(new ClassExistenceResource(SecurityExpressionLanguage::class));
if (class_exists(SecurityExpressionLanguage::class)) {
$container->setAlias('sensio_framework_extra.security.expression_language', new Alias($config['security']['expression_language'], false));
} else {
$definitionsToRemove[] = 'sensio_framework_extra.security.expression_language.default';
}
} else {
$definitionsToRemove[] = 'sensio_framework_extra.security.expression_language.default';
}
if (\PHP_VERSION_ID < 70000) {
$this->addClassesToCompile([
'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([
'Sensio\\Bundle\\FrameworkExtraBundle\\Configuration\\ConfigurationAnnotation',
]);
}
if ($config['request']['converters']) {
$container->getDefinition('sensio_framework_extra.converter.listener')->replaceArgument(1, $config['request']['auto_convert']);
}
}
if (!empty($config['templating']['controller_patterns'])) {
$container
->getDefinition('sensio_framework_extra.view.guesser')
->addArgument($config['templating']['controller_patterns']);
}
if ($config['psr_message']['enabled']) {
$loader->load('psr7.xml');
if (!class_exists(ServerRequestFactory::class)) {
$definitionsToRemove[] = 'sensio_framework_extra.psr7.argument_value_resolver.server_request';
}
}
foreach ($definitionsToRemove as $definition) {
$container->removeDefinition($definition);
}
}
/**
* 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,115 @@
<?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\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
*/
private $reader;
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.
*/
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
if (!\is_array($controller) && method_exists($controller, '__invoke')) {
$controller = [$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 = [];
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);
}
}
private function getConfigurations(array $annotations)
{
$configurations = [];
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;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return [
KernelEvents::CONTROLLER => 'onKernelController',
];
}
}

View File

@@ -0,0 +1,184 @@
<?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\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(), [200, 203, 300, 301, 302, 304, 404, 410])) {
return;
}
if (!$response->headers->hasCacheControlDirective('s-maxage') && null !== $age = $configuration->getSMaxAge()) {
$age = $this->convertToSecondsIfNeeded($age);
$response->setSharedMaxAge($age);
}
if ($configuration->mustRevalidate()) {
$response->headers->addCacheControlDirective('must-revalidate');
}
if (!$response->headers->hasCacheControlDirective('max-age') && null !== $age = $configuration->getMaxAge()) {
$age = $this->convertToSecondsIfNeeded($age);
$response->setMaxAge($age);
}
if (!$response->headers->hasCacheControlDirective('max-stale') && null !== $stale = $configuration->getMaxStale()) {
$stale = $this->convertToSecondsIfNeeded($stale);
$response->headers->addCacheControlDirective('max-stale', $stale);
}
if (!$response->headers->has('Expires') && null !== $configuration->getExpires()) {
$date = \DateTime::createFromFormat('U', strtotime($configuration->getExpires()), new \DateTimeZone('UTC'));
$response->setExpires($date);
}
if (!$response->headers->has('Vary') && null !== $configuration->getVary()) {
$response->setVary($configuration->getVary());
}
if ($configuration->isPublic()) {
$response->setPublic();
}
if ($configuration->isPrivate()) {
$response->setPrivate();
}
if (!$response->headers->has('Last-Modified') && isset($this->lastModifiedDates[$request])) {
$response->setLastModified($this->lastModifiedDates[$request]);
unset($this->lastModifiedDates[$request]);
}
if (!$response->headers->has('Etag') && isset($this->etags[$request])) {
$response->setEtag($this->etags[$request]);
unset($this->etags[$request]);
}
}
public static function getSubscribedEvents()
{
return [
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;
}
/**
* @param int|string $time Time that can be either expressed in seconds or with relative time format (1 day, 2 weeks, ...)
*
* @return int
*/
private function convertToSecondsIfNeeded($time)
{
if (!is_numeric($time)) {
$now = microtime(true);
$time = ceil(strtotime($time, $now) - $now);
}
return $time;
}
}

View File

@@ -0,0 +1,102 @@
<?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\EventListener;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Sensio\Bundle\FrameworkExtraBundle\Request\ArgumentNameConverter;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
/**
* Handles the IsGranted annotation on controllers.
*
* @author Ryan Weaver <ryan@knpuniversity.com>
*/
class IsGrantedListener implements EventSubscriberInterface
{
private $argumentNameConverter;
private $authChecker;
public function __construct(ArgumentNameConverter $argumentNameConverter, AuthorizationCheckerInterface $authChecker = null)
{
$this->argumentNameConverter = $argumentNameConverter;
$this->authChecker = $authChecker;
}
public function onKernelControllerArguments(FilterControllerArgumentsEvent $event)
{
$request = $event->getRequest();
/** @var $configurations IsGranted[] */
if (!$configurations = $request->attributes->get('_is_granted')) {
return;
}
if (null === $this->authChecker) {
throw new \LogicException('To use the @IsGranted tag, you need to install symfony/security-bundle and configure your security system.');
}
$arguments = $this->argumentNameConverter->getControllerArguments($event);
foreach ($configurations as $configuration) {
$subject = null;
if ($configuration->getSubject()) {
if (!isset($arguments[$configuration->getSubject()])) {
throw new \RuntimeException(sprintf('Could not find the subject "%s" for the @IsGranted annotation. Try adding a "$%s" argument to your controller method.', $configuration->getSubject(), $configuration->getSubject()));
}
$subject = $arguments[$configuration->getSubject()];
}
if (!$this->authChecker->isGranted($configuration->getAttributes(), $subject)) {
$argsString = $this->getIsGrantedString($configuration);
$message = $configuration->getMessage() ?: sprintf('Access Denied by controller annotation @IsGranted(%s)', $argsString);
if ($statusCode = $configuration->getStatusCode()) {
throw new HttpException($statusCode, $message);
}
throw new AccessDeniedException($message);
}
}
}
private function getIsGrantedString(IsGranted $isGranted)
{
$attributes = array_map(function ($attribute) {
return sprintf('"%s"', $attribute);
}, (array) $isGranted->getAttributes());
if (1 === \count($attributes)) {
$argsString = reset($attributes);
} else {
$argsString = sprintf('[%s]', implode(', ', $attributes));
}
if (null !== $isGranted->getSubject()) {
$argsString = sprintf('%s, %s', $argsString, $isGranted->getSubject());
}
return $argsString;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return [KernelEvents::CONTROLLER_ARGUMENTS => 'onKernelControllerArguments'];
}
}

View File

@@ -0,0 +1,122 @@
<?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\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
*/
private $manager;
private $autoConvert;
/**
* @var bool
*/
private $isParameterTypeSupported;
/**
* @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.
*/
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
$request = $event->getRequest();
$configurations = [];
if ($configuration = $request->attributes->get('_converters')) {
foreach (\is_array($configuration) ? $configuration : [$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([]);
$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;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return [
KernelEvents::CONTROLLER => 'onKernelController',
];
}
}

View File

@@ -0,0 +1,60 @@
<?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\EventListener;
use Psr\Http\Message\ResponseInterface;
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.
*/
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 [
KernelEvents::VIEW => 'onKernelView',
];
}
}

View File

@@ -0,0 +1,137 @@
<?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\EventListener;
use Psr\Log\LoggerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Request\ArgumentNameConverter;
use Sensio\Bundle\FrameworkExtraBundle\Security\ExpressionLanguage;
use Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\KernelEvents;
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\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 $argumentNameConverter;
private $tokenStorage;
private $authChecker;
private $language;
private $trustResolver;
private $roleHierarchy;
private $logger;
public function __construct(ArgumentNameConverter $argumentNameConverter, ExpressionLanguage $language = null, AuthenticationTrustResolverInterface $trustResolver = null, RoleHierarchyInterface $roleHierarchy = null, TokenStorageInterface $tokenStorage = null, AuthorizationCheckerInterface $authChecker = null, LoggerInterface $logger = null)
{
$this->argumentNameConverter = $argumentNameConverter;
$this->tokenStorage = $tokenStorage;
$this->authChecker = $authChecker;
$this->language = $language;
$this->trustResolver = $trustResolver;
$this->roleHierarchy = $roleHierarchy;
$this->logger = $logger;
}
public function onKernelControllerArguments(FilterControllerArgumentsEvent $event)
{
$request = $event->getRequest();
if (!$configurations = $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.');
}
foreach ($configurations as $configuration) {
if (!$this->language->evaluate($configuration->getExpression(), $this->getVariables($event))) {
if ($statusCode = $configuration->getStatusCode()) {
throw new HttpException($statusCode, $configuration->getMessage());
}
throw new AccessDeniedException($configuration->getMessage() ?: sprintf('Expression "%s" denied access.', $configuration->getExpression()));
}
}
}
// code should be sync with Symfony\Component\Security\Core\Authorization\Voter\ExpressionVoter
private function getVariables(FilterControllerArgumentsEvent $event)
{
$request = $event->getRequest();
$token = $this->tokenStorage->getToken();
if (null !== $this->roleHierarchy) {
$roles = $this->roleHierarchy->getReachableRoles($token->getRoles());
} else {
$roles = $token->getRoles();
}
$variables = [
'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,
];
$controllerArguments = $this->argumentNameConverter->getControllerArguments($event);
if ($diff = array_intersect(array_keys($variables), array_keys($controllerArguments))) {
foreach ($diff as $key => $variableName) {
if ($variables[$variableName] === $controllerArguments[$variableName]) {
unset($diff[$key]);
}
}
if ($diff) {
$singular = 1 === \count($diff);
if (null !== $this->logger) {
$this->logger->warning(sprintf('Controller argument%s "%s" collided with the built-in security expression variables. The built-in value%s are being used for the @Security expression.', $singular ? '' : 's', implode('", "', $diff), $singular ? 's' : ''));
}
}
}
// controller variables should also be accessible
return array_merge($controllerArguments, $variables);
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return [KernelEvents::CONTROLLER_ARGUMENTS => 'onKernelControllerArguments'];
}
}

View File

@@ -0,0 +1,147 @@
<?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\EventListener;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
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;
use Sensio\Bundle\FrameworkExtraBundle\Templating\TemplateGuesser;
/**
* Handles the Template annotation for actions.
*
* Depends on pre-processing of the ControllerListener.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TemplateListener implements EventSubscriberInterface
{
private $templateGuesser;
private $twig;
public function __construct(TemplateGuesser $templateGuesser, \Twig_Environment $twig = null)
{
$this->templateGuesser = $templateGuesser;
$this->twig = $twig;
}
/**
* Guesses the template name to render and its variables and adds them to
* the request object.
*/
public function onKernelController(FilterControllerEvent $event)
{
$request = $event->getRequest();
$template = $request->attributes->get('_template');
if (!$template instanceof Template) {
return;
}
$controller = $event->getController();
if (!\is_array($controller) && method_exists($controller, '__invoke')) {
$controller = [$controller, '__invoke'];
}
$template->setOwner($controller);
// when no template has been given, try to resolve it based on the controller
if (null === $template->getTemplate()) {
$template->setTemplate($this->templateGuesser->guessTemplateName($controller, $request));
}
}
/**
* Renders the template and initializes a new response object with the
* rendered template content.
*/
public function onKernelView(GetResponseForControllerResultEvent $event)
{
/* @var Template $template */
$request = $event->getRequest();
$template = $request->attributes->get('_template');
if (!$template instanceof Template) {
return;
}
if (null === $this->twig) {
throw new \LogicException('You can not use the "@Template" annotation if the Twig Bundle is not available.');
}
$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
if ($template->isStreamable()) {
$callback = function () use ($template, $parameters) {
$this->twig->display($template->getTemplate(), $parameters);
};
$event->setResponse(new StreamedResponse($callback));
} else {
$event->setResponse(new Response($this->twig->render($template->getTemplate(), $parameters)));
}
// make sure the owner (controller+dependencies) is not cached or stored elsewhere
$template->setOwner([]);
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return [
KernelEvents::CONTROLLER => ['onKernelController', -128],
KernelEvents::VIEW => 'onKernelView',
];
}
private function resolveDefaultParameters(Request $request, Template $template, $controller, $action)
{
$parameters = [];
$arguments = $template->getVars();
if (0 === \count($arguments)) {
$r = new \ReflectionObject($controller);
$arguments = [];
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,59 @@
<?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;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactoryInterface;
use Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent;
/**
* @author Ryan Weaver <ryan@knpuniversity.com>
*/
class ArgumentNameConverter
{
private $argumentMetadataFactory;
public function __construct(ArgumentMetadataFactoryInterface $argumentMetadataFactory)
{
$this->argumentMetadataFactory = $argumentMetadataFactory;
}
/**
* Returns an associative array of the controller arguments for the event.
*
* @param FilterControllerArgumentsEvent $event
*
* @return array
*/
public function getControllerArguments(FilterControllerArgumentsEvent $event)
{
$namedArguments = $event->getRequest()->attributes->all();
$argumentMetadatas = $this->argumentMetadataFactory->createArgumentMetadata($event->getController());
$controllerArguments = $event->getArguments();
foreach ($argumentMetadatas as $index => $argumentMetadata) {
if ($argumentMetadata->isVariadic()) {
// set the rest of the arguments as this arg's value
$namedArguments[$argumentMetadata->getName()] = \array_slice($controllerArguments, $index);
break;
}
if (!array_key_exists($index, $controllerArguments)) {
throw new \LogicException(sprintf('Could not find an argument value for argument %d of the controller.', $index));
}
$namedArguments[$argumentMetadata->getName()] = $controllerArguments[$index];
}
return $namedArguments;
}
}

View File

@@ -0,0 +1,54 @@
<?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\ArgumentValueResolver;
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
/**
* Injects the RequestInterface, MessageInterface or ServerRequestInterface when requested.
*
* @author Iltar van der Berg <kjarli@gmail.com>
*/
final class Psr7ServerRequestResolver implements ArgumentValueResolverInterface
{
private static $supportedTypes = [
'Psr\Http\Message\ServerRequestInterface' => true,
'Psr\Http\Message\RequestInterface' => true,
'Psr\Http\Message\MessageInterface' => true,
];
private $httpMessageFactory;
public function __construct(HttpMessageFactoryInterface $httpMessageFactory)
{
$this->httpMessageFactory = $httpMessageFactory;
}
/**
* {@inheritdoc}
*/
public function supports(Request $request, ArgumentMetadata $argument)
{
return isset(self::$supportedTypes[$argument->getType()]);
}
/**
* {@inheritdoc}
*/
public function resolve(Request $request, ArgumentMetadata $argument)
{
yield $this->httpMessageFactory->createRequest($request);
}
}

View File

@@ -0,0 +1,84 @@
<?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;
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()) {
$request->attributes->set($param, null);
return true;
}
$class = $configuration->getClass();
if (isset($options['format'])) {
$date = $class::createFromFormat($options['format'], $value);
if (0 < DateTime::getLastErrors()['warning_count']) {
$date = false;
}
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 $class($value);
}
$request->attributes->set($param, $date);
return true;
}
/**
* {@inheritdoc}
*/
public function supports(ParamConverter $configuration)
{
if (null === $configuration->getClass()) {
return false;
}
return 'DateTime' === $configuration->getClass() || is_subclass_of($configuration->getClass(), \PHP_VERSION_ID < 50500 ? 'DateTime' : 'DateTimeInterface');
}
}

View File

@@ -0,0 +1,344 @@
<?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 Doctrine\DBAL\Types\ConversionException;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\ExpressionLanguage\SyntaxError;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\NoResultException;
/**
* DoctrineParamConverter.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DoctrineParamConverter implements ParamConverterInterface
{
/**
* @var ManagerRegistry
*/
private $registry;
/**
* @var ExpressionLanguage
*/
private $language;
/**
* @var array
*/
private $defaultOptions;
public function __construct(ManagerRegistry $registry = null, ExpressionLanguage $expressionLanguage = null, array $options = [])
{
$this->registry = $registry;
$this->language = $expressionLanguage;
$defaultValues = [
'entity_manager' => null,
'exclude' => [],
'mapping' => [],
'strip_null' => false,
'expr' => null,
'id' => null,
'repository_method' => null,
'map_method_signature' => false,
'evict_cache' => false,
];
$this->defaultOptions = array_merge($defaultValues, $options);
}
/**
* {@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);
}
$errorMessage = null;
if ($expr = $options['expr']) {
$object = $this->findViaExpression($class, $request, $expr, $options, $configuration);
if (null === $object) {
$errorMessage = sprintf('The expression "%s" returned null', $expr);
}
// find by identifier?
} elseif (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()) {
$message = sprintf('%s object not found by the @%s annotation.', $class, $this->getAnnotationName($configuration));
if ($errorMessage) {
$message .= ' '.$errorMessage;
}
throw new NotFoundHttpException($message);
}
$request->attributes->set($name, $object);
return true;
}
private 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 ($options['repository_method']) {
$method = $options['repository_method'];
} else {
$method = 'find';
}
$om = $this->getManager($options['entity_manager'], $class);
if ($options['evict_cache'] && $om instanceof EntityManagerInterface) {
$cacheProvider = $om->getCache();
if ($cacheProvider && $cacheProvider->containsEntity($class, $id)) {
$cacheProvider->evictEntity($class, $id);
}
}
try {
return $om->getRepository($class)->$method($id);
} catch (NoResultException $e) {
return;
} catch (ConversionException $e) {
return;
}
}
private function getIdentifier(Request $request, $options, $name)
{
if (null !== $options['id']) {
if (!\is_array($options['id'])) {
$name = $options['id'];
} elseif (\is_array($options['id'])) {
$id = [];
foreach ($options['id'] as $field) {
if (false !== strstr($field, '%s')) {
// Convert "%s_uuid" to "foobar_uuid"
$field = sprintf($field, $name);
}
$id[$field] = $request->attributes->get($field);
}
return $id;
}
}
if ($request->attributes->has($name)) {
return $request->attributes->get($name);
}
if ($request->attributes->has('id') && !$options['id']) {
return $request->attributes->get('id');
}
return false;
}
private function findOneBy($class, Request $request, $options)
{
if (!$options['mapping']) {
$keys = $request->attributes->keys();
$options['mapping'] = $keys ? array_combine($keys, $keys) : [];
}
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 ($options['id'] && null === $request->attributes->get($options['id'])) {
return false;
}
$criteria = [];
$em = $this->getManager($options['entity_manager'], $class);
$metadata = $em->getClassMetadata($class);
$mapMethodSignature = $options['repository_method']
&& $options['map_method_signature']
&& true === $options['map_method_signature'];
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 null !== $value;
});
}
if (!$criteria) {
return false;
}
if ($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;
} catch (ConversionException $e) {
return;
}
}
private function findDataByMapMethodSignature($em, $class, $repositoryMethod, $criteria)
{
$arguments = [];
$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);
}
private function findViaExpression($class, Request $request, $expression, $options, ParamConverter $configuration)
{
if (null === $this->language) {
throw new \LogicException(sprintf('To use the @%s tag with the "expr" option, you need to install the ExpressionLanguage component.', $this->getAnnotationName($configuration)));
}
$repository = $this->getManager($options['entity_manager'], $class)->getRepository($class);
$variables = array_merge($request->attributes->all(), ['repository' => $repository]);
try {
return $this->language->evaluate($expression, $variables);
} catch (NoResultException $e) {
return;
} catch (ConversionException $e) {
return;
} catch (SyntaxError $e) {
throw new \LogicException(sprintf('Error parsing expression -- %s -- (%s)', $expression, $e->getMessage()), 0, $e);
}
}
/**
* {@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->getManagerNames())) {
return false;
}
if (null === $configuration->getClass()) {
return false;
}
$options = $this->getOptions($configuration, false);
// Doctrine Entity?
$em = $this->getManager($options['entity_manager'], $configuration->getClass());
if (null === $em) {
return false;
}
return !$em->getMetadataFactory()->isTransient($configuration->getClass());
}
private function getOptions(ParamConverter $configuration, $strict = true)
{
$passedOptions = $configuration->getOptions();
if (isset($passedOptions['repository_method'])) {
@trigger_error('The repository_method option of @ParamConverter is deprecated and will be removed in 6.0. Use the expr option or @Entity.', E_USER_DEPRECATED);
}
if (isset($passedOptions['map_method_signature'])) {
@trigger_error('The map_method_signature option of @ParamConverter is deprecated and will be removed in 6.0. Use the expr option or @Entity.', E_USER_DEPRECATED);
}
$extraKeys = array_diff(array_keys($passedOptions), array_keys($this->defaultOptions));
if ($extraKeys && $strict) {
throw new \InvalidArgumentException(sprintf('Invalid option(s) passed to @%s: %s', $this->getAnnotationName($configuration), implode(', ', $extraKeys)));
}
return array_replace($this->defaultOptions, $passedOptions);
}
private function getManager($name, $class)
{
if (null === $name) {
return $this->registry->getManagerForClass($class);
}
return $this->registry->getManager($name);
}
private function getAnnotationName(ParamConverter $configuration)
{
$r = new \ReflectionClass($configuration);
return $r->getShortName();
}
}

View File

@@ -0,0 +1,40 @@
<?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 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.
*
* @return bool True if the object is supported, else false
*/
public function supports(ParamConverter $configuration);
}

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\Request\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\HttpFoundation\Request;
/**
* Managers converters.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Henrik Bjornskov <henrik@bjrnskov.dk>
*/
class ParamConverterManager
{
/**
* @var array
*/
private $converters = [];
/**
* @var array
*/
private $namedConverters = [];
/**
* 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 array|object $configurations
*/
public function apply(Request $request, $configurations)
{
if (\is_object($configurations)) {
$configurations = [$configurations];
}
foreach ($configurations as $configuration) {
$this->applyConverter($request, $configuration);
}
}
/**
* Applies converter on request based on the given configuration.
*/
private function applyConverter(Request $request, ParamConverter $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 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 (null !== $priority) {
if (!isset($this->converters[$priority])) {
$this->converters[$priority] = [];
}
$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 = [];
foreach ($this->converters as $all) {
$converters = array_merge($converters, $all);
}
return $converters;
}
}

View File

@@ -0,0 +1,13 @@
<?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.controller.listener" class="Sensio\Bundle\FrameworkExtraBundle\EventListener\ControllerListener" public="false">
<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" public="false">
<tag name="kernel.event_subscriber" />
</service>
</services>
</container>

View File

@@ -0,0 +1,28 @@
<?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.converter.listener" class="Sensio\Bundle\FrameworkExtraBundle\EventListener\ParamConverterListener" public="false">
<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\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterManager" />
<service id="sensio_framework_extra.converter.doctrine.orm" class="Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\DoctrineParamConverter" public="false">
<tag name="request.param_converter" converter="doctrine.orm" />
<argument type="service" id="doctrine" on-invalid="ignore" />
<argument type="service" id="sensio_framework_extra.converter.doctrine.orm.expression_language" on-invalid="null" />
</service>
<service id="framework_extra_bundle.date_time_param_converter" class="Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\DateTimeParamConverter" public="false">
<tag name="request.param_converter" converter="datetime" />
</service>
<service id="sensio_framework_extra.converter.doctrine.orm.expression_language.default" class="Symfony\Component\ExpressionLanguage\ExpressionLanguage" public="false" />
</services>
</container>

View File

@@ -0,0 +1,21 @@
<?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.argument_value_resolver.server_request" class="Sensio\Bundle\FrameworkExtraBundle\Request\ArgumentValueResolver\Psr7ServerRequestResolver" public="false">
<argument type="service" id="sensio_framework_extra.psr7.http_message_factory" />
<tag name="controller.argument_value_resolver" />
</service>
<service id="sensio_framework_extra.psr7.listener.response" class="Sensio\Bundle\FrameworkExtraBundle\EventListener\PsrResponseListener" public="false">
<argument type="service" id="sensio_framework_extra.psr7.http_foundation_factory" />
<tag name="kernel.event_subscriber" />
</service>
</services>
</container>

View File

@@ -0,0 +1,28 @@
<?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.routing.loader.annot_class" class="Sensio\Bundle\FrameworkExtraBundle\Routing\AnnotatedRouteControllerLoader" public="false">
<tag name="routing.loader" />
<argument type="service" id="annotation_reader" />
<deprecated>The "%service_id%" service is deprecated since version 5.2</deprecated>
</service>
<service id="sensio_framework_extra.routing.loader.annot_dir" class="Symfony\Component\Routing\Loader\AnnotationDirectoryLoader" public="false">
<tag name="routing.loader" />
<argument type="service" id="file_locator" />
<argument type="service" id="sensio_framework_extra.routing.loader.annot_class" />
<deprecated>The "%service_id%" service is deprecated since version 5.2</deprecated>
</service>
<service id="sensio_framework_extra.routing.loader.annot_file" class="Symfony\Component\Routing\Loader\AnnotationFileLoader" public="false">
<tag name="routing.loader" />
<argument type="service" id="file_locator" />
<argument type="service" id="sensio_framework_extra.routing.loader.annot_class" />
<deprecated>The "%service_id%" service is deprecated since version 5.2</deprecated>
</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">
<services>
<service id="sensio_framework_extra.security.listener" class="Sensio\Bundle\FrameworkExtraBundle\EventListener\SecurityListener" public="false">
<argument type="service" id="framework_extra_bundle.argument_name_convertor" />
<argument type="service" id="sensio_framework_extra.security.expression_language.default" 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" />
<argument type="service" id="logger" 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" />
<service id="framework_extra_bundle.event.is_granted" class="Sensio\Bundle\FrameworkExtraBundle\EventListener\IsGrantedListener" public="false">
<argument type="service" id="framework_extra_bundle.argument_name_convertor" />
<argument type="service" id="security.authorization_checker" on-invalid="null" />
<tag name="kernel.event_subscriber" />
</service>
<service id="framework_extra_bundle.argument_name_convertor" class="Sensio\Bundle\FrameworkExtraBundle\Request\ArgumentNameConverter" public="false">
<argument type="service" id="argument_metadata_factory" />
</service>
</services>
</container>

View File

@@ -0,0 +1,18 @@
<?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.view.guesser" class="Sensio\Bundle\FrameworkExtraBundle\Templating\TemplateGuesser" public="false">
<argument type="service" id="kernel" />
</service>
<service id="sensio_framework_extra.view.listener" class="Sensio\Bundle\FrameworkExtraBundle\EventListener\TemplateListener" public="false">
<tag name="kernel.event_subscriber" />
<argument type="service" id="sensio_framework_extra.view.guesser" />
<argument type="service" id="twig" on-invalid="null" />
</service>
</services>
</container>

View File

@@ -0,0 +1,91 @@
<?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;
@trigger_error(sprintf('The "%s" class is deprecated since version 5.2. Use "%s" instead.', AnnotatedRouteControllerLoader::class, \Symfony\Bundle\FrameworkBundle\Routing\AnnotatedRouteControllerLoader::class), E_USER_DEPRECATED);
/**
* 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>
*
* @deprecated since version 5.2
*/
class AnnotatedRouteControllerLoader extends AnnotationClassLoader
{
/**
* Configures the _controller default parameter and eventually the HTTP method
* requirement of a given Route instance.
*
* @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());
} elseif ('__invoke' === $method->getName()) {
$route->setDefault('_controller', $class->getName());
} else {
$route->setDefault('_controller', $class->getName().'::'.$method->getName());
}
// requirements (@Method)
foreach ($this->reader->getMethodAnnotations($method) as $configuration) {
if ($configuration instanceof Method) {
$route->setMethods($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.
*
* @return string The default route name
*/
protected function getDefaultRouteName(\ReflectionClass $class, \ReflectionMethod $method)
{
$routeName = parent::getDefaultRouteName($class, $method);
return preg_replace(
['/(bundle|controller)_/', '/action(_\d+)?$/', '/__/'],
['_', '\\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
/*
* 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;
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\OptimizerPass;
/**
* @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 OptimizerPass());
$container->addCompilerPass(new AddExpressionLanguageProvidersPass());
}
}

View File

@@ -0,0 +1,116 @@
<?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 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
*/
private $kernel;
/**
* @var string[]
*/
private $controllerPatterns;
/**
* @param string[] $controllerPatterns Regexps extracting the controller name from its FQN
*/
public function __construct(KernelInterface $kernel, array $controllerPatterns = [])
{
$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
*
* @return string The template name
*
* @throws \InvalidArgumentException
*/
public function guessTemplateName($controller, Request $request)
{
if (\is_object($controller) && method_exists($controller, '__invoke')) {
$controller = [$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 = str_replace('\\', '/', strtolower(preg_replace('/([a-z\d])([A-Z])/', '\\1_\\2', $tempMatch[1])));
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 ('__invoke' === $controller[1]) {
$matchAction = $matchController;
$matchController = null;
} else {
$matchAction = preg_replace('/Action$/', '', $controller[1]);
}
$matchAction = strtolower(preg_replace('/([a-z\d])([A-Z])/', '\\1_\\2', $matchAction));
$bundleName = $this->getBundleForClass($className);
return sprintf(($bundleName ? '@'.$bundleName.'/' : '').$matchController.($matchController ? '/' : '').$matchAction.'.'.$request->getRequestFormat().'.twig');
}
/**
* Returns the bundle name in which the given class name is located.
*
* @param string $class A fully qualified controller class name
*
* @return string|null $bundle A bundle name
*/
private function getBundleForClass($class)
{
$reflectionClass = new \ReflectionClass($class);
$bundles = $this->kernel->getBundles();
do {
$namespace = $reflectionClass->getNamespaceName();
foreach ($bundles as $bundle) {
if ('Symfony\Bundle\FrameworkBundle' === $bundle->getNamespace()) {
continue;
}
if (0 === strpos($namespace, $bundle->getNamespace())) {
return preg_replace('/Bundle$/', '', $bundle->getName());
}
}
$reflectionClass = $reflectionClass->getParentClass();
} while ($reflectionClass);
}
}