Skip to content

Commit fa73e5f

Browse files
dunglasxabbuh
authored andcommitted
[Validator] Add AutoMapping constraint to enable or disable auto-validation
1 parent 91f0862 commit fa73e5f

10 files changed

+286
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
4.4.0
55
-----
66

7+
* added `EnableAutoMapping` and `DisableAutoMapping` constraints to enable or disable auto mapping for class or a property
78
* using anything else than a `string` as the code of a `ConstraintViolation` is deprecated, a `string` type-hint will
89
be added to the constructor of the `ConstraintViolation` class and to the `ConstraintViolationBuilder::setCode()`
910
method in 5.0

Constraints/DisableAutoMapping.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Constraints;
13+
14+
use Symfony\Component\Validator\Constraint;
15+
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
16+
17+
/**
18+
* Disables auto mapping.
19+
*
20+
* Using the annotations on a property has higher precedence than using it on a class,
21+
* which has higher precedence than any configuration that might be defined outside the class.
22+
*
23+
* @Annotation
24+
*
25+
* @author Kévin Dunglas <[email protected]>
26+
*/
27+
class DisableAutoMapping extends Constraint
28+
{
29+
public function __construct($options = null)
30+
{
31+
if (\is_array($options) && \array_key_exists('groups', $options)) {
32+
throw new ConstraintDefinitionException(sprintf('The option "groups" is not supported by the constraint "%s".', __CLASS__));
33+
}
34+
35+
parent::__construct($options);
36+
}
37+
38+
/**
39+
* {@inheritdoc}
40+
*/
41+
public function getTargets()
42+
{
43+
return [self::PROPERTY_CONSTRAINT, self::CLASS_CONSTRAINT];
44+
}
45+
}

Constraints/EnableAutoMapping.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Constraints;
13+
14+
use Symfony\Component\Validator\Constraint;
15+
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
16+
17+
/**
18+
* Enables auto mapping.
19+
*
20+
* Using the annotations on a property has higher precedence than using it on a class,
21+
* which has higher precedence than any configuration that might be defined outside the class.
22+
*
23+
* @Annotation
24+
*
25+
* @author Kévin Dunglas <[email protected]>
26+
*/
27+
class EnableAutoMapping extends Constraint
28+
{
29+
public function __construct($options = null)
30+
{
31+
if (\is_array($options) && \array_key_exists('groups', $options)) {
32+
throw new ConstraintDefinitionException(sprintf('The option "groups" is not supported by the constraint "%s".', __CLASS__));
33+
}
34+
35+
parent::__construct($options);
36+
}
37+
38+
/**
39+
* {@inheritdoc}
40+
*/
41+
public function getTargets()
42+
{
43+
return [self::PROPERTY_CONSTRAINT, self::CLASS_CONSTRAINT];
44+
}
45+
}

Mapping/Loader/AutoMappingTrait.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Mapping\Loader;
13+
14+
use Symfony\Component\Validator\Constraints\DisableAutoMapping;
15+
use Symfony\Component\Validator\Constraints\EnableAutoMapping;
16+
use Symfony\Component\Validator\Mapping\ClassMetadata;
17+
18+
/**
19+
* Utility methods to create auto mapping loaders.
20+
*
21+
* @author Kévin Dunglas <[email protected]>
22+
*/
23+
trait AutoMappingTrait
24+
{
25+
private function isAutoMappingEnabledForClass(ClassMetadata $metadata, string $classValidatorRegexp = null): bool
26+
{
27+
// Check if AutoMapping constraint is set first
28+
foreach ($metadata->getConstraints() as $constraint) {
29+
if ($constraint instanceof DisableAutoMapping) {
30+
return false;
31+
}
32+
33+
if ($constraint instanceof EnableAutoMapping) {
34+
return true;
35+
}
36+
}
37+
38+
// Fallback on the config
39+
return null === $classValidatorRegexp || preg_match($classValidatorRegexp, $metadata->getClassName());
40+
}
41+
}

