Merge branch '3.4' into 4.4

* 3.4:
  [Yaml] Fix for #36624; Allow PHP constant as first key in block
  Use PHPUnit 9.3 on php 8.
  fix mapping errors from unmapped forms
  [Validator] Add target guards for Composite nested constraints
This commit is contained in:
Fabien Potencier 2020-08-13 08:22:32 +02:00
commit a379051c82
11 changed files with 215 additions and 25 deletions

View File

@ -12,7 +12,11 @@ if (!getenv('SYMFONY_PHPUNIT_VERSION')) {
if (false === getenv('SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT') && false !== strpos(@file_get_contents(__DIR__.'/src/Symfony/Component/HttpKernel/Kernel.php'), 'const MAJOR_VERSION = 3;')) {
putenv('SYMFONY_PHPUNIT_REMOVE_RETURN_TYPEHINT=1');
}
putenv('SYMFONY_PHPUNIT_VERSION=8.3');
if (\PHP_VERSION_ID >= 80000) {
putenv('SYMFONY_PHPUNIT_VERSION=9.3');
} else {
putenv('SYMFONY_PHPUNIT_VERSION=8.3');
}
} elseif (\PHP_VERSION_ID >= 70000) {
putenv('SYMFONY_PHPUNIT_VERSION=6.5');
}

View File

