Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[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

Conversation

rela589n
Copy link
Contributor

Q A
Branch? 7.4
Bug fix? no
New feature? yes
Deprecations? no
Issues #58029 (comment)
License MIT

This PR: (1) adds the ability to create constraint violation builder from an existing violation (static factory method: ConstraintViolationBuilder::fromViolation($violation)) so that it can be adjusted in the builder, in particular (2) setPath() method, and finally retrieve new violation with (3) getViolation() method.

…ult)

This PR was merged into the 7.4 branch.

Discussion
----------

Replace `get_class()` calls by `::class`

| Q             | A
| ------------- | ---
| Branch?       | 7.4
| Bug fix?      | no
| New feature?  | no
| Deprecations? | no
| Issues        | Fix #... <!-- prefix each issue number with "Fix #", no need to create an issue if none exists, explain below instead -->
| License       | MIT

Replace `get_class()` by `::class`
It was already done in past in symfony#47401

Commits
-------

e0a602b Replace get_class() calls by ::class
Copy link
Member

@nicolas-grekas nicolas-grekas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a line to the changelog of the component also

*
* @return $this
*
* @see \Symfony\Contracts\Translation\TranslatorInterface::trans()
*/
public function setPlural(int $number): static;
public function setPlural(?int $number): static;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a BC break
looking at the decoration test case, this might not be needed - instead, don't call setPlural if getPlural returns null

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're absolutely right, thank you for pointing this out
for some reason I didn't think about decoration 🫤

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've changed the interface

What about ConstarintViolationBuilder itself?

@@ -90,7 +120,7 @@ public function setInvalidValue(mixed $invalidValue): static
return $this;
}

public function setPlural(int $number): static
public function setPlural(?int $number): static
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see my concern about this change in the interface

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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?
is this change released on a new major version as bc break? is there any deprecation of "not using new type"?

@@ -27,24 +28,52 @@
class ConstraintViolationBuilder implements ConstraintViolationBuilderInterface
{
private string $propertyPath;
private ?string $message = null;
Copy link
Member

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:

Suggested change
private ?string $message = null;
private string $translatedMessage;

Copy link
Contributor Author

@rela589n rela589n May 31, 2025

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

private ?Constraint $constraint,
private string|\Stringable $message,
private string|\Stringable $messageTemplate,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's keep the previous name

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was motivated by using the same naming as in ConstraintViolation, as these properties are named there as $message and $messageTemplate.

Naming them as $message and $translatedMessage here would confuse the picture, since $message means different things. Though, if you'd like them to be named this way, I will make a change.

Frankly speaking I myself thought about adding $translatedMessage, and letting the original $message alone, because the rename might introduce a BC break, as changing parameter name from message to messageTemplate would break the invocation code if it passes it as a named parameter.

Though, I've checked BC promise, and it says:

[10] Parameter names are only covered by the compatibility promise for constructors of Attribute classes. Using PHP named arguments might break your code when upgrading to newer Symfony versions.

[11] Only optional argument(s) of a constructor at last position may be added.

So, keeping it unified looks better to me, but in any case, feel free to ask what you will

crydotsnake and others added 8 commits May 30, 2025 17:28
…nt (crydotsnake)

This PR was squashed before being merged into the 7.4 branch.

Discussion
----------

[Dotenv] improve documentation for dotenv component

| Q             | A
| ------------- | ---
| Branch?       | 7.4
| Bug fix?      | no
| New feature?  | no
| Deprecations? | no
| Issues        | -
| License       | MIT

Improves the documentation for the Symfony dotenv component :)

Commits
-------

adfc7e9 [Dotenv] improve documentation for dotenv component
…, `DateType` and `TimeType` (wkania)

This PR was merged into the 7.4 branch.

Discussion
----------

[Form] Add `input=date_point` to `DateTimeType`, `DateType` and `TimeType`

| Q             | A
| ------------- | ---
| Branch?       | 7.4
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Issues        |
| License       | MIT

