Skip to content

Conversation

@nicolas-grekas
Copy link
Member

@nicolas-grekas nicolas-grekas commented Aug 28, 2025

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.)

#[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.

@alexandre-daubois
Copy link
Member

The feature seems nice, but I'm still trying to grasp what it really does. If I understand correctly and if we take Sylius example, is the following code correct?

use Sylius\Component\Product\Model\ProductTranslation;

#[ValidationFor(ProductTranslation::class)]
class MyProductTranslation
{
    #[Assert\NotBlank(groups: ['my_app'])] 
    #[Assert\Length(min: 10, groups: ['my_app'])]
    public string $name = '';
}

Then on validation, Sylius' ProductTranslation constraints would be overridden by the ones defined in MyProductTranslation. Is that right?

@nicolas-grekas
Copy link
Member Author

@alexandre-daubois you've got it right, provided you also configure the sylius.form.type.product_translation.validation_groups parameter as explained in the doc.
That's already doable using yaml and xml but not using plain PHP attributes.

@OskarStark OskarStark changed the title [Validator] Add #[ValidationFor] to declare new constraints for a class [Validator] Add #[ValidationFor] to declare new constraints for a class Aug 29, 2025
@OskarStark
Copy link
Contributor

Could we find a proper tribute name with something like #[As*]?

@nicolas-grekas
Copy link
Member Author

@OskarStark that's not a service. As* is for services to me.

Copy link
Member

@alexandre-daubois alexandre-daubois left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work with class constraints? I don't think I saw support or tests for them. I wonder if they should be supported for feature completeness?

@nicolas-grekas
Copy link
Member Author

Thank you @alexandre-daubois and @stof, I addressed your comments.

@nicolas-grekas nicolas-grekas changed the title [Validator] Add #[ValidationFor] to declare new constraints for a class [Validator] Add #[ExtendsValidationFor] to declare new constraints for a class Aug 30, 2025
Copy link
Member

@alexandre-daubois alexandre-daubois left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The feature is a bit puzzling to me (having a class that “lives” somewhat on its own and is never actually used in the code except by the container), but the use case is relevant so looks fine 👍

fabpot added a commit that referenced this pull request Sep 2, 2025
…ct classes for resource definitions (nicolas-grekas)

This PR was merged into the 7.4 branch.

Discussion
----------

[DependencyInjection] Parse attributes found on abstract classes for resource definitions

| Q             | A
| ------------- | ---
| Branch?       | 7.4
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Issues        | -
| License       | MIT

This PR improves support for resource definitions by allowing to parse attributes on abstract classes.

This can be useful for PRs like #61563 and #61545, so that the attribute proposed there could be set on abstract classes.

Commits
-------

6571f7b [DependencyInjection] Parse attributes found on abstract classes for resource definitions
@alexander-schranz
Copy link
Contributor

alexander-schranz commented Sep 5, 2025

We have the same issue when removing XML and Yaml for Validation we require a way that a Project can add new validations to third party classes of @sulu.

We are fine with whatever alternative Symfony provides our CMS users, personally I would maybe go more with something like:

class CustomUserValidationProvider
{
    #[UserValidationProviderFor(User::class)]
    public static function loadValidatorMetadata(ClassMetadata $metadata): void
    {
        $metadata->addPropertyConstraint('name', new NotBlank());
    }
}

Very similar how you could already have loadValidatorMetadata on the Entity itself see PHP way here. But we could add both kind of ways.

@stof
Copy link
Member

stof commented Sep 5, 2025

@alexander-schranz that would be a separate feature, which can totally be added in parallel if projects prefer using the PHP metadata API than adding attributes.

@nicolas-grekas
Copy link
Member Author

@alexander-schranz thanks for chiming in and for the proposal.
On my side, I prefer the attribute-way because it doesn't require learning about the ClassMetadata API, which is totally different than attributes.
I get that config classes like this might be unusual, but they're nonetheless a very good substrate for configuration via attributes. I ensured such classes could be made abstract, to prevent using them for anything else.

@fabpot
Copy link
Member

fabpot commented Sep 10, 2025

Thank you @nicolas-grekas.

@fabpot fabpot merged commit f603472 into symfony:7.4 Sep 10, 2025
10 of 12 checks passed
fabpot added a commit that referenced this pull request Sep 10, 2025
…re new serialization attributes for a class (nicolas-grekas)

This PR was merged into the 7.4 branch.

Discussion
----------

[Serializer] Add `#[ExtendsSerializationFor]` to declare new serialization attributes for a class

| Q             | A
| ------------- | ---
| Branch?       | 7.4
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Issues        | -
| License       | MIT

This PR builds on #61532
It's a sibling of #61545

I propose to add a `#[ExtendsSerializationFor]` attribute that allows adding serialization attributes 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-serialization-of-api

At the moment, the only way to achieve this is by declaring the new attributes in the (hardcoded) `config/serialization/` folder, using either xml or yaml. No attributes.

With this PR, one will be able to define those extra serialization attributes 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 serialization attributes; target = the existing class to add serialization attributes to.)

```php
#[ExtendsSerializationFor(TargetClass::class)]
abstract class SourceClass
{
    #[Groups(['my_app'])]
    #[SerializedName('fullName')]
    public string $name = '';

    #[Groups(['my_app'])]
    public string $email = '';

    #[Groups(['my_app'])]
    #[MaxDepth(2)]
    public Category $category;
}
```

(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 `#[ExtendsSerializationFor(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 serializer is configured to map the target to its source classes.
3. At runtime, when loading serialization metadata for the target, attributes (groups, serialized names, max depth, etc.) are read from both the target and its mapped source classes and applied accordingly.

Commits
-------

386f949 [Serializer] Add `#[ExtendsSerializationFor]` to declare new serialization attributes for a class
@nicolas-grekas nicolas-grekas deleted the validator.for branch September 10, 2025 12:12
This was referenced Oct 27, 2025
fabpot added a commit that referenced this pull request Nov 20, 2025
…hods private (xabbuh)

This PR was merged into the 7.4 branch.

Discussion
----------

[Serializer][Validator] make `doLoadClassMetadata()` methods private

| Q             | A
| ------------- | ---
| Branch?       | 7.4
| Bug fix?      | no
| New feature?  | no
| Deprecations? | no
| Issues        |
| License       | MIT

I don't see a good reason to have them callable from the outside. This was probably a mistake in #61545/#61563.

Commits
-------

9653488 make doLoadClassMetadata() methods private
@ruudk
Copy link
Contributor

ruudk commented Dec 4, 2025

Unfortunately this broke LiveComponents that have Validator constraints defined on properties. Explained in symfony/ux#3194.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants