Skip to content

[Validator] ConstraintViolationBuilder: fromViolation(), addViolation(), setPath() #60746

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: 7.4
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/Symfony/Component/Validator/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
CHANGELOG
=========

7.4
---

* Add `ConstraintViolationBuilder` methods: `fromViolation()`, `setPath()`, `getViolation()`

7.3
---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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);
Expand Down Expand Up @@ -79,6 +108,7 @@ public function setTranslationDomain(string $translationDomain): static
public function disableTranslation(): static
{
$this->translationDomain = false;
$this->translator = null;

return $this;
}
Expand Down Expand Up @@ -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,
Expand All @@ -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
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,20 @@

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.
* Finally, call {@link addViolation()} to add the violation to the current
* execution context.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @method ConstraintViolationInterface getViolation()
* @method $this setPath(string $path)
*/
interface ConstraintViolationBuilderInterface
{
Expand Down
Loading