Merge branch '2.7' into 2.8

* 2.7:
  [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:
Nicolas Grekas 2016-11-25 13:26:42 +01:00
commit 4d04c40ae3
21 changed files with 147 additions and 50 deletions

View File

@ -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();

View File

@ -21,7 +21,7 @@ use Psr\Log\LoggerInterface;
* 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>
*/
@ -92,15 +92,17 @@ class DelegatingLoader extends BaseDelegatingLoader
$this->loading = false;
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;

View File

@ -21,7 +21,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()
{
@ -259,6 +261,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];
}
@ -268,7 +272,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());

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -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';
}
}

View File

@ -87,7 +87,7 @@ class RouterController
$traceRequest = Request::create(
$request->getPathInfo(),
$request->getRequestServer()->get('REQUEST_METHOD'),
$request->getRequestAttributes()->all(),
array(),
$request->getRequestCookies()->all(),
array(),
$request->getRequestServer()->all()

View File

@ -937,15 +937,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]);

View File

@ -335,8 +335,8 @@ class PhpDumper extends Dumper
$code .= $this->addNewInstance($id, $sDefinition, '$'.$name, ' = ');
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);
}
@ -507,8 +507,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);
}
@ -683,8 +683,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)
;

View File

@ -858,6 +858,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();

View File

@ -275,4 +275,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');
}
}

View File

@ -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;
}
}

View File

@ -188,11 +188,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;
@ -351,8 +351,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;
}

View File

@ -197,11 +197,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;
@ -248,8 +248,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);

View File

@ -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)
{

View File

@ -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
{

View File

@ -1472,6 +1472,8 @@ class Request
/**
* Checks whether the method is safe or not.
*
* @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
@ -1484,6 +1486,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()

View File

@ -28,12 +28,20 @@ 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 \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)
{
@ -95,13 +103,19 @@ class RouteCompiler implements RouteCompilerInterface
$precedingChar = strlen($precedingText) > 0 ? substr($precedingText, -1) : '';
$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 && strlen($precedingText) > 1) {
$tokens[] = array('text', substr($precedingText, 0, -1));
} elseif (!$isSeparator && strlen($precedingText) > 0) {

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Routing\Tests;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCompiler;
class RouteCompilerTest extends \PHPUnit_Framework_TestCase
{
@ -176,16 +177,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'),
@ -264,4 +265,13 @@ 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();
}
}

View File

@ -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
{

View File

@ -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)