Merge branch '3.2' into 3.3

* 3.2:
  [DI] use assertStringEqualsFile when possible
  [VarDumper] Adapt to php 7.2 changes
  [DI] Fix using private services in expressions
  [Form][TwigBridge] Don't render _method in form_rest() for a child form
  [Form] Static call TimezoneType::getTimezones
  Removed references for non existent validator constraints
  Remove unused mocks/vars
  [DoctrineBridge][PropertyInfo] Added support for Doctrine Embeddables
  [Validator] Fix IbanValidator for ukrainian IBANs
This commit is contained in:
Nicolas Grekas 2017-07-26 08:42:48 +02:00
commit d56947698f
23 changed files with 329 additions and 38 deletions

View File

@ -50,7 +50,17 @@ class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeE
return;
}
return array_merge($metadata->getFieldNames(), $metadata->getAssociationNames());
$properties = array_merge($metadata->getFieldNames(), $metadata->getAssociationNames());
if ($metadata instanceof ClassMetadataInfo && class_exists('Doctrine\ORM\Mapping\Embedded') && $metadata->embeddedClasses) {
$properties = array_filter($properties, function ($property) {
return false === strpos($property, '.');
});
$properties = array_merge($properties, array_keys($metadata->embeddedClasses));
}
return $properties;
}
/**
@ -105,6 +115,10 @@ class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeE
));
}
if ($metadata instanceof ClassMetadataInfo && class_exists('Doctrine\ORM\Mapping\Embedded') && isset($metadata->embeddedClasses[$property])) {
return array(new Type(Type::BUILTIN_TYPE_OBJECT, false, $metadata->embeddedClasses[$property]['class']));
}
if ($metadata->hasField($property)) {
$typeOfField = $metadata->getTypeOfField($property);
$nullable = $metadata instanceof ClassMetadataInfo && $metadata->isNullable($property);

View File

@ -64,6 +64,21 @@ class DoctrineExtractorTest extends TestCase
);
}
public function testGetPropertiesWithEmbedded()
{
if (!class_exists('Doctrine\ORM\Mapping\Embedded')) {
$this->markTestSkipped('@Embedded is not available in Doctrine ORM lower than 2.5.');
}
$this->assertEquals(
array(
'id',
'embedded',
),
$this->extractor->getProperties('Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineWithEmbedded')
);
}
/**
* @dataProvider typesProvider
*/
@ -72,6 +87,27 @@ class DoctrineExtractorTest extends TestCase
$this->assertEquals($type, $this->extractor->getTypes('Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy', $property, array()));
}
public function testExtractWithEmbedded()
{
if (!class_exists('Doctrine\ORM\Mapping\Embedded')) {
$this->markTestSkipped('@Embedded is not available in Doctrine ORM lower than 2.5.');
}
$expectedTypes = array(new Type(
Type::BUILTIN_TYPE_OBJECT,
false,
'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineEmbeddable'
));
$actualTypes = $this->extractor->getTypes(
'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineWithEmbedded',
'embedded',
array()
);
$this->assertEquals($expectedTypes, $actualTypes);
}
public function typesProvider()
{
return array(

View File

@ -0,0 +1,28 @@
<?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 Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Embeddable;
/**
* @Embeddable
*
* @author Udaltsov Valentin <udaltsov.valentin@gmail.com>
*/
class DoctrineEmbeddable
{
/**
* @Column(type="string")
*/
protected $field;
}

View File

@ -0,0 +1,36 @@
<?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 Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\Embedded;
/**
* @Entity
*
* @author Udaltsov Valentin <udaltsov.valentin@gmail.com>
*/
class DoctrineWithEmbedded
{
/**
* @Id
* @Column(type="smallint")
*/
public $id;
/**
* @Embedded(class="DoctrineEmbeddable")
*/
protected $embedded;
}

View File

@ -339,7 +339,7 @@
{% endif %}
{%- endfor %}
{% if not form.methodRendered %}
{% if not form.methodRendered and form.parent is null %}
{%- do form.setMethodRendered() -%}
{% set method = method|upper %}
{%- if method in ["GET", "POST"] -%}

View File

@ -71,10 +71,6 @@ class RouterDebugCommandTest extends TestCase
->will($this->returnValue($routeCollection))
;
$loader = $this->getMockBuilder('Symfony\Bundle\FrameworkBundle\Routing\DelegatingLoader')
->disableOriginalConstructor()
->getMock();
$container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock();
$container
->expects($this->once())

View File

@ -70,10 +70,6 @@ class RouterMatchCommandTest extends TestCase
->will($this->returnValue($requestContext))
;
$loader = $this->getMockBuilder('Symfony\Bundle\FrameworkBundle\Routing\DelegatingLoader')
->disableOriginalConstructor()
->getMock();
$container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock();
$container
->expects($this->atLeastOnce())

View File

@ -61,7 +61,7 @@ class ConfigDebugCommandTest extends WebTestCase
public function testDumpUndefinedBundleOption()
{
$tester = $this->createCommandTester();
$ret = $tester->execute(array('name' => 'TestBundle', 'path' => 'foo'));
$tester->execute(array('name' => 'TestBundle', 'path' => 'foo'));
$this->assertContains('Unable to find configuration for "test.foo"', $tester->getDisplay());
}

View File

@ -13,8 +13,10 @@ namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ExpressionLanguage;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\ExpressionLanguage\Expression;
/**
* Run this pass before passes that need to know more about the relation of
@ -32,6 +34,7 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements Repe
private $repeatedPass;
private $onlyConstructorArguments;
private $lazy;
private $expressionLanguage;
/**
* @param bool $onlyConstructorArguments Sets this Service Reference pass to ignore method calls
@ -79,6 +82,11 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements Repe
return $value;
}
if ($value instanceof Expression) {
$this->getExpressionLanguage()->compile((string) $value, array('this' => 'container'));
return $value;
}
if ($value instanceof Reference) {
$targetDefinition = $this->getDefinition((string) $value);
@ -143,4 +151,27 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements Repe
return $id;
}
private function getExpressionLanguage()
{
if (null === $this->expressionLanguage) {
$providers = $this->container->getExpressionLanguageProviders();
$this->expressionLanguage = new ExpressionLanguage(null, $providers, function ($arg) {
if ('""' === substr_replace($arg, '', 1, -1)) {
$id = stripcslashes(substr($arg, 1, -1));
$this->graph->connect(
$this->currentId,
$this->currentDefinition,
$this->getDefinitionId($id),
$this->getDefinition($id)
);
}
return sprintf('$this->get(%s)', $arg);
});
}
return $this->expressionLanguage;
}
}

View File

@ -1710,7 +1710,15 @@ EOF;
throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
}
$providers = $this->container->getExpressionLanguageProviders();
$this->expressionLanguage = new ExpressionLanguage(null, $providers);
$this->expressionLanguage = new ExpressionLanguage(null, $providers, function ($arg) {
$id = '""' === substr_replace($arg, '', 1, -1) ? stripcslashes(substr($arg, 1, -1)) : null;
if (null !== $id && ($this->container->hasAlias($id) || $this->container->hasDefinition($id))) {
return $this->getServiceCall($id);
}
return sprintf('$this->get(%s)', $arg);
});
if ($this->container->isTrackingResources()) {
foreach ($providers as $provider) {

View File

@ -25,10 +25,10 @@ class ExpressionLanguage extends BaseExpressionLanguage
/**
* {@inheritdoc}
*/
public function __construct($cache = null, array $providers = array())
public function __construct($cache = null, array $providers = array(), callable $serviceCompiler = null)
{
// prepend the default provider to let users override it easily
array_unshift($providers, new ExpressionLanguageProvider());
array_unshift($providers, new ExpressionLanguageProvider($serviceCompiler));
parent::__construct($cache, $providers);
}

View File

@ -24,10 +24,17 @@ use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
*/
class ExpressionLanguageProvider implements ExpressionFunctionProviderInterface
{
private $serviceCompiler;
public function __construct(callable $serviceCompiler = null)
{
$this->serviceCompiler = $serviceCompiler;
}
public function getFunctions()
{
return array(
new ExpressionFunction('service', function ($arg) {
new ExpressionFunction('service', $this->serviceCompiler ?: function ($arg) {
return sprintf('$this->get(%s)', $arg);
}, function (array $variables, $value) {
return $variables['container']->get($value);

View File

@ -138,7 +138,7 @@ class PhpDumperTest extends TestCase
{
$container = include self::$fixturesPath.'/containers/container9.php';
$dumper = new PhpDumper($container);
$this->assertEquals(str_replace('%path%', str_replace('\\', '\\\\', self::$fixturesPath.DIRECTORY_SEPARATOR.'includes'.DIRECTORY_SEPARATOR), file_get_contents(self::$fixturesPath.'/php/services9.php')), $dumper->dump(), '->dump() dumps services');
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services9.php', str_replace(str_replace('\\', '\\\\', self::$fixturesPath.DIRECTORY_SEPARATOR.'includes'.DIRECTORY_SEPARATOR), '%path%', $dumper->dump()), '->dump() dumps services');
}
public function testAddService()
@ -146,7 +146,7 @@ class PhpDumperTest extends TestCase
$container = include self::$fixturesPath.'/containers/container9.php';
$container->compile();
$dumper = new PhpDumper($container);
$this->assertEquals(str_replace('%path%', str_replace('\\', '\\\\', self::$fixturesPath.DIRECTORY_SEPARATOR.'includes'.DIRECTORY_SEPARATOR), file_get_contents(self::$fixturesPath.'/php/services9_compiled.php')), $dumper->dump(), '->dump() dumps services');
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services9_compiled.php', str_replace(str_replace('\\', '\\\\', self::$fixturesPath.DIRECTORY_SEPARATOR.'includes'.DIRECTORY_SEPARATOR), '%path%', $dumper->dump()), '->dump() dumps services');
$container = new ContainerBuilder();
$container->register('foo', 'FooClass')->addArgument(new \stdClass());
@ -585,6 +585,22 @@ class PhpDumperTest extends TestCase
$this->assertInstanceOf('BazClass', $container->get('bar')->getBaz());
}
public function testExpressionReferencingPrivateService()
{
$container = new ContainerBuilder();
$container->register('private_bar', 'stdClass')
->setPublic(false);
$container->register('private_foo', 'stdClass')
->setPublic(false);
$container->register('public_foo', 'stdClass')
->addArgument(new Expression('service("private_foo")'));
$container->compile();
$dumper = new PhpDumper($container);
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services_private_in_expression.php', $dumper->dump());
}
public function testDumpHandlesLiteralClassWithRootNamespace()
{
$container = new ContainerBuilder();

View File

@ -356,7 +356,7 @@ class ProjectServiceContainer extends Container
if ($this->has('foobaz')) {
$instance->setBar($this->get('foobaz', ContainerInterface::NULL_ON_INVALID_REFERENCE));
}
$instance->setBar(($this->get("foo")->foo() . (($this->hasParameter("foo")) ? ($this->getParameter("foo")) : ("default"))));
$instance->setBar((${($_ = isset($this->services['foo']) ? $this->services['foo'] : $this->get('foo')) && false ?: '_'}->foo() . (($this->hasParameter("foo")) ? ($this->getParameter("foo")) : ("default"))));
return $instance;
}

View File

@ -348,7 +348,7 @@ class ProjectServiceContainer extends Container
$instance->setBar(${($_ = isset($this->services['foo']) ? $this->services['foo'] : $this->get('foo')) && false ?: '_'});
$instance->setBar(NULL);
$instance->setBar(($this->get("foo")->foo() . (($this->hasParameter("foo")) ? ($this->getParameter("foo")) : ("default"))));
$instance->setBar((${($_ = isset($this->services['foo']) ? $this->services['foo'] : $this->get('foo')) && false ?: '_'}->foo() . (($this->hasParameter("foo")) ? ($this->getParameter("foo")) : ("default"))));
return $instance;
}

View File

@ -0,0 +1,96 @@
<?php
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
/**
* ProjectServiceContainer.
*
* This class has been auto-generated
* by the Symfony Dependency Injection Component.
*
* @final since Symfony 3.3
*/
class ProjectServiceContainer extends Container
{
private $parameters;
private $targetDirs = array();
/**
* Constructor.
*/
public function __construct()
{
$this->services = array();
$this->methodMap = array(
'private_foo' => 'getPrivateFooService',
'public_foo' => 'getPublicFooService',
);
$this->privates = array(
'private_foo' => true,
);
$this->aliases = array();
}
/**
* {@inheritdoc}
*/
public function compile()
{
throw new LogicException('You cannot compile a dumped container that was already compiled.');
}
/**
* {@inheritdoc}
*/
public function isCompiled()
{
return true;
}
/**
* {@inheritdoc}
*/
public function isFrozen()
{
@trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0. Use the isCompiled() method instead.', __METHOD__), E_USER_DEPRECATED);
return true;
}
/**
* Gets the 'public_foo' service.
*
* This service is shared.
* This method always returns the same instance of the service.
*
* @return \stdClass A stdClass instance
*/
protected function getPublicFooService()
{
return $this->services['public_foo'] = new \stdClass(${($_ = isset($this->services['private_foo']) ? $this->services['private_foo'] : $this->getPrivateFooService()) && false ?: '_'});
}
/**
* Gets the 'private_foo' service.
*
* This service is shared.
* This method always returns the same instance of the service.
*
* This service is private.
* If you want to be able to request this service from the container directly,
* make it public, otherwise you might end up with broken code.
*
* @return \stdClass A stdClass instance
*/
protected function getPrivateFooService()
{
return $this->services['private_foo'] = new \stdClass();
}
}

View File

@ -72,7 +72,7 @@ class TimezoneType extends AbstractType implements ChoiceLoaderInterface
return $this->choiceList;
}
return $this->choiceList = new ArrayChoiceList($this->getTimezones(), $value);
return $this->choiceList = new ArrayChoiceList(self::getTimezones(), $value);
}
/**

View File

@ -151,8 +151,6 @@ class ValidatorTypeGuesser implements FormTypeGuesserInterface
case 'Symfony\Component\Validator\Constraints\Count':
return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\CollectionType', array(), Guess::LOW_CONFIDENCE);
case 'Symfony\Component\Validator\Constraints\True':
case 'Symfony\Component\Validator\Constraints\False':
case 'Symfony\Component\Validator\Constraints\IsTrue':
case 'Symfony\Component\Validator\Constraints\IsFalse':
return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\CheckboxType', array(), Guess::MEDIUM_CONFIDENCE);
@ -171,7 +169,6 @@ class ValidatorTypeGuesser implements FormTypeGuesserInterface
switch (get_class($constraint)) {
case 'Symfony\Component\Validator\Constraints\NotNull':
case 'Symfony\Component\Validator\Constraints\NotBlank':
case 'Symfony\Component\Validator\Constraints\True':
case 'Symfony\Component\Validator\Constraints\IsTrue':
return new ValueGuess(true, Guess::HIGH_CONFIDENCE);
}

View File

@ -33,7 +33,7 @@ class IbanValidator extends ConstraintValidator
* a BBAN (Basic Bank Account Number) which has a fixed length per country and,
* included within it, a bank identifier with a fixed position and a fixed length per country
*
* @see http://www.swift.com/dsp/resources/documents/IBAN_Registry.pdf
* @see https://www.swift.com/sites/default/files/resources/iban_registry.pdf
*
* @var array
*/
@ -129,7 +129,7 @@ class IbanValidator extends ConstraintValidator
'TL' => 'TL\d{2}\d{3}\d{14}\d{2}', // Timor-Leste
'TN' => 'TN59\d{2}\d{3}\d{13}\d{2}', // Tunisia
'TR' => 'TR\d{2}\d{5}[\dA-Z]{1}[\dA-Z]{16}', // Turkey
'UA' => 'UA\d{2}[A-Z]{6}[\dA-Z]{19}', // Ukraine
'UA' => 'UA\d{2}\d{6}[\dA-Z]{19}', // Ukraine
'VG' => 'VG\d{2}[A-Z]{4}\d{16}', // Virgin Islands, British
'WF' => 'FR\d{2}\d{5}\d{5}[\dA-Z]{11}\d{2}', // Wallis and Futuna Islands
'XK' => 'XK\d{2}\d{4}\d{10}\d{2}', // Republic of Kosovo

View File

@ -113,7 +113,7 @@ class IbanValidatorTest extends ConstraintValidatorTestCase
//Extended country list
//http://www.nordea.com/Our+services/International+products+and+services/Cash+Management/IBAN+countries/908462.html
// http://www.swift.com/dsp/resources/documents/IBAN_Registry.pdf
// https://www.swift.com/sites/default/files/resources/iban_registry.pdf
array('AO06000600000100037131174'), //Angola
array('AZ21NABZ00000000137010001944'), //Azerbaijan
array('BH29BMAG1299123456BH00'), //Bahrain
@ -151,6 +151,7 @@ class IbanValidatorTest extends ConstraintValidatorTestCase
array('TL380080012345678910157'), //Timor-Leste
array('TN5914207207100707129648'), //Tunisia
array('TR330006100519786457841326'), //Turkey
array('UA213223130000026007233566001'), //Ukraine
array('AE260211000000230064016'), //United Arab Emirates
);
}
@ -263,6 +264,7 @@ class IbanValidatorTest extends ConstraintValidatorTestCase
array('TL3800800123456789101571'), //Timor-Leste
array('TN59142072071007071296481'), //Tunisia
array('TR3300061005197864578413261'), //Turkey
array('UA21AAAA1300000260072335660012'), //Ukraine
array('AE2602110000002300640161'), //United Arab Emirates
);
}
@ -372,6 +374,7 @@ class IbanValidatorTest extends ConstraintValidatorTestCase
array('TL380080012345678910158'), //Timor-Leste
array('TN5914207207100707129649'), //Tunisia
array('TR330006100519786457841327'), //Turkey
array('UA213223130000026007233566002'), //Ukraine
array('AE260211000000230064017'), //United Arab Emirates
);
}

