feature #32107 [Validator] Add AutoMapping constraint to enable or disable auto-validation (dunglas)

This PR was squashed before being merged into the 4.4 branch (closes #32107).

Discussion
----------

[Validator] Add AutoMapping constraint to enable or disable auto-validation

| Q             | A
| ------------- | ---
| Branch?       | 4.4
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Tests pass?   | yes    <!-- please add some, will be required by reviewers -->
| Fixed tickets | #32070, #32015   <!-- #-prefixed issue number(s), if any -->
| License       | MIT
| Doc PR        | todo

As discussed in #32070 and #32015, it's sometimes mandatory to prevent some classes or properties to be auto mapped (auto-validated). This PR introduces a new constraint, `@AutoMapping` allowing to do exactly that. Examples:

Class:

```php
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity
 * @Assert\AutoMapping(false)
 */
class DoctrineLoaderNoAutoMappingEntity
{
    /**
     * @ORM\Id
     * @ORM\Column
     */
    public $id;

    /**
     * @ORM\Column(length=20, unique=true)
     */
    public $maxLength;
}
```

Property:

```php
namespace Symfony\Bridge\Doctrine\Tests\Fixtures;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity
 */
class DoctrineLoaderEntity extends DoctrineLoaderParentEntity
{
    /**
     * @ORM\Id
     * @ORM\Column
     */
    public $id;

    /**
     * @ORM\Column(length=10)
     * @Assert\AutoMapping(false)
     */
    public $noAutoMapping;
}
```

The rules are the following:

* If the constraint is present on a property, and set to true, auto-mapping is always on, regardless of the config, and of any class level annotation
* If the constraint is present on a property, and set to false, auto-mapping is always off, regardless of the config, and of any class level annotation
* If the constraint is present on a class, and set to true, auto-mapping is always on except if a the annotation has been added to a specific property, and regardless of the config
* If the constraint is present on a class, and set to false, auto-mapping is always off except if a the annotation has been added to a specific property, and regardless of the config

Commits
-------

f6519ce88b [Validator] Add AutoMapping constraint to enable or disable auto-validation
This commit is contained in:
Christian Flothmann 2019-10-30 08:50:31 +01:00
commit 9e7ab8c003
14 changed files with 407 additions and 32 deletions

View File

@ -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] : [];

View File

@ -0,0 +1,41 @@
<?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\Fixtures;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity
* @Assert\DisableAutoMapping
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class DoctrineLoaderNoAutoMappingEntity
{
/**
* @ORM\Id
* @ORM\Column
*/
public $id;
/**
* @ORM\Column(length=20, unique=true)
*/
public $maxLength;
/**
* @Assert\EnableAutoMapping
* @ORM\Column(length=20)
*/
public $autoMappingExplicitlyEnabled;
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,45 @@
<?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\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 <dunglas@gmail.com>
*/
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];
}
}

View File

@ -0,0 +1,45 @@
<?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\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 <dunglas@gmail.com>
*/
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];
}
}

View File

@ -0,0 +1,41 @@
<?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\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 <dunglas@gmail.com>
*/
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());
}
}

View File

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

View File

@ -0,0 +1,30 @@
<?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\Validator\Tests\Constraints;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\Constraints\DisableAutoMapping;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
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']);
}
}

View File

@ -0,0 +1,30 @@
<?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\Validator\Tests\Constraints;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\Constraints\EnableAutoMapping;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
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']);
}
}

View File

@ -49,6 +49,11 @@ class PropertyInfoLoaderEntity
public $readOnly;
/**
* @Assert\DisableAutoMapping
*/
public $noAutoMapping;
public function setNonExistentField()
{
}

View File

@ -0,0 +1,29 @@
<?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\Validator\Tests\Fixtures;
use Symfony\Component\Validator\Constraints as Assert;
/**
* @Assert\DisableAutoMapping
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class PropertyInfoLoaderNoAutoMappingEntity
{
public $string;
/**
* @Assert\EnableAutoMapping
*/
public $autoMappingExplicitlyEnabled;
}

View File

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