-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Validator] Add ConstraintViolationBuilder methods: fromViolation(), setPath(), getViolation() #60582
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
[Validator] Add ConstraintViolationBuilder methods: fromViolation(), setPath(), getViolation() #60582
Changes from 1 commit
e0a602b
d92477a
adfc7e9
c7d7543
f1160d6
21c1205
8548aac
f8e605c
29055ef
20f264b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
…setPath(), getViolation()
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Making this property nullable doesn't look good to me as you would now have to be aware of the inner state of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @xabbuh , what do you propose instead? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
how often is it necessary to "decide" in the way you say it? afaik, So what is the point in "deciding"? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Take a look at this arbitrary example taken from the Symfony code base (from the $this->context->buildViolation($constraint->maxMessage)
->setParameter('{{ limit }}', $constraint->max)
->setPlural((int) $constraint->max)
->setCode(Choice::TOO_MANY_ERROR)
->addViolation(); You wouldn't know here if the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, but what do you mean that we wouldn't know if it will throw or not? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a core design principle: not allowing clumsy behavior by making the design forbid them. Weakening means increasing the risk for others, while the benefit we'd get is to cover a niche use case that can be implemented another way. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually I do not like the idea of using constraint violation builder both for building and adding violations to the list. Though, in current circumstances when there's exactly one builder that is used both to build and to add violations, what's the best way? A separate class that entirely duplicates the current builder but There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that building and adding violations are two different purposes, but isn't it what already happens in the class? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @nicolas-grekas , @xabbuh , what do you think about the fact that this is already so? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we would do things differently when we were to build the component again and split responsibility here. But given the impact that would have if we split the current implementation this is not going to happen. |
||
private ?Constraint $constraint, | ||
private string|\Stringable $message, | ||
private string|\Stringable $messageTemplate, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's keep the previous name There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was motivated by using the same naming as in Naming them as Frankly speaking I myself thought about adding Though, I've checked BC promise, and it says:
So, keeping it unified looks better to me, but in any case, feel free to ask what you will |
||
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(); | ||
|
||
return $builder | ||
->setPlural($violation->getPlural()) | ||
->setCode($violation->getCode()) | ||
->setCause($violation->getCause()); | ||
} | ||
|
||
public function setPath(string $path): static | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. having this method is probably confusing for users of this class given that we already have an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In most programming languages, setter methods (which modify private variables) are conventionally named by starting with "set" followed by the variable name, with the first letter of the variable name capitalized. For example, if a variable is named firstName, the setter would be setFirstName() |
||
{ | ||
$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; | ||
} | ||
|
@@ -90,7 +120,7 @@ public function setInvalidValue(mixed $invalidValue): static | |
return $this; | ||
} | ||
|
||
public function setPlural(int $number): static | ||
public function setPlural(?int $number): static | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. see my concern about this change in the interface There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. changed the interface btw, from my point question arises: if at any point we'd like to extend interface, how is it handled? |
||
{ | ||
$this->plural = $number; | ||
|
||
|
@@ -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('Cannot add a violation without an 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 | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding this method adds a complete new meaning to the interface. I don't feel that it's a good idea having the same interface serve two different purposes. |
||
* @method $this setPath(string $path) | ||
*/ | ||
interface ConstraintViolationBuilderInterface | ||
{ | ||
|
@@ -84,13 +89,13 @@ public function setInvalidValue(mixed $invalidValue): static; | |
* Sets the number which determines how the plural form of the violation | ||
* message is chosen when it is translated. | ||
* | ||
* @param int $number The number for determining the plural form | ||
* @param int|null $number The number for determining the plural form | ||
* | ||
* @return $this | ||
* | ||
* @see \Symfony\Contracts\Translation\TranslatorInterface::trans() | ||
*/ | ||
public function setPlural(int $number): static; | ||
public function setPlural(?int $number): static; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is a BC break There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you're absolutely right, thank you for pointing this out There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've changed the interface What about |
||
|
||
/** | ||
* Sets the violation code. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need to make this nullable:
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it the point that it will be eventually initialized?
right now this is only initialized as
$this->message ??= $this->translateMessage()
any access to this property prior to that place will throw an exception