Symfony 8.1 improves the Validator component with new constraints, Clock support, and reentrant validators.
New Xml Constraint
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
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
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
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
ConstraintValidatorclass need no changes because the base class handles the execution context automatically. - Validators implementing
ConstraintValidatorInterfacedirectly should migrate tovalidateInContext(). The oldvalidate()andinitialize()methods are deprecated. - Tests using
ConstraintValidatorTestCaseshould call$this->validate(...)(a new helper) instead of$this->validator->validate(...).