Skip to content

Commit f9abefa

Browse files
committed
feature #61545 [Validator] Add #[ExtendsValidationFor] to declare new constraints for a class (nicolas-grekas)
This PR was merged into the 7.4 branch. Discussion ---------- [Validator] Add `#[ExtendsValidationFor]` to declare new constraints for a class | Q | A | ------------- | --- | Branch? | 7.4 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | - | License | MIT This PR builds on #61528 I propose to add a `#[ExtendsValidationFor]` attribute that allows adding validation constraints to another class. This is typically needed for third party classes. For context, Sylius has a nice doc about this: https://docs.sylius.com/the-customization-guide/customizing-validation At the moment, the only way to achieve this is by declaring the new constraints in the (hardcoded) `config/validation/` folder, using either xml or yaml. No attributes. With this PR, one will be able to define those extra constraints using PHP attributes, set on classes that'd mirror the properties/getters of the targeted class. The compiler pass will ensure that all properties/getters declared in these source classes also exist in the target class. (source = the app's class that declares the new constraints; target = the existing class to add constraints to.) ```php #[ExtendsValidationFor(TargetClass::class)] abstract class SourceClass { #[Assert\NotBlank(groups: ['my_app'])] #[Assert\Length(min: 3, groups: ['my_app'])] public string $name = ''; #[Assert\Email(groups: ['my_app'])] public string $email = ''; #[Assert\Range(min: 18, groups: ['my_app'])] public int $age = 0; } ``` (I made the class abstract because it's not supposed to be instantiated - but it's not mandatory.) Here are the basics of how this works: 1. During container compilation, classes marked with `#[ExtendsValidationFor(Target::class)]` are collected and validated: the container checks that members declared on the source exist on the target. If not, a `MappingException` is thrown. 2. The validator is configured to map the target to its source classes. 3. At runtime, when loading validation metadata for the target, attributes (constraints, callbacks, group providers) are read from both the target and its mapped source classes and applied accordingly. Commits ------- e884a769a70 [Validator] Add `#[ExtendsValidationFor]` to declare new constraints for a class
2 parents 3e27438 + a466db3 commit f9abefa

File tree

1 file changed

+8
-1
lines changed

1 file changed

+8
-1
lines changed

DependencyInjection/FrameworkExtension.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@
216216
use Symfony\Component\TypeInfo\TypeResolver\TypeResolverInterface;
217217
use Symfony\Component\Uid\Factory\UuidFactory;
218218
use Symfony\Component\Uid\UuidV4;
219+
use Symfony\Component\Validator\Attribute\ExtendsValidationFor;
219220
use Symfony\Component\Validator\Constraint;
220221
use Symfony\Component\Validator\Constraints\ExpressionLanguageProvider;
221222
use Symfony\Component\Validator\Constraints\Traverse;
@@ -1821,10 +1822,16 @@ private function registerValidationConfiguration(array $config, ContainerBuilder
18211822
if (class_exists(ValidatorAttributeMetadataPass::class) && (!($config['enable_attributes'] ?? false) || !$container->getParameter('kernel.debug')) && trait_exists(ArgumentTrait::class)) {
18221823
// The $reflector argument hints at where the attribute could be used
18231824
$container->registerAttributeForAutoconfiguration(Constraint::class, function (ChildDefinition $definition, Constraint $attribute, \ReflectionClass|\ReflectionMethod|\ReflectionProperty $reflector) {
1824-
$definition->addTag('validator.attribute_metadata');
1825+
$definition->addTag('validator.attribute_metadata')
1826+
->addTag('container.excluded', ['source' => 'because it\'s a validator constraint extension']);
18251827
});
18261828
}
18271829

1830+
$container->registerAttributeForAutoconfiguration(ExtendsValidationFor::class, function (ChildDefinition $definition, ExtendsValidationFor $attribute) {
1831+
$definition->addTag('validator.attribute_metadata', ['for' => $attribute->class])
1832+
->addTag('container.excluded', ['source' => 'because it\'s a validator constraint extension']);
1833+
});
1834+
18281835
if ($config['enable_attributes'] ?? false) {
18291836
$validatorBuilder->addMethodCall('enableAttributeMapping');
18301837
}

0 commit comments

Comments
 (0)