Many Symfony applications rely on external bundles and packages that provide
their own classes. When you needed to customize how those classes were validated
or serialized, you had to redefine their metadata in XML or YAML files placed in
hardcoded configuration directories such as config/validation/. This worked,
but it felt disconnected from your application code.
Symfony 7.4 introduces a better approach. You can now extend validation and
serialization metadata using PHP attributes. To extend the validation metadata
of an external class, create a new class anywhere in your application and apply
the #[ExtendsValidationFor] attribute. This attribute declares the FQCN of
the class you want to extend:
1 2 3 4 5 6 7 8
use Acme\Some\Bundle\UserRegistration;
use Symfony\Component\Validator\Attribute\ExtendsValidationFor;
#[ExtendsValidationFor(UserRegistration::class)]
class UserRegistrationValidation
{
// ...
}
Your class can be named however you like. Inside it, add the properties and getters that you want to extend from the original class. Use the exact same names as in the original class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
use Symfony\Component\Validator\Attribute\ExtendsValidationFor;
use Symfony\Component\Validator\Constraints as Assert;
#[ExtendsValidationFor(UserRegistration::class)]
class UserRegistrationValidation
{
#[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;
}
How this works:
- During container compilation, Symfony collects classes marked with
#[ExtendsValidationFor(Target::class)]and verifies that the properties and getters declared in your class exist in the target class. If not, aMappingExceptionis thrown. - The validator is configured so that the target class is mapped to your validation extension class.
- At runtime, when loading validation metadata for the target class, Symfony reads attributes (constraints, callbacks, group providers) from both the original class and your extension class and merges them.
These extension classes are not meant to be instantiated. If you prefer, you can declare them as abstract:
1 2 3 4 5
#[ExtendsValidationFor(UserRegistration::class)]
abstract class UserRegistrationValidation
{
// ...
}
Symfony 7.4 introduces the same idea for serialization metadata. By applying
#[ExtendsSerializationFor] to your own classes, you can declare new
serialization attributes for third party classes without defining XML or YAML
in the hardcoded config/serialization/ directory:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
use Symfony\Component\Serializer\Attribute\ExtendsSerializationFor;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Serializer\Attribute\MaxDepth;
use Symfony\Component\Serializer\Attribute\SerializedName;
// ...
#[ExtendsSerializationFor(UserRegistration::class)]
abstract class UserRegistrationSerialization
{
#[Groups(['my_app'])]
#[SerializedName('fullName')]
public string $name = '';
#[Groups(['my_app'])]
public string $email = '';
#[Groups(['my_app'])]
#[MaxDepth(2)]
public Category $category;
}
As with validation, Symfony verifies that the properties or accessors declared in your class exist in the target class. At runtime, metadata from the original class and your extension class is merged.
Nice! Is it possible to remove existing validation rules or only merge?