Mapping/Loader/PropertyInfoLoader.php

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
1717
use Symfony\Component\PropertyInfo\Type as PropertyInfoType;
1818
use Symfony\Component\Validator\Constraints\All;
19+
use Symfony\Component\Validator\Constraints\DisableAutoMapping;
20+
use Symfony\Component\Validator\Constraints\EnableAutoMapping;
1921
use Symfony\Component\Validator\Constraints\NotBlank;
2022
use Symfony\Component\Validator\Constraints\NotNull;
2123
use Symfony\Component\Validator\Constraints\Type;
@@ -28,6 +30,8 @@
2830
*/
2931
final class PropertyInfoLoader implements LoaderInterface
3032
{
33+
use AutoMappingTrait;
34+
3135
private $listExtractor;
3236
private $typeExtractor;
3337
private $accessExtractor;
@@ -47,14 +51,12 @@ public function __construct(PropertyListExtractorInterface $listExtractor, Prope
4751
public function loadClassMetadata(ClassMetadata $metadata): bool
4852
{
4953
$className = $metadata->getClassName();
50-
if (null !== $this->classValidatorRegexp && !preg_match($this->classValidatorRegexp, $className)) {
51-
return false;
52-
}
53-
5454
if (!$properties = $this->listExtractor->getProperties($className)) {
5555
return false;
5656
}
5757

58+
$loaded = false;
59+
$enabledForClass = $this->isAutoMappingEnabledForClass($metadata, $this->classValidatorRegexp);
5860
foreach ($properties as $property) {
5961
if (false === $this->accessExtractor->isWritable($className, $property)) {
6062
continue;
@@ -69,12 +71,22 @@ public function loadClassMetadata(ClassMetadata $metadata): bool
6971
continue;
7072
}
7173

74+
$enabledForProperty = $enabledForClass;
7275
$hasTypeConstraint = false;
7376
$hasNotNullConstraint = false;
7477
$hasNotBlankConstraint = false;
7578
$allConstraint = null;
7679
foreach ($metadata->getPropertyMetadata($property) as $propertyMetadata) {
7780
foreach ($propertyMetadata->getConstraints() as $constraint) {
81+
// Enabling or disabling auto-mapping explicitly always takes precedence
82+
if ($constraint instanceof DisableAutoMapping) {
83+
continue 3;
84+
}
85+
86+
if ($constraint instanceof EnableAutoMapping) {
87+
$enabledForProperty = true;
88+
}
89+
7890
if ($constraint instanceof Type) {
7991
$hasTypeConstraint = true;
8092
} elseif ($constraint instanceof NotNull) {
@@ -87,6 +99,11 @@ public function loadClassMetadata(ClassMetadata $metadata): bool
8799
}
88100
}
89101

102+
if (!$enabledForProperty) {
103+
continue;
104+
}
105+
106+
$loaded = true;
90107
$builtinTypes = [];
91108
$nullable = false;
92109
$scalar = true;
@@ -118,7 +135,7 @@ public function loadClassMetadata(ClassMetadata $metadata): bool
118135
}
119136
}
120137

121-
return true;
138+
return $loaded;
122139
}
123140

124141
private function getTypeConstraint(string $builtinType, PropertyInfoType $type): Type
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Tests\Constraints;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Validator\Constraints\DisableAutoMapping;
16+
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
17+
18+
/**
19+
* @author Kévin Dunglas <[email protected]>
20+
*/
21+
class DisableAutoMappingTest extends TestCase
22+
{
23+
public function testGroups()
24+
{
25+
$this->expectException(ConstraintDefinitionException::class);
26+
$this->expectExceptionMessage(sprintf('The option "groups" is not supported by the constraint "%s".', DisableAutoMapping::class));
27+
28+
new DisableAutoMapping(['groups' => 'foo']);
29+
}
30+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Tests\Constraints;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Validator\Constraints\EnableAutoMapping;
16+
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
17+
18+
/**
19+
* @author Kévin Dunglas <[email protected]>
20+
*/
21+
class EnableAutoMappingTest extends TestCase
22+
{
23+
public function testGroups()
24+
{
25+
$this->expectException(ConstraintDefinitionException::class);
26+
$this->expectExceptionMessage(sprintf('The option "groups" is not supported by the constraint "%s".', EnableAutoMapping::class));
27+
28+
new EnableAutoMapping(['groups' => 'foo']);
29+
}
30+
}

Tests/Fixtures/PropertyInfoLoaderEntity.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ class PropertyInfoLoaderEntity
4949

5050
public $readOnly;
5151

52+
/**
53+
* @Assert\DisableAutoMapping
54+
*/
55+
public $noAutoMapping;
56+
5257
public function setNonExistentField()
5358
{
5459
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Tests\Fixtures;
13+
14+
use Symfony\Component\Validator\Constraints as Assert;
15+
16+
/**
17+
* @Assert\DisableAutoMapping
18+
*
19+
* @author Kévin Dunglas <[email protected]>
20+
*/
21+
class PropertyInfoLoaderNoAutoMappingEntity
22+
{
23+
public $string;
24+
25+
/**
26+
* @Assert\EnableAutoMapping
27+
*/
28+
public $autoMappingExplicitlyEnabled;
29+
}

0 commit comments

Comments
 (0)