@ -231,13 +231,6 @@ class ViolationMapper implements ViolationMapperInterface
// Form inherits its parent data
// Cut the piece out of the property path and proceed
$propertyPathBuilder->remove($i);
} elseif (!$scope->getConfig()->getMapped()) {
// Form is not mapped
// Set the form as new origin and strip everything
// we have so far in the path
$origin = $scope;
$propertyPathBuilder->remove(0, $i + 1);
$i = 0;
} else {
/* @var \Symfony\Component\PropertyAccess\PropertyPathInterface $propertyPath */
$propertyPath = $scope->getPropertyPath();

View File

@ -0,0 +1,16 @@
<?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\Component\Form\Tests\Extension\Validator\ViolationMapper\Fixtures;
class Issue
{
}

View File

@ -22,6 +22,7 @@ use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormConfigBuilder;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\Tests\Extension\Validator\ViolationMapper\Fixtures\Issue;
use Symfony\Component\PropertyAccess\PropertyPath;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationInterface;
@ -70,12 +71,12 @@ class ViolationMapperTest extends TestCase
$this->params = ['foo' => 'bar'];
}
protected function getForm($name = 'name', $propertyPath = null, $dataClass = null, $errorMapping = [], $inheritData = false, $synchronized = true)
protected function getForm($name = 'name', $propertyPath = null, $dataClass = null, $errorMapping = [], $inheritData = false, $synchronized = true, array $options = [])
{
$config = new FormConfigBuilder($name, $dataClass, $this->dispatcher, [
'error_mapping' => $errorMapping,
]);
$config->setMapped(true);
] + $options);
$config->setMapped(isset($options['mapped']) ? $options['mapped'] : true);
$config->setInheritData($inheritData);
$config->setPropertyPath($propertyPath);
$config->setCompound(true);
@ -91,12 +92,9 @@ class ViolationMapperTest extends TestCase
return new Form($config);
}
/**
* @param $propertyPath
*/
protected function getConstraintViolation($propertyPath): ConstraintViolation
protected function getConstraintViolation($propertyPath, $root = null): ConstraintViolation
{
return new ConstraintViolation($this->message, $this->messageTemplate, $this->params, null, $propertyPath, null);
return new ConstraintViolation($this->message, $this->messageTemplate, $this->params, $root, $propertyPath, null);
}
protected function getFormError(ConstraintViolationInterface $violation, FormInterface $form): FormError
@ -107,6 +105,33 @@ class ViolationMapperTest extends TestCase
return $error;
}
public function testMappingErrorsWhenFormIsNotMapped()
{
$form = $this->getForm('name', null, Issue::class, [
'child1' => 'child2',
]);
$violation = $this->getConstraintViolation('children[child1].data', $form);
$child1Form = $this->getForm('child1', null, null, [], false, true, [
'mapped' => false,
]);
$child2Form = $this->getForm('child2', null, null, [], false, true, [
'mapped' => false,
]);
$form->add($child1Form);
$form->add($child2Form);
$form->submit([]);
$this->mapper->mapViolation($violation, $form);
$this->assertCount(0, $form->getErrors());
$this->assertCount(0, $form->get('child1')->getErrors());
$this->assertCount(1, $form->get('child2')->getErrors());
}
public function testMapToFormInheritingParentDataIfDataDoesNotMatch()
{
$violation = $this->getConstraintViolation('children[address].data.foo');

View File

@ -136,6 +136,17 @@ abstract class Composite extends Constraint
*/
abstract protected function getCompositeOption();
/**
* @internal Used by metadata
*
* @return Constraint[]
*/
public function getNestedContraints()
{
/* @var Constraint[] $nestedConstraints */
return $this->{$this->getCompositeOption()};
}
/**
* Initializes the nested constraints.
*

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Validator\Mapping;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\Composite;
use Symfony\Component\Validator\Constraints\GroupSequence;
use Symfony\Component\Validator\Constraints\Traverse;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
@ -173,9 +174,7 @@ class ClassMetadata extends GenericMetadata implements ClassMetadataInterface
*/
public function addConstraint(Constraint $constraint)
{
if (!\in_array(Constraint::CLASS_CONSTRAINT, (array) $constraint->getTargets())) {
throw new ConstraintDefinitionException(sprintf('The constraint "%s" cannot be put on classes.', \get_class($constraint)));
}
$this->checkConstraint($constraint);
if ($constraint instanceof Traverse) {
if ($constraint->traverse) {
@ -487,4 +486,17 @@ class ClassMetadata extends GenericMetadata implements ClassMetadataInterface
$this->members[$property][] = $metadata;
}
private function checkConstraint(Constraint $constraint)
{
if (!\in_array(Constraint::CLASS_CONSTRAINT, (array) $constraint->getTargets(), true)) {
throw new ConstraintDefinitionException(sprintf('The constraint "%s" cannot be put on classes.', \get_class($constraint)));
}
if ($constraint instanceof Composite) {
foreach ($constraint->getNestedContraints() as $nestedContraint) {
$this->checkConstraint($nestedContraint);
}
}
}
}

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\Validator\Mapping;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\Composite;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
/**
@ -71,9 +72,7 @@ abstract class MemberMetadata extends GenericMetadata implements PropertyMetadat
*/
public function addConstraint(Constraint $constraint)
{
if (!\in_array(Constraint::PROPERTY_CONSTRAINT, (array) $constraint->getTargets())) {
throw new ConstraintDefinitionException(sprintf('The constraint "%s" cannot be put on properties or getters.', \get_class($constraint)));
}
$this->checkConstraint($constraint);
parent::addConstraint($constraint);
@ -181,4 +180,17 @@ abstract class MemberMetadata extends GenericMetadata implements PropertyMetadat
* @return \ReflectionMethod|\ReflectionProperty The reflection instance
*/
abstract protected function newReflectionMember($objectOrClassName);
private function checkConstraint(Constraint $constraint)
{
if (!\in_array(Constraint::PROPERTY_CONSTRAINT, (array) $constraint->getTargets(), true)) {
throw new ConstraintDefinitionException(sprintf('The constraint "%s" cannot be put on properties or getters.', \get_class($constraint)));
}
if ($constraint instanceof Composite) {
foreach ($constraint->getNestedContraints() as $nestedContraint) {
$this->checkConstraint($nestedContraint);
}
}
}
}

View File

@ -13,8 +13,11 @@ namespace Symfony\Component\Validator\Tests\Mapping;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\Composite;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Tests\Fixtures\ClassConstraint;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintB;
use Symfony\Component\Validator\Tests\Fixtures\PropertyConstraint;
@ -52,6 +55,20 @@ class ClassMetadataTest extends TestCase
$this->metadata->addConstraint(new PropertyConstraint());
}
public function testAddCompositeConstraintRejectsNestedPropertyConstraints()
{
$this->expectException(ConstraintDefinitionException::class);
$this->expectExceptionMessage('The constraint "Symfony\Component\Validator\Tests\Fixtures\PropertyConstraint" cannot be put on classes.');
$this->metadata->addConstraint(new ClassCompositeConstraint([new PropertyConstraint()]));
}
public function testAddCompositeConstraintAcceptsNestedClassConstraints()
{
$this->metadata->addConstraint($constraint = new ClassCompositeConstraint([new ClassConstraint()]));
$this->assertSame($this->metadata->getConstraints(), [$constraint]);
}
public function testAddPropertyConstraints()
{
$this->metadata->addPropertyConstraint('firstName', new ConstraintA());
@ -311,3 +328,23 @@ class ClassMetadataTest extends TestCase
$this->assertCount(0, $this->metadata->getPropertyMetadata('foo'), '->getPropertyMetadata() returns an empty collection if no metadata is configured for the given property');
}
}
class ClassCompositeConstraint extends Composite
{
public $nested;
public function getDefaultOption()
{
return $this->getCompositeOption();
}
protected function getCompositeOption()
{
return 'nested';
}
public function getTargets()
{
return [self::CLASS_CONSTRAINT];
}
}

