Symfony 8.1 improves the Validator component with new constraints, Clock support, and reentrant validators.

New Xml Constraint

Mokhtar Tlili
Contributed by Mokhtar Tlili in #57365

XML payloads still appear in many Symfony applications (SOAP responses, configuration files, sitemap excerpts, or third-party feeds). Until now, validating that a value was well-formed XML (and optionally conformed to an XSD schema) meant writing a custom constraint or adding libxml calls throughout controllers. Symfony 8.1 adds a built-in Xml constraint that handles both cases:

1
2
3
4
5
6
7
8
9
10
11
12
13
// src/Entity/Report.php
namespace App\Entity;

use Symfony\Component\Validator\Constraints as Assert;

class Report
{
    #[Assert\Xml]
    public string $rawContent;

    #[Assert\Xml(schemaPath: 'config/schemas/report.xsd')]
    public string $validatedContent;
}

The validator first checks that the value is well-formed XML. If you pass a schemaPath, it additionally validates the document against that XSD schema and reports each schema error as a separate violation, including the offending line number. The schemaPath is checked at construction time, so unreadable files fail fast with an InvalidArgumentException.

Clock-aware Comparison and Range Validators

Louis-Arnaud
Contributed by Louis-Arnaud in #63360

Symfony's comparison constraints (GreaterThan, GreaterThanOrEqual, LessThan, LessThanOrEqual) and the Range constraint accept relative date strings like today or -18 years. Under the hood, they were resolved with new \DateTimeImmutable($string), which always reads the real wall clock and makes date-based validation impossible to test deterministically.

In Symfony 8.1, these validators are clock-aware. When the Clock component is installed and registered as a service, relative date strings are resolved against the configured clock, including MockClock in tests:

1
2
3
4
5
6
7
8
9
10
11
12
13
use Symfony\Component\Clock\MockClock;
use Symfony\Component\Validator\Constraints\GreaterThan;
use Symfony\Component\Validator\Constraints\GreaterThanValidator;

$clock = new MockClock('2026-05-20 00:00:00 UTC');
$validator = new GreaterThanValidator(null, $clock);

// "-10 days" is resolved relative to the frozen "now" (2026-05-10),
// so the 2026-05-12 value passes the GreaterThan check.
$validator->validate(
    new \DateTimeImmutable('2026-05-12 00:00:00 UTC'),
    new GreaterThan('-10 days'),
);

When using FrameworkBundle, the wiring is automatic: the validator service prototype now binds the clock service to every constraint validator constructor declaring a ClockInterface argument.

Strict Check for Property Metadata Existence

Louis-Arnaud
Contributed by Louis-Arnaud in #63355

The Validator service exposes two methods to validate a single property of an object: validateProperty() reads the current value, and validatePropertyValue() validates a value you provide explicitly. Both methods silently return zero violations when the property has no constraints defined, which also happens when the property name contains a typo or has been renamed.

Symfony 8.1 adds an opt-in ValidatorBuilder::enablePropertyMetadataExistenceCheck() method that makes those calls throw a ValidatorException instead:

1
2
3
4
5
6
7
8
use Symfony\Component\Validator\Validation;

$validator = Validation::createValidatorBuilder()
    ->enablePropertyMetadataExistenceCheck()
    ->getValidator();

// throws ValidatorException: the property "nmae" does not exist in class "Author"
$validator->validateProperty($author, 'nmae');

Reentrant Constraint Validators

Christophe Coevoet
Contributed by Christophe Coevoet in #63945

Constraint validators used to be stateful: initialize($context) stored the execution context on the validator instance, and validate() later read it back from $this->context. That design made re-entering the same validator during recursive validation tricky, especially in validators such as CollectionValidator.

Symfony 8.1 resolves this by passing the execution context explicitly to every validation call via the new ConstraintValidatorInterface::validateInContext() method. The migration is incremental:

  • Validators extending the abstract ConstraintValidator class need no changes because the base class handles the execution context automatically.
  • Validators implementing ConstraintValidatorInterface directly should migrate to validateInContext(). The old validate() and initialize() methods are deprecated.
  • Tests using ConstraintValidatorTestCase should call $this->validate(...) (a new helper) instead of $this->validator->validate(...).
Published in #Living on the edge