### 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 1. 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 2. BC break(s), though we could still keep `ValidatorInterface` untouched 3. A lot of work, since there are so many `ConstraintValidator` implementations ### Why Validator, it was not built for this! 1. Developers are familiar with it, no need to come up or familiarize with a custom solution 2. Unlike some php business logic (`if`s etc), Validator `Constraint`s are declarative – it is possible to inspect and enumerate them, generate docs 3. 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`](https://github.com/symfony/symfony/blob/7.3/src/Symfony/Component/Validator/Constraints/RangeValidator.php), demonstrating the problem and possible solution. ```diff - 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()`.