View File

@ -12,11 +12,16 @@
namespace Symfony\Component\Validator\Tests\Mapping;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\Constraints\Collection;
use Symfony\Component\Validator\Constraints\Composite;
use Symfony\Component\Validator\Constraints\Required;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Mapping\MemberMetadata;
use Symfony\Component\Validator\Tests\Fixtures\ClassConstraint;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintB;
use Symfony\Component\Validator\Tests\Fixtures\PropertyConstraint;
class MemberMetadataTest extends TestCase
{
@ -43,6 +48,34 @@ class MemberMetadataTest extends TestCase
$this->metadata->addConstraint(new ClassConstraint());
}
public function testAddCompositeConstraintRejectsNestedClassConstraints()
{
$this->expectException(ConstraintDefinitionException::class);
$this->expectExceptionMessage('The constraint "Symfony\Component\Validator\Tests\Fixtures\ClassConstraint" cannot be put on properties or getters.');
$this->metadata->addConstraint(new PropertyCompositeConstraint([new ClassConstraint()]));
}
public function testAddCompositeConstraintRejectsDeepNestedClassConstraints()
{
$this->expectException(ConstraintDefinitionException::class);
$this->expectExceptionMessage('The constraint "Symfony\Component\Validator\Tests\Fixtures\ClassConstraint" cannot be put on properties or getters.');
$this->metadata->addConstraint(new Collection(['field1' => new Required([new ClassConstraint()])]));
}
public function testAddCompositeConstraintAcceptsNestedPropertyConstraints()
{
$this->metadata->addConstraint($constraint = new PropertyCompositeConstraint([new PropertyConstraint()]));
$this->assertSame($this->metadata->getConstraints(), [$constraint]);
}
public function testAddCompositeConstraintAcceptsDeepNestedPropertyConstraints()
{
$this->metadata->addConstraint($constraint = new Collection(['field1' => new Required([new PropertyConstraint()])]));
$this->assertSame($this->metadata->getConstraints(), [$constraint]);
}
public function testSerialize()
{
$this->metadata->addConstraint(new ConstraintA(['property1' => 'A']));
@ -82,3 +115,18 @@ class TestMemberMetadata extends MemberMetadata
{
}
}
class PropertyCompositeConstraint extends Composite
{
public $nested;
public function getDefaultOption()
{
return $this->getCompositeOption();
}
protected function getCompositeOption()
{
return 'nested';
}
}

View File

@ -176,8 +176,12 @@ class Parser
$this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $flags)
);
} else {
if (isset($values['leadspaces'])
&& self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+?))?\s*$#u', $this->trimTag($values['value']), $matches)
if (
isset($values['leadspaces'])
&& (
'!' === $values['value'][0]
|| self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+?))?\s*$#u', $this->trimTag($values['value']), $matches)
)
) {
// this is a compact notation element, add to next block and parse
$block = $values['value'];

View File

@ -2077,6 +2077,34 @@ YAML;
$this->parser->parse('!php/const:App\Kernel::SEMART_VERSION', Yaml::PARSE_CUSTOM_TAGS | Yaml::PARSE_CONSTANT);
}
public function testPhpConstantTagMappingAsScalarKey()
{
$yaml = <<<YAML
map1:
- foo: 'value_0'
!php/const 'Symfony\Component\Yaml\Tests\B::BAR': 'value_1'
map2:
- !php/const 'Symfony\Component\Yaml\Tests\B::FOO': 'value_0'
bar: 'value_1'
YAML;
$this->assertSame([
'map1' => [['foo' => 'value_0', 'bar' => 'value_1']],
'map2' => [['foo' => 'value_0', 'bar' => 'value_1']],
], $this->parser->parse($yaml, Yaml::PARSE_CONSTANT));
}
public function testTagMappingAsScalarKey()
{
$yaml = <<<YAML
map1:
- !!str 0: 'value_0'
!!str 1: 'value_1'
YAML;
$this->assertSame([
'map1' => [['0' => 'value_0', '1' => 'value_1']],
], $this->parser->parse($yaml));
}
public function testMergeKeysWhenMappingsAreParsedAsObjects()
{
$yaml = <<<YAML
@ -2339,7 +2367,7 @@ YAML;
parameters:
abc
# Comment
# Comment
YAML;
$this->assertSame(['parameters' => 'abc'], $this->parser->parse($yaml));