Description
Description
While it is super easy to get the reasons why validation failed, it's impossible to get any proof/trace of all the things that were validated. Not even TraceableValidator
helps here, because the information (code
, message
) is not exposed from ConstraintValidatorInterface
implementations unless there are violations. The most you can do today is save all the properties of existing constraints to logs (or have custom logic per Constraint
to form similar messages as Validator) – but it would be much nicer if we could have the same messages as we do for violations. The violation messages in built-in Constraint
s are written in a way which does not say "This value is less than 10"
but "This value should be at least 10"
, meaning they could be used as-is when validation passes too.
With this it would be possible to simply use Validator to validate business rules and generate (with a little bit of user-space code) audit logs like
✅ age: This value should be between 18 and 100.
❌ internalCustomerScore: This value should be between 150.0 and 250.0.
Then later when you need to know "was the age of a particular customer checked 3 years ago" and "what were the age limits at the time", it is really simple to do by just checking the logs originated from the Validator.
Drawbacks / challenges
- This would likely be really bad for performance & memory usage, thus it would need to be opt-in to store the non-violation-related data
- BC break(s), though we could still keep
ValidatorInterface
untouched - A lot of work, since there are so many
ConstraintValidator
implementations
Why Validator, it was not built for this!
- Developers are familiar with it, no need to come up or familiarize with a custom solution
- Unlike some php business logic (
if
s etc), ValidatorConstraint
s are declarative – it is possible to inspect and enumerate them, generate docs - Having something separate, yet really similar to Validator would mean a lot of maintenance work as all the related validation logic would need to be separately maintained. You'd basically end-up copy-pasting the
ConstraintValidator
s and only add the exposing of non-violation results
Example
Below is an example from the built-in RangeValidator
, demonstrating the problem and possible solution.
- if ($hasLowerLimit && $hasUpperLimit && ($value < $min || $value > $max)) {
+ if ($hasLowerLimit && $hasUpperLimit) {
$message = $constraint->notInRangeMessage;
$code = Range::NOT_IN_RANGE_ERROR;
- $violationBuilder = $this->context->buildViolation($message)
+ $resultBuilder = $this->context->buildValidationResult($value < $min || $value > $max)
+ ->setMessage($message)
->setParameter('{{ value }}', $this->formatValue($value, self::PRETTY_DATE))
->setParameter('{{ min }}', $this->formatValue($min, self::PRETTY_DATE))
->setParameter('{{ max }}', $this->formatValue($max, self::PRETTY_DATE))
->setCode($code);
if (null !== $constraint->maxPropertyPath) {
- $violationBuilder->setParameter('{{ max_limit_path }}', $constraint->maxPropertyPath);
+ $resultBuilder->setParameter('{{ max_limit_path }}', $constraint->maxPropertyPath);
}
if (null !== $constraint->minPropertyPath) {
- $violationBuilder->setParameter('{{ min_limit_path }}', $constraint->minPropertyPath);
+ $resultBuilder->setParameter('{{ min_limit_path }}', $constraint->minPropertyPath);
}
- $violationBuilder->addViolation();
+ $resultBuilder->addResult();
}
And then there would be some ExecutionContextInterface::getResults()
method to fetch all the results. This could co-exist with the existing getViolations()
.