From f6519ce88b7b41b019e01ac409e38bb8a7b57677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 20 Jun 2019 00:06:36 +0200 Subject: [PATCH] [Validator] Add AutoMapping constraint to enable or disable auto-validation --- .../Tests/Fixtures/DoctrineLoaderEntity.php | 6 ++ .../DoctrineLoaderNoAutoMappingEntity.php | 41 ++++++++++++++ .../Tests/Validator/DoctrineLoaderTest.php | 44 +++++++++++++-- .../Doctrine/Validator/DoctrineLoader.php | 56 +++++++++++-------- src/Symfony/Component/Validator/CHANGELOG.md | 1 + .../Constraints/DisableAutoMapping.php | 45 +++++++++++++++ .../Constraints/EnableAutoMapping.php | 45 +++++++++++++++ .../Mapping/Loader/AutoMappingTrait.php | 41 ++++++++++++++ .../Mapping/Loader/PropertyInfoLoader.php | 27 +++++++-- .../Constraints/DisableAutoMappingTest.php | 30 ++++++++++ .../Constraints/EnableAutoMappingTest.php | 30 ++++++++++ .../Fixtures/PropertyInfoLoaderEntity.php | 5 ++ .../PropertyInfoLoaderNoAutoMappingEntity.php | 29 ++++++++++ .../Mapping/Loader/PropertyInfoLoaderTest.php | 39 ++++++++++++- 14 files changed, 407 insertions(+), 32 deletions(-) create mode 100644 src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderNoAutoMappingEntity.php create mode 100644 src/Symfony/Component/Validator/Constraints/DisableAutoMapping.php create mode 100644 src/Symfony/Component/Validator/Constraints/EnableAutoMapping.php create mode 100644 src/Symfony/Component/Validator/Mapping/Loader/AutoMappingTrait.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/DisableAutoMappingTest.php create mode 100644 src/Symfony/Component/Validator/Tests/Constraints/EnableAutoMappingTest.php create mode 100644 src/Symfony/Component/Validator/Tests/Fixtures/PropertyInfoLoaderNoAutoMappingEntity.php diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEntity.php index 9a2111f2b9..06f8674e56 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEntity.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderEntity.php @@ -69,6 +69,12 @@ class DoctrineLoaderEntity extends DoctrineLoaderParentEntity /** @ORM\Column(type="simple_array", length=100) */ public $simpleArrayField = []; + /** + * @ORM\Column(length=10) + * @Assert\DisableAutoMapping + */ + public $noAutoMapping; + public static function loadValidatorMetadata(ClassMetadata $metadata): void { $allowEmptyString = property_exists(Assert\Length::class, 'allowEmptyString') ? ['allowEmptyString' => true] : []; diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderNoAutoMappingEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderNoAutoMappingEntity.php new file mode 100644 index 0000000000..0914411431 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/DoctrineLoaderNoAutoMappingEntity.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\Fixtures; + +use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Validator\Constraints as Assert; + +/** + * @ORM\Entity + * @Assert\DisableAutoMapping + * + * @author Kévin Dunglas + */ +class DoctrineLoaderNoAutoMappingEntity +{ + /** + * @ORM\Id + * @ORM\Column + */ + public $id; + + /** + * @ORM\Column(length=20, unique=true) + */ + public $maxLength; + + /** + * @Assert\EnableAutoMapping + * @ORM\Column(length=20) + */ + public $autoMappingExplicitlyEnabled; +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php index 648617d12c..2c87b870a8 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/DoctrineLoaderTest.php @@ -17,12 +17,15 @@ use Symfony\Bridge\Doctrine\Tests\Fixtures\BaseUser; use Symfony\Bridge\Doctrine\Tests\Fixtures\DoctrineLoaderEmbed; use Symfony\Bridge\Doctrine\Tests\Fixtures\DoctrineLoaderEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\DoctrineLoaderNestedEmbed; +use Symfony\Bridge\Doctrine\Tests\Fixtures\DoctrineLoaderNoAutoMappingEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\DoctrineLoaderParentEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Bridge\Doctrine\Validator\DoctrineLoader; +use Symfony\Component\Validator\Constraints\DisableAutoMapping; use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Mapping\CascadingStrategy; use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AutoMappingTrait; use Symfony\Component\Validator\Mapping\TraversalStrategy; use Symfony\Component\Validator\Tests\Fixtures\Entity; use Symfony\Component\Validator\Validation; @@ -33,12 +36,15 @@ use Symfony\Component\Validator\ValidatorBuilder; */ class DoctrineLoaderTest extends TestCase { + protected function setUp(): void + { + if (!trait_exists(AutoMappingTrait::class)) { + $this->markTestSkipped('Auto-mapping requires symfony/validation 4.4+'); + } + } + public function testLoadClassMetadata() { - if (!method_exists(ValidatorBuilder::class, 'addLoader')) { - $this->markTestSkipped('Auto-mapping requires symfony/validation 4.2+'); - } - $validator = Validation::createValidatorBuilder() ->addMethodMapping('loadValidatorMetadata') ->enableAnnotationMapping() @@ -134,6 +140,12 @@ class DoctrineLoaderTest extends TestCase $this->assertCount(1, $textFieldConstraints); $this->assertInstanceOf(Length::class, $textFieldConstraints[0]); $this->assertSame(1000, $textFieldConstraints[0]->max); + + $noAutoMappingMetadata = $classMetadata->getPropertyMetadata('noAutoMapping'); + $this->assertCount(1, $noAutoMappingMetadata); + $noAutoMappingConstraints = $noAutoMappingMetadata[0]->getConstraints(); + $this->assertCount(1, $noAutoMappingConstraints); + $this->assertInstanceOf(DisableAutoMapping::class, $noAutoMappingConstraints[0]); } public function testFieldMappingsConfiguration() @@ -180,4 +192,28 @@ class DoctrineLoaderTest extends TestCase [false, '{^'.preg_quote(Entity::class).'$}'], ]; } + + public function testClassNoAutoMapping() + { + if (!method_exists(ValidatorBuilder::class, 'addLoader')) { + $this->markTestSkipped('Auto-mapping requires symfony/validation 4.2+'); + } + + $validator = Validation::createValidatorBuilder() + ->enableAnnotationMapping() + ->addLoader(new DoctrineLoader(DoctrineTestHelper::createTestEntityManager())) + ->getValidator(); + + $classMetadata = $validator->getMetadataFor(new DoctrineLoaderNoAutoMappingEntity()); + + $classConstraints = $classMetadata->getConstraints(); + $this->assertCount(1, $classConstraints); + $this->assertInstanceOf(DisableAutoMapping::class, $classConstraints[0]); + + $maxLengthMetadata = $classMetadata->getPropertyMetadata('maxLength'); + $this->assertEmpty($maxLengthMetadata); + + $autoMappingExplicitlyEnabledMetadata = $classMetadata->getPropertyMetadata('autoMappingExplicitlyEnabled'); + $this->assertCount(2, $autoMappingExplicitlyEnabledMetadata[0]->constraints); + } } diff --git a/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php b/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php index 138b5d1cbb..7cfde6515c 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php +++ b/src/Symfony/Bridge/Doctrine/Validator/DoctrineLoader.php @@ -16,9 +16,12 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Mapping\MappingException as OrmMappingException; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; +use Symfony\Component\Validator\Constraints\DisableAutoMapping; +use Symfony\Component\Validator\Constraints\EnableAutoMapping; use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\Valid; use Symfony\Component\Validator\Mapping\ClassMetadata; +use Symfony\Component\Validator\Mapping\Loader\AutoMappingTrait; use Symfony\Component\Validator\Mapping\Loader\LoaderInterface; /** @@ -28,6 +31,8 @@ use Symfony\Component\Validator\Mapping\Loader\LoaderInterface; */ final class DoctrineLoader implements LoaderInterface { + use AutoMappingTrait; + private $entityManager; private $classValidatorRegexp; @@ -43,10 +48,6 @@ final class DoctrineLoader implements LoaderInterface public function loadClassMetadata(ClassMetadata $metadata): bool { $className = $metadata->getClassName(); - if (null !== $this->classValidatorRegexp && !preg_match($this->classValidatorRegexp, $className)) { - return false; - } - try { $doctrineMetadata = $this->entityManager->getClassMetadata($className); } catch (MappingException | OrmMappingException $exception) { @@ -57,6 +58,9 @@ final class DoctrineLoader implements LoaderInterface return false; } + $loaded = false; + $enabledForClass = $this->isAutoMappingEnabledForClass($metadata, $this->classValidatorRegexp); + /* Available keys: - type - scale @@ -69,41 +73,49 @@ final class DoctrineLoader implements LoaderInterface // Type and nullable aren't handled here, use the PropertyInfo Loader instead. foreach ($doctrineMetadata->fieldMappings as $mapping) { + $enabledForProperty = $enabledForClass; + $lengthConstraint = null; + foreach ($metadata->getPropertyMetadata($mapping['fieldName']) as $propertyMetadata) { + foreach ($propertyMetadata->getConstraints() as $constraint) { + // Enabling or disabling auto-mapping explicitly always takes precedence + if ($constraint instanceof DisableAutoMapping) { + continue 3; + } elseif ($constraint instanceof EnableAutoMapping) { + $enabledForProperty = true; + } elseif ($constraint instanceof Length) { + $lengthConstraint = $constraint; + } + } + } + + if (!$enabledForProperty) { + continue; + } + if (true === ($mapping['unique'] ?? false) && !isset($existingUniqueFields[$mapping['fieldName']])) { $metadata->addConstraint(new UniqueEntity(['fields' => $mapping['fieldName']])); + $loaded = true; } if (null === ($mapping['length'] ?? null) || !\in_array($mapping['type'], ['string', 'text'], true)) { continue; } - $constraint = $this->getLengthConstraint($metadata, $mapping['fieldName']); - if (null === $constraint) { + if (null === $lengthConstraint) { if (isset($mapping['originalClass']) && false === strpos($mapping['declaredField'], '.')) { $metadata->addPropertyConstraint($mapping['declaredField'], new Valid()); + $loaded = true; } elseif (property_exists($className, $mapping['fieldName'])) { $metadata->addPropertyConstraint($mapping['fieldName'], new Length(['max' => $mapping['length']])); + $loaded = true; } - } elseif (null === $constraint->max) { + } elseif (null === $lengthConstraint->max) { // If a Length constraint exists and no max length has been explicitly defined, set it - $constraint->max = $mapping['length']; + $lengthConstraint->max = $mapping['length']; } } - return true; - } - - private function getLengthConstraint(ClassMetadata $metadata, string $fieldName): ?Length - { - foreach ($metadata->getPropertyMetadata($fieldName) as $propertyMetadata) { - foreach ($propertyMetadata->getConstraints() as $constraint) { - if ($constraint instanceof Length) { - return $constraint; - } - } - } - - return null; + return $loaded; } private function getExistingUniqueFields(ClassMetadata $metadata): array diff --git a/src/Symfony/Component/Validator/CHANGELOG.md b/src/Symfony/Component/Validator/CHANGELOG.md index 698034722d..08260d40e6 100644 --- a/src/Symfony/Component/Validator/CHANGELOG.md +++ b/src/Symfony/Component/Validator/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 4.4.0 ----- + * added `EnableAutoMapping` and `DisableAutoMapping` constraints to enable or disable auto mapping for class or a property * using anything else than a `string` as the code of a `ConstraintViolation` is deprecated, a `string` type-hint will be added to the constructor of the `ConstraintViolation` class and to the `ConstraintViolationBuilder::setCode()` method in 5.0 diff --git a/src/Symfony/Component/Validator/Constraints/DisableAutoMapping.php b/src/Symfony/Component/Validator/Constraints/DisableAutoMapping.php new file mode 100644 index 0000000000..66f7b1e491 --- /dev/null +++ b/src/Symfony/Component/Validator/Constraints/DisableAutoMapping.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; + +/** + * Disables auto mapping. + * + * Using the annotations on a property has higher precedence than using it on a class, + * which has higher precedence than any configuration that might be defined outside the class. + * + * @Annotation + * + * @author Kévin Dunglas + */ +class DisableAutoMapping extends Constraint +{ + public function __construct($options = null) + { + if (\is_array($options) && \array_key_exists('groups', $options)) { + throw new ConstraintDefinitionException(sprintf('The option "groups" is not supported by the constraint "%s".', __CLASS__)); + } + + parent::__construct($options); + } + + /** + * {@inheritdoc} + */ + public function getTargets() + { + return [self::PROPERTY_CONSTRAINT, self::CLASS_CONSTRAINT]; + } +} diff --git a/src/Symfony/Component/Validator/Constraints/EnableAutoMapping.php b/src/Symfony/Component/Validator/Constraints/EnableAutoMapping.php new file mode 100644 index 0000000000..8c485e186e --- /dev/null +++ b/src/Symfony/Component/Validator/Constraints/EnableAutoMapping.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; + +/** + * Enables auto mapping. + * + * Using the annotations on a property has higher precedence than using it on a class, + * which has higher precedence than any configuration that might be defined outside the class. + * + * @Annotation + * + * @author Kévin Dunglas + */ +class EnableAutoMapping extends Constraint +{ + public function __construct($options = null) + { + if (\is_array($options) && \array_key_exists('groups', $options)) { + throw new ConstraintDefinitionException(sprintf('The option "groups" is not supported by the constraint "%s".', __CLASS__)); + } + + parent::__construct($options); + } + + /** + * {@inheritdoc} + */ + public function getTargets() + { + return [self::PROPERTY_CONSTRAINT, self::CLASS_CONSTRAINT]; + } +} diff --git a/src/Symfony/Component/Validator/Mapping/Loader/AutoMappingTrait.php b/src/Symfony/Component/Validator/Mapping/Loader/AutoMappingTrait.php new file mode 100644 index 0000000000..1c21810763 --- /dev/null +++ b/src/Symfony/Component/Validator/Mapping/Loader/AutoMappingTrait.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Mapping\Loader; + +use Symfony\Component\Validator\Constraints\DisableAutoMapping; +use Symfony\Component\Validator\Constraints\EnableAutoMapping; +use Symfony\Component\Validator\Mapping\ClassMetadata; + +/** + * Utility methods to create auto mapping loaders. + * + * @author Kévin Dunglas + */ +trait AutoMappingTrait +{ + private function isAutoMappingEnabledForClass(ClassMetadata $metadata, string $classValidatorRegexp = null): bool + { + // Check if AutoMapping constraint is set first + foreach ($metadata->getConstraints() as $constraint) { + if ($constraint instanceof DisableAutoMapping) { + return false; + } + + if ($constraint instanceof EnableAutoMapping) { + return true; + } + } + + // Fallback on the config + return null === $classValidatorRegexp || preg_match($classValidatorRegexp, $metadata->getClassName()); + } +} diff --git a/src/Symfony/Component/Validator/Mapping/Loader/PropertyInfoLoader.php b/src/Symfony/Component/Validator/Mapping/Loader/PropertyInfoLoader.php index 379210b54b..e2ad9cb9ec 100644 --- a/src/Symfony/Component/Validator/Mapping/Loader/PropertyInfoLoader.php +++ b/src/Symfony/Component/Validator/Mapping/Loader/PropertyInfoLoader.php @@ -16,6 +16,8 @@ use Symfony\Component\PropertyInfo\PropertyListExtractorInterface; use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface; use Symfony\Component\PropertyInfo\Type as PropertyInfoType; use Symfony\Component\Validator\Constraints\All; +use Symfony\Component\Validator\Constraints\DisableAutoMapping; +use Symfony\Component\Validator\Constraints\EnableAutoMapping; use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\NotNull; use Symfony\Component\Validator\Constraints\Type; @@ -28,6 +30,8 @@ use Symfony\Component\Validator\Mapping\ClassMetadata; */ final class PropertyInfoLoader implements LoaderInterface { + use AutoMappingTrait; + private $listExtractor; private $typeExtractor; private $accessExtractor; @@ -47,14 +51,12 @@ final class PropertyInfoLoader implements LoaderInterface public function loadClassMetadata(ClassMetadata $metadata): bool { $className = $metadata->getClassName(); - if (null !== $this->classValidatorRegexp && !preg_match($this->classValidatorRegexp, $className)) { - return false; - } - if (!$properties = $this->listExtractor->getProperties($className)) { return false; } + $loaded = false; + $enabledForClass = $this->isAutoMappingEnabledForClass($metadata, $this->classValidatorRegexp); foreach ($properties as $property) { if (false === $this->accessExtractor->isWritable($className, $property)) { continue; @@ -69,12 +71,22 @@ final class PropertyInfoLoader implements LoaderInterface continue; } + $enabledForProperty = $enabledForClass; $hasTypeConstraint = false; $hasNotNullConstraint = false; $hasNotBlankConstraint = false; $allConstraint = null; foreach ($metadata->getPropertyMetadata($property) as $propertyMetadata) { foreach ($propertyMetadata->getConstraints() as $constraint) { + // Enabling or disabling auto-mapping explicitly always takes precedence + if ($constraint instanceof DisableAutoMapping) { + continue 3; + } + + if ($constraint instanceof EnableAutoMapping) { + $enabledForProperty = true; + } + if ($constraint instanceof Type) { $hasTypeConstraint = true; } elseif ($constraint instanceof NotNull) { @@ -87,6 +99,11 @@ final class PropertyInfoLoader implements LoaderInterface } } + if (!$enabledForProperty) { + continue; + } + + $loaded = true; $builtinTypes = []; $nullable = false; $scalar = true; @@ -118,7 +135,7 @@ final class PropertyInfoLoader implements LoaderInterface } } - return true; + return $loaded; } private function getTypeConstraint(string $builtinType, PropertyInfoType $type): Type diff --git a/src/Symfony/Component/Validator/Tests/Constraints/DisableAutoMappingTest.php b/src/Symfony/Component/Validator/Tests/Constraints/DisableAutoMappingTest.php new file mode 100644 index 0000000000..49dd532c7b --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/DisableAutoMappingTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Tests\Constraints; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Validator\Constraints\DisableAutoMapping; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; + +/** + * @author Kévin Dunglas + */ +class DisableAutoMappingTest extends TestCase +{ + public function testGroups() + { + $this->expectException(ConstraintDefinitionException::class); + $this->expectExceptionMessage(sprintf('The option "groups" is not supported by the constraint "%s".', DisableAutoMapping::class)); + + new DisableAutoMapping(['groups' => 'foo']); + } +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/EnableAutoMappingTest.php b/src/Symfony/Component/Validator/Tests/Constraints/EnableAutoMappingTest.php new file mode 100644 index 0000000000..7dc8b17068 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/EnableAutoMappingTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Tests\Constraints; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Validator\Constraints\EnableAutoMapping; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; + +/** + * @author Kévin Dunglas + */ +class EnableAutoMappingTest extends TestCase +{ + public function testGroups() + { + $this->expectException(ConstraintDefinitionException::class); + $this->expectExceptionMessage(sprintf('The option "groups" is not supported by the constraint "%s".', EnableAutoMapping::class)); + + new EnableAutoMapping(['groups' => 'foo']); + } +} diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/PropertyInfoLoaderEntity.php b/src/Symfony/Component/Validator/Tests/Fixtures/PropertyInfoLoaderEntity.php index 5f2a37179d..144a0016e8 100644 --- a/src/Symfony/Component/Validator/Tests/Fixtures/PropertyInfoLoaderEntity.php +++ b/src/Symfony/Component/Validator/Tests/Fixtures/PropertyInfoLoaderEntity.php @@ -49,6 +49,11 @@ class PropertyInfoLoaderEntity public $readOnly; + /** + * @Assert\DisableAutoMapping + */ + public $noAutoMapping; + public function setNonExistentField() { } diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/PropertyInfoLoaderNoAutoMappingEntity.php b/src/Symfony/Component/Validator/Tests/Fixtures/PropertyInfoLoaderNoAutoMappingEntity.php new file mode 100644 index 0000000000..d14cb7c7c7 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Fixtures/PropertyInfoLoaderNoAutoMappingEntity.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Tests\Fixtures; + +use Symfony\Component\Validator\Constraints as Assert; + +/** + * @Assert\DisableAutoMapping + * + * @author Kévin Dunglas + */ +class PropertyInfoLoaderNoAutoMappingEntity +{ + public $string; + + /** + * @Assert\EnableAutoMapping + */ + public $autoMappingExplicitlyEnabled; +} diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/PropertyInfoLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/PropertyInfoLoaderTest.php index f87b19f795..8e122dcdd8 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/PropertyInfoLoaderTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/PropertyInfoLoaderTest.php @@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Validator\Constraints\All; +use Symfony\Component\Validator\Constraints\DisableAutoMapping; use Symfony\Component\Validator\Constraints\Iban; use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\NotNull; @@ -23,6 +24,7 @@ use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader; use Symfony\Component\Validator\Tests\Fixtures\Entity; use Symfony\Component\Validator\Tests\Fixtures\PropertyInfoLoaderEntity; +use Symfony\Component\Validator\Tests\Fixtures\PropertyInfoLoaderNoAutoMappingEntity; use Symfony\Component\Validator\Validation; /** @@ -47,6 +49,7 @@ class PropertyInfoLoaderTest extends TestCase 'alreadyPartiallyMappedCollection', 'readOnly', 'nonExistentField', + 'noAutoMapping', ]) ; $propertyInfoStub @@ -61,6 +64,7 @@ class PropertyInfoLoaderTest extends TestCase [new Type(Type::BUILTIN_TYPE_STRING, true)], [new Type(Type::BUILTIN_TYPE_STRING, true)], [new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true, null, new Type(Type::BUILTIN_TYPE_FLOAT))], + [new Type(Type::BUILTIN_TYPE_STRING)], [new Type(Type::BUILTIN_TYPE_STRING)] )) ; @@ -76,7 +80,8 @@ class PropertyInfoLoaderTest extends TestCase true, true, true, - false + false, + true )) ; @@ -158,6 +163,12 @@ class PropertyInfoLoaderTest extends TestCase $readOnlyMetadata = $classMetadata->getPropertyMetadata('readOnly'); $this->assertEmpty($readOnlyMetadata); + + $noAutoMappingMetadata = $classMetadata->getPropertyMetadata('noAutoMapping'); + $this->assertCount(1, $noAutoMappingMetadata); + $noAutoMappingConstraints = $noAutoMappingMetadata[0]->getConstraints(); + $this->assertCount(1, $noAutoMappingConstraints); + $this->assertInstanceOf(DisableAutoMapping::class, $noAutoMappingConstraints[0]); } /** @@ -189,4 +200,30 @@ class PropertyInfoLoaderTest extends TestCase [false, '{^'.preg_quote(Entity::class).'$}'], ]; } + + public function testClassNoAutoMapping() + { + $propertyInfoStub = $this->createMock(PropertyInfoExtractorInterface::class); + $propertyInfoStub + ->method('getProperties') + ->willReturn(['string', 'autoMappingExplicitlyEnabled']) + ; + $propertyInfoStub + ->method('getTypes') + ->willReturnOnConsecutiveCalls( + [new Type(Type::BUILTIN_TYPE_STRING)], + [new Type(Type::BUILTIN_TYPE_BOOL)] + ); + + $propertyInfoLoader = new PropertyInfoLoader($propertyInfoStub, $propertyInfoStub, $propertyInfoStub); + $validator = Validation::createValidatorBuilder() + ->enableAnnotationMapping() + ->addLoader($propertyInfoLoader) + ->getValidator() + ; + + $classMetadata = $validator->getMetadataFor(new PropertyInfoLoaderNoAutoMappingEntity()); + $this->assertEmpty($classMetadata->getPropertyMetadata('string')); + $this->assertCount(3, $classMetadata->getPropertyMetadata('autoMappingExplicitlyEnabled')[0]->constraints); + } }