diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 8da21ec1213f2..e5e58745ee337 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -4,7 +4,7 @@
| Bug fix? | yes/no
| New feature? | yes/no
| Deprecations? | yes/no
-| Tickets | Fix #...
+| Tickets | Fix #...
| License | MIT
| Doc PR | symfony/symfony-docs#...
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php
index 690d04f318a88..a7bca6e521977 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php
@@ -236,6 +236,25 @@ public function testTypeNotGuessableNoServicesFound()
}
}
+ /**
+ * @requires PHP 8
+ */
+ public function testTypeNotGuessableUnionType()
+ {
+ $this->expectException('Symfony\Component\DependencyInjection\Exception\AutowiringFailedException');
+ $this->expectExceptionMessage('Cannot autowire service "a": argument "$collision" of method "Symfony\Component\DependencyInjection\Tests\Compiler\UnionClasses::__construct()" has type "Symfony\Component\DependencyInjection\Tests\Compiler\CollisionA|Symfony\Component\DependencyInjection\Tests\Compiler\CollisionB" but this class was not found.');
+ $container = new ContainerBuilder();
+
+ $container->register(CollisionA::class);
+ $container->register(CollisionB::class);
+
+ $aDefinition = $container->register('a', UnionClasses::class);
+ $aDefinition->setAutowired(true);
+
+ $pass = new AutowirePass();
+ $pass->process($container);
+ }
+
public function testTypeNotGuessableWithTypeSet()
{
$container = new ContainerBuilder();
@@ -319,6 +338,40 @@ public function testOptionalParameter()
$this->assertEquals(Foo::class, $definition->getArgument(2));
}
+ /**
+ * @requires PHP 8
+ */
+ public function testParameterWithNullUnionIsSkipped()
+ {
+ $container = new ContainerBuilder();
+
+ $optDefinition = $container->register('opt', UnionNull::class);
+ $optDefinition->setAutowired(true);
+
+ (new AutowirePass())->process($container);
+
+ $definition = $container->getDefinition('opt');
+ $this->assertNull($definition->getArgument(0));
+ }
+
+ /**
+ * @requires PHP 8
+ */
+ public function testParameterWithNullUnionIsAutowired()
+ {
+ $container = new ContainerBuilder();
+
+ $container->register(CollisionInterface::class, CollisionA::class);
+
+ $optDefinition = $container->register('opt', UnionNull::class);
+ $optDefinition->setAutowired(true);
+
+ (new AutowirePass())->process($container);
+
+ $definition = $container->getDefinition('opt');
+ $this->assertEquals(CollisionInterface::class, $definition->getArgument(0));
+ }
+
public function testDontTriggerAutowiring()
{
$container = new ContainerBuilder();
@@ -435,6 +488,21 @@ public function testScalarArgsCannotBeAutowired()
}
}
+ /**
+ * @requires PHP 8
+ */
+ public function testUnionScalarArgsCannotBeAutowired()
+ {
+ $this->expectException('Symfony\Component\DependencyInjection\Exception\AutowiringFailedException');
+ $this->expectExceptionMessage('Cannot autowire service "union_scalars": argument "$timeout" of method "Symfony\Component\DependencyInjection\Tests\Compiler\UnionScalars::__construct()" is type-hinted "int|float", you should configure its value explicitly.');
+ $container = new ContainerBuilder();
+
+ $container->register('union_scalars', UnionScalars::class)
+ ->setAutowired(true);
+
+ (new AutowirePass())->process($container);
+ }
+
public function testNoTypeArgsCannotBeAutowired()
{
$container = new ContainerBuilder();
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/MergeExtensionConfigurationPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/MergeExtensionConfigurationPassTest.php
index d5db48cc30645..a2cb3bda5ed96 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/MergeExtensionConfigurationPassTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/MergeExtensionConfigurationPassTest.php
@@ -12,6 +12,7 @@
namespace Symfony\Component\DependencyInjection\Tests\Compiler;
use PHPUnit\Framework\TestCase;
+use Symfony\Component\Config\Definition\BaseNode;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Resource\FileResource;
@@ -128,6 +129,23 @@ public function testThrowingExtensionsGetMergedBag()
$this->assertSame(['FOO'], array_keys($container->getParameterBag()->getEnvPlaceholders()));
}
+
+ public function testReuseEnvPlaceholderGeneratedByPreviousExtension()
+ {
+ if (!property_exists(BaseNode::class, 'placeholderUniquePrefixes')) {
+ $this->markTestSkipped('This test requires symfony/config ^4.4.11|^5.0.11|^5.1.3');
+ }
+
+ $container = new ContainerBuilder();
+ $container->registerExtension(new FooExtension());
+ $container->registerExtension(new TestCccExtension());
+ $container->prependExtensionConfig('foo', ['bool_node' => '%env(bool:MY_ENV_VAR)%']);
+ $container->prependExtensionConfig('test_ccc', ['bool_node' => '%env(bool:MY_ENV_VAR)%']);
+
+ (new MergeExtensionConfigurationPass())->process($container);
+
+ $this->addToAssertionCount(1);
+ }
}
class FooConfiguration implements ConfigurationInterface
@@ -139,6 +157,7 @@ public function getConfigTreeBuilder(): TreeBuilder
->children()
->scalarNode('bar')->end()
->scalarNode('baz')->end()
+ ->booleanNode('bool_node')->end()
->end();
return $treeBuilder;
@@ -166,6 +185,8 @@ public function load(array $configs, ContainerBuilder $container)
$container->getParameterBag()->get('env(BOZ)');
$container->resolveEnvPlaceholders($config['baz']);
}
+
+ $container->setParameter('foo.param', 'ccc');
}
}
@@ -194,3 +215,36 @@ public function load(array $configs, ContainerBuilder $container)
throw new \Exception();
}
}
+
+final class TestCccConfiguration implements ConfigurationInterface
+{
+ public function getConfigTreeBuilder(): TreeBuilder
+ {
+ $treeBuilder = new TreeBuilder('test_ccc');
+ $treeBuilder->getRootNode()
+ ->children()
+ ->booleanNode('bool_node')->end()
+ ->end();
+
+ return $treeBuilder;
+ }
+}
+
+final class TestCccExtension extends Extension
+{
+ public function getAlias(): string
+ {
+ return 'test_ccc';
+ }
+
+ public function getConfiguration(array $config, ContainerBuilder $container): ?ConfigurationInterface
+ {
+ return new TestCccConfiguration();
+ }
+
+ public function load(array $configs, ContainerBuilder $container)
+ {
+ $configuration = $this->getConfiguration($configs, $container);
+ $this->processConfiguration($configuration, $configs);
+ }
+}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php
index abb9a9c2ed366..ba664f767baf1 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php
@@ -879,12 +879,12 @@ public function testfindTaggedServiceIds()
->addTag('bar', ['bar' => 'bar'])
->addTag('foo', ['foofoo' => 'foofoo'])
;
- $this->assertEquals($builder->findTaggedServiceIds('foo'), [
+ $this->assertEquals([
'foo' => [
['foo' => 'foo'],
['foofoo' => 'foofoo'],
],
- ], '->findTaggedServiceIds() returns an array of service ids and its tag attributes');
+ ], $builder->findTaggedServiceIds('foo'), '->findTaggedServiceIds() returns an array of service ids and its tag attributes');
$this->assertEquals([], $builder->findTaggedServiceIds('foobar'), '->findTaggedServiceIds() returns an empty array if there is annotated services');
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/DefinitionTest.php b/src/Symfony/Component/DependencyInjection/Tests/DefinitionTest.php
index f67cdd520e709..f5b3a9166be9a 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/DefinitionTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/DefinitionTest.php
@@ -280,10 +280,10 @@ public function testTags()
$def->addTag('foo', ['foo' => 'bar']);
$this->assertEquals([[], ['foo' => 'bar']], $def->getTag('foo'), '->addTag() can adds the same tag several times');
$def->addTag('bar', ['bar' => 'bar']);
- $this->assertEquals($def->getTags(), [
+ $this->assertEquals([
'foo' => [[], ['foo' => 'bar']],
'bar' => [['bar' => 'bar']],
- ], '->getTags() returns all tags');
+ ], $def->getTags(), '->getTags() returns all tags');
}
public function testSetArgument()
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
index 51a88705d6fe6..58a186f009097 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
@@ -107,7 +107,7 @@ public function testDumpRelativeDir()
$container = new ContainerBuilder();
$container->setDefinition('test', $definition);
- $container->setParameter('foo', 'wiz'.\dirname(__DIR__));
+ $container->setParameter('foo', 'file://'.\dirname(__DIR__));
$container->setParameter('bar', __DIR__);
$container->setParameter('baz', '%bar%/PhpDumperTest.php');
$container->setParameter('buz', \dirname(__DIR__, 2));
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php
index bae07f83ac70d..df5c1c42b2cfc 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php
@@ -4,6 +4,10 @@
use Psr\Log\LoggerInterface;
+if (PHP_VERSION_ID >= 80000) {
+ require __DIR__.'/uniontype_classes.php';
+}
+
class Foo
{
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/uniontype_classes.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/uniontype_classes.php
new file mode 100644
index 0000000000000..3a0c77c53941c
--- /dev/null
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/uniontype_classes.php
@@ -0,0 +1,24 @@
+services['test'] = new \stdClass(('wiz'.\dirname(__DIR__, 1)), [('wiz'.\dirname(__DIR__, 1)) => (\dirname(__DIR__, 2).'/')]);
+ return $this->services['test'] = new \stdClass(('file://'.\dirname(__DIR__, 1)), [('file://'.\dirname(__DIR__, 1)) => (\dirname(__DIR__, 2).'/')]);
}
public function getParameter($name)
@@ -109,7 +109,7 @@ private function getDynamicParameter(string $name)
protected function getDefaultParameters(): array
{
return [
- 'foo' => ('wiz'.\dirname(__DIR__, 1)),
+ 'foo' => ('file://'.\dirname(__DIR__, 1)),
'bar' => __DIR__,
'baz' => (__DIR__.'/PhpDumperTest.php'),
'buz' => \dirname(__DIR__, 2),
diff --git a/src/Symfony/Component/DomCrawler/Tests/FormTest.php b/src/Symfony/Component/DomCrawler/Tests/FormTest.php
index 88987b05b5a74..db390efccada2 100644
--- a/src/Symfony/Component/DomCrawler/Tests/FormTest.php
+++ b/src/Symfony/Component/DomCrawler/Tests/FormTest.php
@@ -170,25 +170,28 @@ public function testMultiValuedFields()
');
$this->assertEquals(
- array_keys($form->all()),
- ['foo[2]', 'foo[3]', 'bar[foo][0]', 'bar[foo][foobar]']
+ ['foo[2]', 'foo[3]', 'bar[foo][0]', 'bar[foo][foobar]'],
+ array_keys($form->all())
);
- $this->assertEquals($form->get('foo[2]')->getValue(), 'foo');
- $this->assertEquals($form->get('foo[3]')->getValue(), 'foo');
- $this->assertEquals($form->get('bar[foo][0]')->getValue(), 'foo');
- $this->assertEquals($form->get('bar[foo][foobar]')->getValue(), 'foo');
+ $this->assertEquals('foo', $form->get('foo[2]')->getValue());
+ $this->assertEquals('foo', $form->get('foo[3]')->getValue());
+ $this->assertEquals('foo', $form->get('bar[foo][0]')->getValue());
+ $this->assertEquals('foo', $form->get('bar[foo][foobar]')->getValue());
$form['foo[2]'] = 'bar';
$form['foo[3]'] = 'bar';
- $this->assertEquals($form->get('foo[2]')->getValue(), 'bar');
- $this->assertEquals($form->get('foo[3]')->getValue(), 'bar');
+ $this->assertEquals('bar', $form->get('foo[2]')->getValue());
+ $this->assertEquals('bar', $form->get('foo[3]')->getValue());
$form['bar'] = ['foo' => ['0' => 'bar', 'foobar' => 'foobar']];
- $this->assertEquals($form->get('bar[foo][0]')->getValue(), 'bar');
- $this->assertEquals($form->get('bar[foo][foobar]')->getValue(), 'foobar');
+ $this->assertEquals('bar', $form->get('bar[foo][0]')->getValue());
+ $this->assertEquals(
+ 'foobar',
+ $form->get('bar[foo][foobar]')->getValue()
+ );
}
/**
@@ -979,7 +982,7 @@ public function testGetPhpValuesWithEmptyTextarea()
$nodes = $dom->getElementsByTagName('form');
$form = new Form($nodes->item(0), 'http://example.com');
- $this->assertEquals($form->getPhpValues(), ['example' => '']);
+ $this->assertEquals(['example' => ''], $form->getPhpValues());
}
public function testGetReturnTypes()
diff --git a/src/Symfony/Component/Dotenv/composer.json b/src/Symfony/Component/Dotenv/composer.json
index 5520f45397794..ec04c164914c7 100644
--- a/src/Symfony/Component/Dotenv/composer.json
+++ b/src/Symfony/Component/Dotenv/composer.json
@@ -16,7 +16,7 @@
}
],
"require": {
- "php": "^7.1.3"
+ "php": ">=7.1.3"
},
"require-dev": {
"symfony/process": "^3.4.2|^4.0|^5.0"
diff --git a/src/Symfony/Component/ErrorHandler/ErrorHandler.php b/src/Symfony/Component/ErrorHandler/ErrorHandler.php
index e02a8fc45dced..bad3d0cfddb4f 100644
--- a/src/Symfony/Component/ErrorHandler/ErrorHandler.php
+++ b/src/Symfony/Component/ErrorHandler/ErrorHandler.php
@@ -423,18 +423,6 @@ public function handleError(int $type, string $message, string $file, int $line)
}
$scope = $this->scopedErrors & $type;
- if (4 < $numArgs = \func_num_args()) {
- $context = $scope ? (func_get_arg(4) ?: []) : [];
- } else {
- $context = [];
- }
-
- if (isset($context['GLOBALS']) && $scope) {
- $e = $context; // Whatever the signature of the method,
- unset($e['GLOBALS'], $context); // $context is always a reference in 5.3
- $context = $e;
- }
-
if (false !== strpos($message, "@anonymous\0")) {
$logMessage = $this->parseAnonymousClass($message);
} else {
@@ -496,6 +484,8 @@ public function handleError(int $type, string $message, string $file, int $line)
// `return trigger_error($e, E_USER_ERROR);` allows this error handler
// to make $e get through the __toString() barrier.
+ $context = 4 < \func_num_args() ? (func_get_arg(4) ?: []) : [];
+
foreach ($context as $e) {
if ($e instanceof \Throwable && $e->__toString() === $message) {
self::$toStringException = $e;
diff --git a/src/Symfony/Component/ErrorHandler/Tests/Exception/FlattenExceptionTest.php b/src/Symfony/Component/ErrorHandler/Tests/Exception/FlattenExceptionTest.php
index 55fd4de29f57d..2b1d181b58d64 100644
--- a/src/Symfony/Component/ErrorHandler/Tests/Exception/FlattenExceptionTest.php
+++ b/src/Symfony/Component/ErrorHandler/Tests/Exception/FlattenExceptionTest.php
@@ -175,9 +175,9 @@ public function testPreviousError()
$flattened = FlattenException::createFromThrowable($exception)->getPrevious();
- $this->assertEquals($flattened->getMessage(), 'Oh noes!', 'The message is copied from the original exception.');
- $this->assertEquals($flattened->getCode(), 42, 'The code is copied from the original exception.');
- $this->assertEquals($flattened->getClass(), 'ParseError', 'The class is set to the class of the original exception');
+ $this->assertEquals('Oh noes!', $flattened->getMessage(), 'The message is copied from the original exception.');
+ $this->assertEquals(42, $flattened->getCode(), 'The code is copied from the original exception.');
+ $this->assertEquals('ParseError', $flattened->getClass(), 'The class is set to the class of the original exception');
}
/**
@@ -303,7 +303,7 @@ function () {},
$this->assertSame(['resource', 'stream'], $array[$i++]);
$args = $array[$i++];
- $this->assertSame($args[0], 'object');
+ $this->assertSame('object', $args[0]);
$this->assertTrue('Closure' === $args[1] || is_subclass_of($args[1], '\Closure'), 'Expect object class name to be Closure or a subclass of Closure.');
$this->assertSame(['array', [['integer', 1], ['integer', 2]]], $array[$i++]);
@@ -318,8 +318,8 @@ function () {},
$this->assertSame(['float', INF], $array[$i++]);
// assertEquals() does not like NAN values.
- $this->assertEquals($array[$i][0], 'float');
- $this->assertNan($array[$i++][1]);
+ $this->assertEquals('float', $array[$i][0]);
+ $this->assertNan($array[$i][1]);
}
public function testRecursionInArguments()
@@ -358,7 +358,7 @@ public function testTooBigArray()
$flattened = FlattenException::createFromThrowable($exception);
$trace = $flattened->getTrace();
- $this->assertSame($trace[1]['args'][0], ['array', ['array', '*SKIPPED over 10000 entries*']]);
+ $this->assertSame(['array', ['array', '*SKIPPED over 10000 entries*']], $trace[1]['args'][0]);
$serializeTrace = serialize($trace);
diff --git a/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php b/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php
index 3ae1136c4c976..4b27d3b23aef4 100644
--- a/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php
+++ b/src/Symfony/Component/EventDispatcher/DependencyInjection/RegisterListenersPass.php
@@ -138,7 +138,7 @@ private function getEventFromTypeDeclaration(ContainerBuilder $container, string
|| !($r = $container->getReflectionClass($class, false))
|| !$r->hasMethod($method)
|| 1 > ($m = $r->getMethod($method))->getNumberOfParameters()
- || !($type = $m->getParameters()[0]->getType())
+ || !($type = $m->getParameters()[0]->getType()) instanceof \ReflectionNamedType
|| $type->isBuiltin()
|| Event::class === ($name = $type->getName())
|| LegacyEvent::class === $name
diff --git a/src/Symfony/Component/Finder/composer.json b/src/Symfony/Component/Finder/composer.json
index 0b1408c0dfd41..8ab56eab7f466 100644
--- a/src/Symfony/Component/Finder/composer.json
+++ b/src/Symfony/Component/Finder/composer.json
@@ -16,7 +16,7 @@
}
],
"require": {
- "php": "^7.1.3"
+ "php": ">=7.1.3"
},
"autoload": {
"psr-4": { "Symfony\\Component\\Finder\\": "" },
diff --git a/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php b/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php
index 657d9d63bec26..bc31505157f77 100644
--- a/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php
+++ b/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php
@@ -13,6 +13,8 @@
use Symfony\Component\Form\DataMapperInterface;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
+use Symfony\Component\PropertyAccess\Exception\AccessException;
+use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
@@ -46,7 +48,7 @@ public function mapDataToForms($data, $forms)
$config = $form->getConfig();
if (!$empty && null !== $propertyPath && $config->getMapped()) {
- $form->setData($this->propertyAccessor->getValue($data, $propertyPath));
+ $form->setData($this->getPropertyValue($data, $propertyPath));
} else {
$form->setData($config->getData());
}
@@ -76,16 +78,32 @@ public function mapFormsToData($forms, &$data)
$propertyValue = $form->getData();
// If the field is of type DateTimeInterface and the data is the same skip the update to
// keep the original object hash
- if ($propertyValue instanceof \DateTimeInterface && $propertyValue == $this->propertyAccessor->getValue($data, $propertyPath)) {
+ if ($propertyValue instanceof \DateTimeInterface && $propertyValue == $this->getPropertyValue($data, $propertyPath)) {
continue;
}
// If the data is identical to the value in $data, we are
// dealing with a reference
- if (!\is_object($data) || !$config->getByReference() || $propertyValue !== $this->propertyAccessor->getValue($data, $propertyPath)) {
+ if (!\is_object($data) || !$config->getByReference() || $propertyValue !== $this->getPropertyValue($data, $propertyPath)) {
$this->propertyAccessor->setValue($data, $propertyPath, $propertyValue);
}
}
}
}
+
+ private function getPropertyValue($data, $propertyPath)
+ {
+ try {
+ return $this->propertyAccessor->getValue($data, $propertyPath);
+ } catch (AccessException $e) {
+ if (!$e instanceof UninitializedPropertyException
+ // For versions without UninitializedPropertyException check the exception message
+ && (class_exists(UninitializedPropertyException::class) || false === strpos($e->getMessage(), 'You should initialize it'))
+ ) {
+ throw $e;
+ }
+
+ return null;
+ }
+ }
}
diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php
index d86ae70968388..f15aac5fde9db 100644
--- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php
+++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php
@@ -140,11 +140,11 @@ public function transform($value)
*/
public function reverseTransform($value)
{
- if (!\is_string($value)) {
+ if (null !== $value && !\is_string($value)) {
throw new TransformationFailedException('Expected a string.');
}
- if ('' === $value) {
+ if (null === $value || '' === $value) {
return null;
}
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
index 4be88149770f8..dce846f10e61e 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
@@ -368,7 +368,7 @@ private function addSubForm(FormBuilderInterface $builder, string $name, ChoiceV
'value' => $choiceView->value,
'label' => $choiceView->label,
'attr' => $choiceView->attr,
- 'translation_domain' => $options['translation_domain'],
+ 'translation_domain' => $options['choice_translation_domain'],
'block_name' => 'entry',
];
diff --git a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php
index 2ac749dfff01d..1f2dd3aa40f28 100644
--- a/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php
+++ b/src/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php
@@ -153,7 +153,10 @@ public function validate($form, Constraint $formConstraint)
foreach ($form as $child) {
if (!$child->isSynchronized()) {
$childrenSynchronized = false;
- break;
+
+ $fieldFormConstraint = new Form();
+ $this->context->setNode($this->context->getValue(), $child, $this->context->getMetadata(), $this->context->getPropertyPath());
+ $validator->atPath(sprintf('children[%s]', $child->getName()))->validate($child, $fieldFormConstraint);
}
}
diff --git a/src/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php b/src/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php
index 4e0443dd3b680..880d300fa4d97 100644
--- a/src/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php
+++ b/src/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php
@@ -97,6 +97,7 @@ public function guessTypeForConstraint(Constraint $constraint)
case 'long':
return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\IntegerType', [], Guess::MEDIUM_CONFIDENCE);
+ case \DateTime::class:
case '\DateTime':
return new TypeGuess('Symfony\Component\Form\Extension\Core\Type\DateType', [], Guess::MEDIUM_CONFIDENCE);
diff --git a/src/Symfony/Component/Form/Resources/translations/validators.de.xlf b/src/Symfony/Component/Form/Resources/translations/validators.de.xlf
index a9a183197edc6..fe4353120d256 100644
--- a/src/Symfony/Component/Form/Resources/translations/validators.de.xlf
+++ b/src/Symfony/Component/Form/Resources/translations/validators.de.xlf
@@ -14,6 +14,10 @@