View File

@ -70,7 +70,7 @@ class Caster
$i = 0;
$prefixedKeys = array();
foreach ($a as $k => $v) {
if (isset($k[0]) && "\0" !== $k[0]) {
if (isset($k[0]) ? "\0" !== $k[0] : \PHP_VERSION_ID >= 70200) {
if (!isset($publicProperties[$class])) {
foreach (get_class_vars($class) as $prop => $v) {
$publicProperties[$class][$prop] = true;

View File

@ -233,7 +233,7 @@ object(Symfony\Component\VarDumper\Cloner\Data)#%i (6) {
EOTXT;
ob_start();
var_dump($clone);
$this->assertStringMatchesFormat($expected, ob_get_clean());
$this->assertStringMatchesFormat(\PHP_VERSION_ID >= 70200 ? str_replace('"1"', '1', $expected) : $expected, ob_get_clean());
}
public function testCaster()

View File

@ -200,8 +200,22 @@ EOTXT
$var[] = &$v;
$var[''] = 2;
$this->assertDumpMatchesFormat(
<<<'EOTXT'
if (\PHP_VERSION_ID >= 70200) {
$this->assertDumpMatchesFormat(
<<<'EOTXT'
array:4 [
0 => {}
1 => &1 null
2 => &1 null
"" => 2
]
EOTXT
,
$var
);
} else {
$this->assertDumpMatchesFormat(
<<<'EOTXT'
array:4 [
"0" => {}
"1" => &1 null
@ -209,9 +223,10 @@ array:4 [
"" => 2
]
EOTXT
,
$var
);
,
$var
);
}
}
public function testObjectCast()
@ -219,16 +234,28 @@ EOTXT
$var = (object) array(1 => 1);
$var->{1} = 2;
$this->assertDumpMatchesFormat(
<<<'EOTXT'
if (\PHP_VERSION_ID >= 70200) {
$this->assertDumpMatchesFormat(
<<<'EOTXT'
{
+"1": 2
}
EOTXT
,
$var
);
} else {
$this->assertDumpMatchesFormat(
<<<'EOTXT'
{
+1: 1
+"1": 2
}
EOTXT
,
$var
);
,
$var
);
}
}
public function testClosedResource()