Merge branch '3.1' into 3.2
* 3.1: [Routing] Fail properly when a route parameter name cannot be used as a PCRE subpattern name [FrameworkBundle] Improve performance of ControllerNameParser Update documentation link to the component [HttpFoundation] Add links to RFC-7231 [DI] Initialize properties before method calls Tag missing internals [WebProfilerBundle] Dont use request attributes in RouterController Fix complete config tests
This commit is contained in:
commit
5461c1ed53
@ -46,11 +46,12 @@ class ControllerNameParser
|
||||
*/
|
||||
public function parse($controller)
|
||||
{
|
||||
$originalController = $controller;
|
||||
if (3 !== count($parts = explode(':', $controller))) {
|
||||
$parts = explode(':', $controller);
|
||||
if (3 !== count($parts) || in_array('', $parts, true)) {
|
||||
throw new \InvalidArgumentException(sprintf('The "%s" controller is not a valid "a:b:c" controller string.', $controller));
|
||||
}
|
||||
|
||||
$originalController = $controller;
|
||||
list($bundle, $controller, $action) = $parts;
|
||||
$controller = str_replace('/', '\\', $controller);
|
||||
$bundles = array();
|
||||
|
@ -20,7 +20,7 @@ use Symfony\Component\Config\Loader\LoaderResolverInterface;
|
||||
* DelegatingLoader delegates route loading to other loaders using a loader resolver.
|
||||
*
|
||||
* This implementation resolves the _controller attribute from the short notation
|
||||
* to the fully-qualified form (from a:b:c to class:method).
|
||||
* to the fully-qualified form (from a:b:c to class::method).
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
@ -75,15 +75,17 @@ class DelegatingLoader extends BaseDelegatingLoader
|
||||
}
|
||||
|
||||
foreach ($collection->all() as $route) {
|
||||
if ($controller = $route->getDefault('_controller')) {
|
||||
try {
|
||||
$controller = $this->parser->parse($controller);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
// unable to optimize unknown notation
|
||||
}
|
||||
|
||||
$route->setDefault('_controller', $controller);
|
||||
if (!$controller = $route->getDefault('_controller')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$controller = $this->parser->parse($controller);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
// unable to optimize unknown notation
|
||||
}
|
||||
|
||||
$route->setDefault('_controller', $controller);
|
||||
}
|
||||
|
||||
return $collection;
|
||||
|
@ -20,7 +20,9 @@ abstract class CompleteConfigurationTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
private static $containerCache = array();
|
||||
|
||||
abstract protected function loadFromFile(ContainerBuilder $container, $file);
|
||||
abstract protected function getLoader(ContainerBuilder $container);
|
||||
|
||||
abstract protected function getFileExtension();
|
||||
|
||||
public function testRolesHierarchy()
|
||||
{
|
||||
@ -334,6 +336,8 @@ abstract class CompleteConfigurationTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
protected function getContainer($file)
|
||||
{
|
||||
$file = $file.'.'.$this->getFileExtension();
|
||||
|
||||
if (isset(self::$containerCache[$file])) {
|
||||
return self::$containerCache[$file];
|
||||
}
|
||||
@ -343,7 +347,7 @@ abstract class CompleteConfigurationTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
$bundle = new SecurityBundle();
|
||||
$bundle->build($container); // Attach all default factories
|
||||
$this->loadFromFile($container, $file);
|
||||
$this->getLoader($container)->load($file);
|
||||
|
||||
$container->getCompilerPassConfig()->setOptimizationPasses(array());
|
||||
$container->getCompilerPassConfig()->setRemovingPasses(array());
|
||||
|
@ -17,9 +17,13 @@ use Symfony\Component\Config\FileLocator;
|
||||
|
||||
class PhpCompleteConfigurationTest extends CompleteConfigurationTest
|
||||
{
|
||||
protected function loadFromFile(ContainerBuilder $container, $file)
|
||||
protected function getLoader(ContainerBuilder $container)
|
||||
{
|
||||
$loadXml = new PhpFileLoader($container, new FileLocator(__DIR__.'/Fixtures/php'));
|
||||
$loadXml->load($file.'.php');
|
||||
return new PhpFileLoader($container, new FileLocator(__DIR__.'/Fixtures/php'));
|
||||
}
|
||||
|
||||
protected function getFileExtension()
|
||||
{
|
||||
return 'php';
|
||||
}
|
||||
}
|
||||
|
@ -17,9 +17,13 @@ use Symfony\Component\Config\FileLocator;
|
||||
|
||||
class XmlCompleteConfigurationTest extends CompleteConfigurationTest
|
||||
{
|
||||
protected function loadFromFile(ContainerBuilder $container, $file)
|
||||
protected function getLoader(ContainerBuilder $container)
|
||||
{
|
||||
$loadXml = new XmlFileLoader($container, new FileLocator(__DIR__.'/Fixtures/xml'));
|
||||
$loadXml->load($file.'.xml');
|
||||
return new XmlFileLoader($container, new FileLocator(__DIR__.'/Fixtures/xml'));
|
||||
}
|
||||
|
||||
protected function getFileExtension()
|
||||
{
|
||||
return 'xml';
|
||||
}
|
||||
}
|
||||
|
@ -17,9 +17,13 @@ use Symfony\Component\Config\FileLocator;
|
||||
|
||||
class YamlCompleteConfigurationTest extends CompleteConfigurationTest
|
||||
{
|
||||
protected function loadFromFile(ContainerBuilder $container, $file)
|
||||
protected function getLoader(ContainerBuilder $container)
|
||||
{
|
||||
$loadXml = new YamlFileLoader($container, new FileLocator(__DIR__.'/Fixtures/yml'));
|
||||
$loadXml->load($file.'.yml');
|
||||
return new YamlFileLoader($container, new FileLocator(__DIR__.'/Fixtures/yml'));
|
||||
}
|
||||
|
||||
protected function getFileExtension()
|
||||
{
|
||||
return 'yml';
|
||||
}
|
||||
}
|
||||
|
@ -901,15 +901,15 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
|
||||
$this->shareService($definition, $service, $id);
|
||||
}
|
||||
|
||||
foreach ($definition->getMethodCalls() as $call) {
|
||||
$this->callMethod($service, $call);
|
||||
}
|
||||
|
||||
$properties = $this->resolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getProperties())));
|
||||
foreach ($properties as $name => $value) {
|
||||
$service->$name = $value;
|
||||
}
|
||||
|
||||
foreach ($definition->getMethodCalls() as $call) {
|
||||
$this->callMethod($service, $call);
|
||||
}
|
||||
|
||||
if ($callable = $definition->getConfigurator()) {
|
||||
if (is_array($callable)) {
|
||||
$callable[0] = $parameterBag->resolveValue($callable[0]);
|
||||
|
@ -347,8 +347,8 @@ class PhpDumper extends Dumper
|
||||
$code .= $this->addNewInstance($sDefinition, '$'.$name, ' = ', $id);
|
||||
|
||||
if (!$this->hasReference($id, $sDefinition->getMethodCalls(), true) && !$this->hasReference($id, $sDefinition->getProperties(), true)) {
|
||||
$code .= $this->addServiceMethodCalls(null, $sDefinition, $name);
|
||||
$code .= $this->addServiceProperties(null, $sDefinition, $name);
|
||||
$code .= $this->addServiceMethodCalls(null, $sDefinition, $name);
|
||||
$code .= $this->addServiceConfigurator(null, $sDefinition, $name);
|
||||
}
|
||||
|
||||
@ -517,8 +517,8 @@ class PhpDumper extends Dumper
|
||||
}
|
||||
|
||||
$name = (string) $this->definitionVariables->offsetGet($iDefinition);
|
||||
$code .= $this->addServiceMethodCalls(null, $iDefinition, $name);
|
||||
$code .= $this->addServiceProperties(null, $iDefinition, $name);
|
||||
$code .= $this->addServiceMethodCalls(null, $iDefinition, $name);
|
||||
$code .= $this->addServiceConfigurator(null, $iDefinition, $name);
|
||||
}
|
||||
|
||||
@ -678,8 +678,8 @@ EOF;
|
||||
$this->addServiceInlinedDefinitions($id, $definition).
|
||||
$this->addServiceInstance($id, $definition).
|
||||
$this->addServiceInlinedDefinitionsSetup($id, $definition).
|
||||
$this->addServiceMethodCalls($id, $definition).
|
||||
$this->addServiceProperties($id, $definition).
|
||||
$this->addServiceMethodCalls($id, $definition).
|
||||
$this->addServiceConfigurator($id, $definition).
|
||||
$this->addServiceReturn($id, $definition)
|
||||
;
|
||||
|
@ -796,6 +796,20 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertTrue($classInList);
|
||||
}
|
||||
|
||||
public function testInitializePropertiesBeforeMethodCalls()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('foo', 'stdClass');
|
||||
$container->register('bar', 'MethodCallClass')
|
||||
->setProperty('simple', 'bar')
|
||||
->setProperty('complex', new Reference('foo'))
|
||||
->addMethodCall('callMe');
|
||||
|
||||
$container->compile();
|
||||
|
||||
$this->assertTrue($container->get('bar')->callPassed(), '->compile() initializes properties before method calls');
|
||||
}
|
||||
|
||||
public function testAutowiring()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
|
@ -321,4 +321,23 @@ class PhpDumperTest extends \PHPUnit_Framework_TestCase
|
||||
$dumper = new PhpDumper($container);
|
||||
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services13.php', $dumper->dump(), '->dump() dumps inline definitions which reference service_container');
|
||||
}
|
||||
|
||||
public function testInitializePropertiesBeforeMethodCalls()
|
||||
{
|
||||
require_once self::$fixturesPath.'/includes/classes.php';
|
||||
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('foo', 'stdClass');
|
||||
$container->register('bar', 'MethodCallClass')
|
||||
->setProperty('simple', 'bar')
|
||||
->setProperty('complex', new Reference('foo'))
|
||||
->addMethodCall('callMe');
|
||||
$container->compile();
|
||||
|
||||
$dumper = new PhpDumper($container);
|
||||
eval('?>'.$dumper->dump(array('class' => 'Symfony_DI_PhpDumper_Test_Properties_Before_Method_Calls')));
|
||||
|
||||
$container = new \Symfony_DI_PhpDumper_Test_Properties_Before_Method_Calls();
|
||||
$this->assertTrue($container->get('bar')->callPassed(), '->dump() initializes properties before method calls');
|
||||
}
|
||||
}
|
||||
|
@ -59,3 +59,20 @@ class BarUserClass
|
||||
$this->bar = $bar;
|
||||
}
|
||||
}
|
||||
|
||||
class MethodCallClass
|
||||
{
|
||||
public $simple;
|
||||
public $complex;
|
||||
private $callPassed = false;
|
||||
|
||||
public function callMe()
|
||||
{
|
||||
$this->callPassed = is_scalar($this->simple) && is_object($this->complex);
|
||||
}
|
||||
|
||||
public function callPassed()
|
||||
{
|
||||
return $this->callPassed;
|
||||
}
|
||||
}
|
||||
|
@ -228,11 +228,11 @@ class ProjectServiceContainer extends Container
|
||||
|
||||
$this->services['foo'] = $instance = \Bar\FooClass::getInstance('foo', $a, array($this->getParameter('foo') => 'foo is '.$this->getParameter('foo').'', 'foobar' => $this->getParameter('foo')), true, $this);
|
||||
|
||||
$instance->setBar($this->get('bar'));
|
||||
$instance->initialize();
|
||||
$instance->foo = 'bar';
|
||||
$instance->moo = $a;
|
||||
$instance->qux = array($this->getParameter('foo') => 'foo is '.$this->getParameter('foo').'', 'foobar' => $this->getParameter('foo'));
|
||||
$instance->setBar($this->get('bar'));
|
||||
$instance->initialize();
|
||||
sc_configure($instance);
|
||||
|
||||
return $instance;
|
||||
@ -425,8 +425,8 @@ class ProjectServiceContainer extends Container
|
||||
{
|
||||
$this->services['inlined'] = $instance = new \Bar();
|
||||
|
||||
$instance->setBaz($this->get('baz'));
|
||||
$instance->pub = 'pub';
|
||||
$instance->setBaz($this->get('baz'));
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
@ -224,11 +224,11 @@ class ProjectServiceContainer extends Container
|
||||
|
||||
$this->services['foo'] = $instance = \Bar\FooClass::getInstance('foo', $a, array('bar' => 'foo is bar', 'foobar' => 'bar'), true, $this);
|
||||
|
||||
$instance->setBar($this->get('bar'));
|
||||
$instance->initialize();
|
||||
$instance->foo = 'bar';
|
||||
$instance->moo = $a;
|
||||
$instance->qux = array('bar' => 'foo is bar', 'foobar' => 'bar');
|
||||
$instance->setBar($this->get('bar'));
|
||||
$instance->initialize();
|
||||
sc_configure($instance);
|
||||
|
||||
return $instance;
|
||||
@ -275,8 +275,8 @@ class ProjectServiceContainer extends Container
|
||||
|
||||
$this->services['foo_with_inline'] = $instance = new \Foo();
|
||||
|
||||
$a->setBaz($this->get('baz'));
|
||||
$a->pub = 'pub';
|
||||
$a->setBaz($this->get('baz'));
|
||||
|
||||
$instance->setBar($a);
|
||||
|
||||
|
@ -155,11 +155,11 @@ class ChoiceFormField extends FormField
|
||||
/**
|
||||
* Adds a choice to the current ones.
|
||||
*
|
||||
* This method should only be used internally.
|
||||
*
|
||||
* @param \DOMElement $node
|
||||
*
|
||||
* @throws \LogicException When choice provided is not multiple nor radio
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function addChoice(\DOMElement $node)
|
||||
{
|
||||
|
@ -14,9 +14,9 @@ namespace Symfony\Component\Form\Util;
|
||||
/**
|
||||
* Iterator for {@link OrderedHashMap} objects.
|
||||
*
|
||||
* This class is internal and should not be used.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class OrderedHashMapIterator implements \Iterator
|
||||
{
|
||||
|
@ -1475,6 +1475,8 @@ class Request
|
||||
/**
|
||||
* Checks whether or not the method is safe.
|
||||
*
|
||||
* @see https://tools.ietf.org/html/rfc7231#section-4.2.1
|
||||
*
|
||||
* @param bool $andCacheable Adds the additional condition that the method should be cacheable. True by default.
|
||||
*
|
||||
* @return bool
|
||||
@ -1505,6 +1507,8 @@ class Request
|
||||
/**
|
||||
* Checks whether the method is cacheable or not.
|
||||
*
|
||||
* @see https://tools.ietf.org/html/rfc7231#section-4.2.3
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isMethodCacheable()
|
||||
|
@ -28,13 +28,21 @@ class RouteCompiler implements RouteCompilerInterface
|
||||
*/
|
||||
const SEPARATORS = '/,;.:-_~+*=@|';
|
||||
|
||||
/**
|
||||
* The maximum supported length of a PCRE subpattern name
|
||||
* http://pcre.org/current/doc/html/pcre2pattern.html#SEC16.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
const VARIABLE_MAXIMUM_LENGTH = 32;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws \InvalidArgumentException If a path variable is named _fragment
|
||||
* @throws \LogicException If a variable is referenced more than once
|
||||
* @throws \DomainException If a variable name is numeric because PHP raises an error for such
|
||||
* subpatterns in PCRE and thus would break matching, e.g. "(?P<123>.+)".
|
||||
* @throws \DomainException If a variable name starts with a digit or if it is too long to be successfully used as
|
||||
* a PCRE subpattern.
|
||||
*/
|
||||
public static function compile(Route $route)
|
||||
{
|
||||
@ -121,13 +129,19 @@ class RouteCompiler implements RouteCompilerInterface
|
||||
}
|
||||
$isSeparator = '' !== $precedingChar && false !== strpos(static::SEPARATORS, $precedingChar);
|
||||
|
||||
if (is_numeric($varName)) {
|
||||
throw new \DomainException(sprintf('Variable name "%s" cannot be numeric in route pattern "%s". Please use a different name.', $varName, $pattern));
|
||||
// A PCRE subpattern name must start with a non-digit. Also a PHP variable cannot start with a digit so the
|
||||
// variable would not be usable as a Controller action argument.
|
||||
if (preg_match('/^\d/', $varName)) {
|
||||
throw new \DomainException(sprintf('Variable name "%s" cannot start with a digit in route pattern "%s". Please use a different name.', $varName, $pattern));
|
||||
}
|
||||
if (in_array($varName, $variables)) {
|
||||
throw new \LogicException(sprintf('Route pattern "%s" cannot reference variable name "%s" more than once.', $pattern, $varName));
|
||||
}
|
||||
|
||||
if (strlen($varName) > self::VARIABLE_MAXIMUM_LENGTH) {
|
||||
throw new \DomainException(sprintf('Variable name "%s" cannot be longer than %s characters in route pattern "%s". Please use a shorter name.', $varName, self::VARIABLE_MAXIMUM_LENGTH, $pattern));
|
||||
}
|
||||
|
||||
if ($isSeparator && $precedingText !== $precedingChar) {
|
||||
$tokens[] = array('text', substr($precedingText, 0, -strlen($precedingChar)));
|
||||
} elseif (!$isSeparator && strlen($precedingText) > 0) {
|
||||
|
@ -283,16 +283,16 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getNumericVariableNames
|
||||
* @dataProvider getVariableNamesStartingWithADigit
|
||||
* @expectedException \DomainException
|
||||
*/
|
||||
public function testRouteWithNumericVariableName($name)
|
||||
public function testRouteWithVariableNameStartingWithADigit($name)
|
||||
{
|
||||
$route = new Route('/{'.$name.'}');
|
||||
$route->compile();
|
||||
}
|
||||
|
||||
public function getNumericVariableNames()
|
||||
public function getVariableNamesStartingWithADigit()
|
||||
{
|
||||
return array(
|
||||
array('09'),
|
||||
@ -371,6 +371,15 @@ class RouteCompilerTest extends \PHPUnit_Framework_TestCase
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \DomainException
|
||||
*/
|
||||
public function testRouteWithTooLongVariableName()
|
||||
{
|
||||
$route = new Route(sprintf('/{%s}', str_repeat('a', RouteCompiler::VARIABLE_MAXIMUM_LENGTH + 1)));
|
||||
$route->compile();
|
||||
}
|
||||
}
|
||||
|
||||
class Utf8RouteCompiler extends RouteCompiler
|
||||
|
@ -12,9 +12,9 @@
|
||||
namespace Symfony\Component\Security\Core\Authentication\RememberMe;
|
||||
|
||||
/**
|
||||
* This class is only used by PersistentTokenRememberMeServices internally.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class PersistentToken implements PersistentTokenInterface
|
||||
{
|
||||
|
@ -7,7 +7,7 @@ The Validator component provides tools to validate values following the
|
||||
Resources
|
||||
---------
|
||||
|
||||
* [Documentation](https://symfony.com/doc/current/book/validation.html)
|
||||
* [Documentation](https://symfony.com/doc/current/components/validator.html)
|
||||
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
|
||||
* [Report issues](https://github.com/symfony/symfony/issues) and
|
||||
[send Pull Requests](https://github.com/symfony/symfony/pulls)
|
||||
|
Reference in New Issue
Block a user