diff --git a/src/Symfony/Component/Validator/CHANGELOG.md b/src/Symfony/Component/Validator/CHANGELOG.md index e8146d2a50683..b80dbe9345ae7 100644 --- a/src/Symfony/Component/Validator/CHANGELOG.md +++ b/src/Symfony/Component/Validator/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.4 +--- + +* Add `ConstraintViolationBuilder` methods: `fromViolation()`, `setPath()`, `getViolation()` + 7.3 --- diff --git a/src/Symfony/Component/Validator/Tests/Violation/ConstraintViolationBuilderTest.php b/src/Symfony/Component/Validator/Tests/Violation/ConstraintViolationBuilderTest.php index d3c614bdea5c4..c4a58213d3c03 100644 --- a/src/Symfony/Component/Validator/Tests/Violation/ConstraintViolationBuilderTest.php +++ b/src/Symfony/Component/Validator/Tests/Violation/ConstraintViolationBuilderTest.php @@ -15,7 +15,9 @@ use Symfony\Component\Translation\IdentityTranslator; use Symfony\Component\Validator\Constraints\Valid; use Symfony\Component\Validator\ConstraintViolation; +use Symfony\Component\Validator\ConstraintViolationInterface; use Symfony\Component\Validator\ConstraintViolationList; +use Symfony\Component\Validator\Util\PropertyPath; use Symfony\Component\Validator\Violation\ConstraintViolationBuilder; use Symfony\Contracts\Translation\TranslatorInterface; @@ -42,7 +44,7 @@ public function testAddViolation() { $this->builder->addViolation(); - $this->assertViolationEquals(new ConstraintViolation($this->messageTemplate, $this->messageTemplate, [], $this->root, 'data', 'foo', null, null, new Valid())); + $this->assertBuiltViolationEquals(new ConstraintViolation($this->messageTemplate, $this->messageTemplate, [], $this->root, 'data', 'foo', null, null, new Valid())); } public function testAppendPropertyPath() @@ -51,7 +53,7 @@ public function testAppendPropertyPath() ->atPath('foo') ->addViolation(); - $this->assertViolationEquals(new ConstraintViolation($this->messageTemplate, $this->messageTemplate, [], $this->root, 'data.foo', 'foo', null, null, new Valid())); + $this->assertBuiltViolationEquals(new ConstraintViolation($this->messageTemplate, $this->messageTemplate, [], $this->root, 'data.foo', 'foo', null, null, new Valid())); } public function testAppendMultiplePropertyPaths() @@ -61,7 +63,7 @@ public function testAppendMultiplePropertyPaths() ->atPath('bar') ->addViolation(); - $this->assertViolationEquals(new ConstraintViolation($this->messageTemplate, $this->messageTemplate, [], $this->root, 'data.foo.bar', 'foo', null, null, new Valid())); + $this->assertBuiltViolationEquals(new ConstraintViolation($this->messageTemplate, $this->messageTemplate, [], $this->root, 'data.foo.bar', 'foo', null, null, new Valid())); } public function testCodeCanBeSet() @@ -70,7 +72,7 @@ public function testCodeCanBeSet() ->setCode('5') ->addViolation(); - $this->assertViolationEquals(new ConstraintViolation($this->messageTemplate, $this->messageTemplate, [], $this->root, 'data', 'foo', null, '5', new Valid())); + $this->assertBuiltViolationEquals(new ConstraintViolation($this->messageTemplate, $this->messageTemplate, [], $this->root, 'data', 'foo', null, '5', new Valid())); } public function testCauseCanBeSet() @@ -81,7 +83,7 @@ public function testCauseCanBeSet() ->setCause($cause) ->addViolation(); - $this->assertViolationEquals(new ConstraintViolation($this->messageTemplate, $this->messageTemplate, [], $this->root, 'data', 'foo', null, null, new Valid(), $cause)); + $this->assertBuiltViolationEquals(new ConstraintViolation($this->messageTemplate, $this->messageTemplate, [], $this->root, 'data', 'foo', null, null, new Valid(), $cause)); } public function testTranslationDomainFalse() @@ -96,12 +98,31 @@ public function testTranslationDomainFalse() $builder->addViolation(); } - private function assertViolationEquals(ConstraintViolation $expectedViolation) + public function testBuildViolationFromExistingViolation() + { + $originalViolation = $this->builder->getViolation(); + + $violation = ConstraintViolationBuilder::fromViolation($originalViolation) + ->setPath(PropertyPath::append('top', $originalViolation->getPropertyPath())) + ->setCause($cause = new \LogicException()) + ->getViolation(); + + $this->assertCount(0, $this->violations); + + $this->assertViolationEquals(new ConstraintViolation($this->messageTemplate, $this->messageTemplate, [], $this->root, 'top.data', 'foo', null, null, new Valid(), $cause), $violation); + } + + private function assertBuiltViolationEquals(ConstraintViolation $expectedViolation): void { $this->assertCount(1, $this->violations); $violation = $this->violations->get(0); + $this->assertViolationEquals($expectedViolation, $violation); + } + + private function assertViolationEquals(ConstraintViolation $expectedViolation, ConstraintViolationInterface $violation): void + { $this->assertSame($expectedViolation->getMessage(), $violation->getMessage()); $this->assertSame($expectedViolation->getMessageTemplate(), $violation->getMessageTemplate()); $this->assertSame($expectedViolation->getParameters(), $violation->getParameters()); diff --git a/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php b/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php index d89932a43dbf2..f20951f067b46 100644 --- a/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php +++ b/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php @@ -13,7 +13,8 @@ use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintViolation; -use Symfony\Component\Validator\ConstraintViolationList; +use Symfony\Component\Validator\ConstraintViolationInterface; +use Symfony\Component\Validator\ConstraintViolationListInterface; use Symfony\Component\Validator\Util\PropertyPath; use Symfony\Contracts\Translation\TranslatorInterface; @@ -27,24 +28,52 @@ class ConstraintViolationBuilder implements ConstraintViolationBuilderInterface { private string $propertyPath; + private ?string $message = null; private ?int $plural = null; private ?string $code = null; private mixed $cause = null; public function __construct( - private ConstraintViolationList $violations, + private ?ConstraintViolationListInterface $violations, private ?Constraint $constraint, - private string|\Stringable $message, + private string|\Stringable $messageTemplate, private array $parameters, private mixed $root, ?string $propertyPath, private mixed $invalidValue, - private TranslatorInterface $translator, + private TranslatorInterface|null $translator = null, private string|false|null $translationDomain = null, ) { $this->propertyPath = $propertyPath ?? ''; } + public static function fromViolation(ConstraintViolationInterface $violation): static + { + $builder = new self( + null, + $violation->getConstraint(), + $violation->getMessageTemplate(), + $violation->getParameters(), + $violation->getRoot(), + $violation->getPropertyPath(), + $violation->getInvalidValue(), + ); + + $builder->message = $violation->getMessage(); + $builder->plural = $violation->getPlural(); + $builder->code = $violation->getCode(); + $builder->cause = $violation->getCause(); + + return $builder; + } + + public function setPath(string $path): static + { + $this->propertyPath = $path; + + return $this; + } + public function atPath(string $path): static { $this->propertyPath = PropertyPath::append($this->propertyPath, $path); @@ -79,6 +108,7 @@ public function setTranslationDomain(string $translationDomain): static public function disableTranslation(): static { $this->translationDomain = false; + $this->translator = null; return $this; } @@ -113,20 +143,18 @@ public function setCause(mixed $cause): static public function addViolation(): void { - $parameters = null === $this->plural ? $this->parameters : (['%count%' => $this->plural] + $this->parameters); - if (false === $this->translationDomain) { - $translatedMessage = strtr($this->message, $parameters); - } else { - $translatedMessage = $this->translator->trans( - $this->message, - $parameters, - $this->translationDomain - ); + if (null === $this->violations) { + throw new \LogicException('Violation can be added only within execution context.'); } - $this->violations->add(new ConstraintViolation( - $translatedMessage, - $this->message, + $this->violations->add($this->getViolation()); + } + + public function getViolation(): ConstraintViolationInterface + { + return new ConstraintViolation( + $this->message ??= $this->translateMessage(), + $this->messageTemplate, $this->parameters, $this->root, $this->propertyPath, @@ -135,6 +163,21 @@ public function addViolation(): void $this->code, $this->constraint, $this->cause - )); + ); + } + + private function translateMessage(): string + { + $parameters = null === $this->plural ? $this->parameters : (['%count%' => $this->plural] + $this->parameters); + + if (null === $this->translator || false === $this->translationDomain) { + return strtr($this->messageTemplate, $parameters); + } + + return $this->translator->trans( + $this->messageTemplate, + $parameters, + $this->translationDomain + ); } } diff --git a/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilderInterface.php b/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilderInterface.php index 195dec924f08d..ecbcf4e6adce1 100644 --- a/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilderInterface.php +++ b/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilderInterface.php @@ -11,8 +11,10 @@ namespace Symfony\Component\Validator\Violation; +use Symfony\Component\Validator\ConstraintViolationInterface; + /** - * Builds {@link \Symfony\Component\Validator\ConstraintViolationInterface} + * Builds {@link ConstraintViolationInterface} * objects. * * Use the various methods on this interface to configure the built violation. @@ -20,6 +22,9 @@ * execution context. * * @author Bernhard Schussek + * + * @method ConstraintViolationInterface getViolation() + * @method $this setPath(string $path) */ interface ConstraintViolationBuilderInterface {