Based on [datetime_immutable](https://symfony.com/blog/new-in-symfony-4-1-added-support-for-immutable-dates-in-forms).

After [DatePointType](symfony#59900) and [DatePointDateType](symfony#60237), it would be great to use Forms without needing to transform values into the DatePoint type manually.

```
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Extension\Core\Type\TimeType;
use Symfony\Component\Form\Extension\Core\Type\BirthdayType;

$builder->add('from', DateType::class, [
    'input' => 'date_point',
]);
$builder->add('from', DateTimeType::class, [
    'input' => 'date_point',
]);
$builder->add('from', TimeType::class, [
    'input' => 'date_point',
]);
$builder->add('from', BirthdayType::class, [
    'input' => 'date_point',
]);
```

Alternative: Make symfony/clock a hard requirement and refactor the existing DateTimeImmutableToDateTimeTransformer to return a DatePoint instead. This should not introduce any breaking changes.

Commits
-------

f1160d6 [Form] Add input=date_point to DateTimeType, DateType and TimeType
…rovider service registration on the schedule name (adrianrudnik)

This PR was squashed before being merged into the 7.4 branch.

Discussion
----------

[Scheduler] Throw error on duplicate schedule provider service registration on the schedule name

| Q             | A
| ------------- | ---
| Branch?       | 7.4
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Issues        | See below
| License       | MIT

The way the schedule providers work, either by tagging a service or using the [AsSchedule](https://github.com/symfony/symfony/blob/7.3/src/Symfony/Component/Scheduler/Attribute/AsSchedule.php) attribute, can lead to a scenario where multiple [ScheduleProviders](https://github.com/symfony/symfony/blob/7.3/src/Symfony/Component/Scheduler/ScheduleProviderInterface.php) register for the same schedule name (e.g. `default`).

The problem arises when the [CompilerPass](https://github.com/symfony/symfony/blob/29da4f53d4a5b6a48ae8b43330e764a86ad7a85b/src/Symfony/Component/Scheduler/DependencyInjection/AddScheduleMessengerPass.php#L49) simply overwrites the previously registered one without throwing a warning, a notice or anything else. The problem would be that a full ScheduleProvider would simply not register and therefore not run.

I decided to use the InvalidArgumentException from DI, as I could not get a trigger_error on E_USER_WARNING to show anything in a `dev` environment, in case I miss a scenario where this would actually be a desirable use case.

Not sure if the test is complete enough for this scenario. I tried to base it on others found in HttpKernel.

Also not sure if this falls under "feature" or "bug fix", so I marked it as a feature above.

Commits
-------

8548aac [Scheduler] Throw error on duplicate schedule provider service registration on the schedule name
@rela589n rela589n force-pushed the feat-constraint-violation-builder-from-violation branch from 4cf1543 to 20f264b Compare May 31, 2025 10:44
@rela589n rela589n changed the title [Validator] Add ConstraintViolationBuilderInterface methods: fromViolation(), setPath(), getViolation() [Validator] Add ConstraintViolationBuilder methods: fromViolation(), setPath(), getViolation() May 31, 2025
Copy link
Member

@xabbuh xabbuh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see that these are valuable changes as they change the ConstraintViolationBuilder class in a way that makes it less obvious to use. Thus I am 👎 here.

private ?int $plural = null;
private ?string $code = null;
private mixed $cause = null;

public function __construct(
private ConstraintViolationList $violations,
private ?ConstraintViolationListInterface $violations,
Copy link
Member

Choose a reason for hiding this comment

The 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 ConstraintViolationBuilder class to decide whether or not you can call addViolation().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xabbuh , what do you propose instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to decide whether or not you can call

how often is it necessary to "decide" in the way you say it?

afaik, addViolation is only called in custom validators that create violation builder using execution context, and it itself provides ConstraintViolationList

So what is the point in "deciding"?

Copy link
Member

Choose a reason for hiding this comment

The 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 ChoiceValidator):

$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 ConstraintViolationBuilderInterface implementation returned by buildViolation() would throw or not.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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?
It will never throw

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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?

Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, and I believe that these two must be kept in one. And if one axis is to be extended, the other must be nullable

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xabbuh , can you concede with this?

->setCause($violation->getCause());
}

public function setPath(string $path): static
Copy link
Member

Choose a reason for hiding this comment

The 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 atPath() method and by looking at the names it's not obvious how they differ.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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()

* 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 <[email protected]>
*
* @method ConstraintViolationInterface getViolation()
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Guys I sincerely believe that this is the only way that can make the existing code applicable for the features that involve violation building

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is one last answer from my side: The changes that you proposed will not be merged for the reasons I outlined. If you really need the class to be this way, you will have to ship it with your library by implementing the interface yourself.

In summary: We won’t merge this. You are not blocked because you can implement the class in userspace. That’s it.

And now I am out here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would you do it if you your self needed it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @xabbuh explained how already. E.g. copy/pasting the existing code and tweaking it on your side to fit your need.
I'm now locking this discussion because it's too time consuming and the matter has been handled on our side.

@xabbuh
Copy link
Member

xabbuh commented Jun 1, 2025

Re-reading the changes I am 👎 on merging these changes. I also doubt that the described use case is common that we need to provide a solution for it in core. You can easily ship a custom ConstraintViolationBuilderInterface implementation in your application/library if you see fit for it in you specific use case though.

@rela589n
Copy link
Contributor Author

rela589n commented Jun 2, 2025

I am 👎 on merging these changes.

Thanks for being honest.

doubt that the described use case is common that we need to provide a

As @fabpot said: "there are always some missing feature that can be added..."

So I believe that this is useful feature, especially for the clients that can't customize validation because it's in the vendor, and they
need to customize property path for example. No way but copying existing constraint violation, and migrating all fields but one into the new object.

@rela589n rela589n requested a review from nicolas-grekas June 2, 2025 05:59
@nicolas-grekas
Copy link
Member

Thanks @xabbuh for the review, I understand and your concerns look valid to me. I also agree the target goal would be achieved by a dedicated builder.
Thanks @rela589n for sharing your proposal. This makes the existing code more fragile and it looks like there's a better way, so I doubt anybody the core-team will approve now that this has been spotted.

@rela589n
Copy link
Contributor Author

rela589n commented Jun 2, 2025

Guys, but what's wrong with the approach?

I honestly do not understand.

@xabbuh
Copy link
Member

xabbuh commented Jun 2, 2025

I outlined the flaws I see with your changes above. I am sorry if that's not clear to you but I don't know how to phrase that in a different way.

The good news is though that you can easily implement the class you have in mind in your application and use it the way you see it so even if your changes are not going to be merged into the Symfony codebase, you are not blocked in using a violation builder the way you see it.

@symfony symfony locked as resolved and limited conversation to collaborators Jun 9, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants