-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Uid] Use more concrete exception classes #60154
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
[Uid] Use more concrete exception classes #60154
Conversation
Hey! I see that this is your first PR. That is great! Welcome! Symfony has a contribution guide which I suggest you to read. In short:
Review the GitHub status checks of your pull request and try to solve the reported issues. If some tests are failing, try to see if they are failing because of this change. When two Symfony core team members approve this change, it will be merged and you will become an official Symfony contributor! I am going to sit back now and wait for the reviews. Cheers! Carsonbot |
chore: tweak CHANGELOG Co-authored-by: Fabien Potencier <[email protected]>
6752938
to
3802394
Compare
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.
What about a single InvalidUidException?
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.
Thinking again about this: can you expand on the use case of exposing invalid type+value info? Since they're invalid, I'm not sure it make sense (you still have the original value when calling the methods that throw if really needed.)
Actually you're right that client code isn't likely to benefit from having these methods, since usually you don't catch these exceptions by yourself. Yet, it makes a lot of sense when you are using exception handling library. Herewith, Value - is the nitty gritty thing we can use to distinguish between two properties both of which are to contain uuid. For example, if we do not distinguish by value, then we won't be able to properly map #[ExceptionalValidation]
class TransferMoneyCommand
{
#[Capture(InvalidUuidException::class)]
#[Capture(NotEnoughMoneyException::class)]
public readonly string $fromCardId;
#[Capture(InvalidUuidException::class)]
public readonly string $toCardId;
}
// note that second uuid is invalid
$command = new TransferMoneyCommand('eb27966e-b78f-7afc-96c5-cd6e18c37616', '!not valid!');
try {
// handler will call `Uuid::fromString()` for both `fromCardId` and `toCardId`
$commandBus->process($command);
// middleware throws exception with violations created from mapped exceptions
} catch (ExceptionalValidationFailedException $e) {
$violationList = $e->getViolationList();
// This violation property path is incorrect, since invalid value was in `toCardId`, not `fromCardId`...
// $violationList[0].propertyPath = 'fromCardId'
// $violationList[0].invalidValue = '!not valid!'
} Yet, if we had final class InvalidUuidValueMatchCondition implements MatchCondition
{
public function __construct(
private readonly mixed $value,
) {
}
/** @param InvalidUuidException $exception */
public function matches(Throwable $exception): bool
{
return $exception->getValue() === $this->value;
}
} And then, if #[Capture(InvalidUuidException::class, condition: InvalidUuidValueMatchCondition::class)]
#[Capture(NotEnoughMoneyException::class)]
public readonly string $fromCardId; Thereby, it results in correct property path mapping: try {
// ...
} catch (ExceptionalValidationFailedException $e) {
$violationList = $e->getViolationList();
// Correct:
// $violationList[0].propertyPath = 'toCardId'
// $violationList[0].invalidValue = '!not valid!'
} |
Shouldn't you match by name instead of value? |
What name do you mean to match by? Name of the exception? Yes, we match by exception name two properties:
If we do not add getters, it would be necessary to parse the exception message in order to get the value, and that's why I'm proposing this PR so that it'd be possible to match by exception value w/o parsing anything. Maybe it's not so clear what I'm talking about, if it's the case, I think I could come up with some diagram that will explain what I'm trying to do. |
I thought about property names. Don't miss this comment also:
|
Sorry if it's not clear right off the way. I'll try to put it a little more easier way. Let's see two approaches that should lead to the same result. First - standard validation. It looks like this: class TransferMoneyCommand
{
#[Assert\Uuid()]
public string $fromCardId;
#[Assert\Uuid()]
public string $toCardId;
} Having object ( Second approach is exceptional validation, looks like this: class TransferMoneyCommand
{
#[Capture(InvalidUuidException::class)]
public string $fromCardId;
#[Capture(InvalidUuidException::class)]
public string $toCardId;
} The same object ( Here violation is based on already thrown exception from the client code, and more explanation can be found here on the diagram. TLDR, it's namely the following: // code to get property path:
try {
// @@ $stack->next($envelope)
} catch (InvalidUuidException $exception) {
$propertyPath = null;
if ($exception->getValue() === $command->fromCardId) {
$propertyPath = 'fromCardId';
}
if ($exception->getValue() === $command->toCardId) {
$propertyPath = 'toCardId';
}
// @@ process the violation
} Therefore it's not possible to know where this If there's Please, let me know this clarifies why |
This looks fragile design to me. Somewhere is the stack, it would make sense to be able to know which property you're checking, without guessing by value. Could be by wrapping the Uid exception in another one that gives the property path for example. That might be a better way forward to cover your need. |
do you mean creating one exception per each property type we check? |
I'm not int the inner of the validation lib, but at some point, properties are going to be checked one by one, and at each step, one knows the corresponding propertyPath. Can't this info be used in some violation object? |
The point here is that it's not the validation lib. It's exception processing lib.
|
Regarding For example, assuming we use single exception, there could be edge case when there are both ulid and uuid in single place: class SomeCommand
{
#[Capture(InvalidUidException::class)]
public string $ulid;
#[Capture(InvalidUidException::class)]
public string $uuid;
} If client code gets SomeCommand( $ulid = Ulid::fromString($command->ulid);
$uuid = Uuid::fromString($command->uuid); // throws This exception should be mapped to Yet, since Therefore, if possible, I'd ask to keep them as separate exceptions. |
👍 |
Hi, @smnandre, I think you have missed my latest response regarding Could you please check it? |
@rela589n I don't see why having 2 separate extension classes is necessary to handle the case of having 1 Uuid property and 1 Ulid property. Wouldn't you have the same issue in case you have 2 Uuid properties (which will still throw the same exception class) ? |
The more I ask (thanks a lot for your answer), the more I find the motivation for the PR inadequate for Symfony. E.g the argument to split in two base types is for your lib to be able to differentiate those two cases more accurately. This has nothing to do with why Symfony should do it. I'm sorry to say so, but I feel like the way this lib works is by doing fragile guesswork. The ideal world for this lib would be one where every exception reports every details about it in a structured form. Since this world doesn't exist, I understand why you're contributing some improvements to Symfony: it's widely used so making it report back what the lib needs has a nice impact on your side. Yet, this vision looks fundamentally unachievable to me, because it relies on ideally being able to patch all exceptions thrown by every existing libs that could end up being turned into validation errors. About the PR, to still help move forward: I'm fine throwing component-specific base types for the component. We do so already in other components. But not for reporting back detailed stuff just because one lib with a questionable approach needs that, sorry. |
This gives context of the value (uuid or ulid), so that
It wouldn't. Having two uuid string properties with the same value, it wouldn't matter if exception is mapped to the first property, or to the second, as both are invalid. In case of two different property values, it wouldn't either, since value is considered as well. |
I acknowledge that Symfony is your framework, and it's completely up to you to decide what will come in, and what will not.
You don't have to be soory, I understand that your opinion differs from mine, and I'm sure that you have prerequisites for it.
Actually, this isn't that big. First is That's more than 90% of most commonly needed things. The rest 10% can be solved with custom exception in the client code.
Yes, that's why I contribute. Symfony is the best framework, suitable for the best tasks on the best projects. My point is following: this change would allow many people to benefit from a beautiful domain, expressed with SF Components, backed by this lib on the level above. I recognize that this approach might not be reasonalbe for you, and you might not like it, and it is completely in your sovereignty to support or reject it. Yet, if you would relent on this, it would mean really a lot to me, and I would be really thankful. |
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.
Let me close as I think parsing the message will work well enough for you and the change doesn't make much sense on the side of Symfony to me.
Thanks for you understanding and for submitting!
@nicolas-grekas , I still desire to have it as a part of symfony Is there particular reason for not having it? |
This PR was merged into the 7.3 branch. Discussion ---------- [Uid] Add component-specific exception classes | Q | A | ------------- | --- | Branch? | 7.3 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | - | License | MIT Generalization of #60154 /cc `@rela589n` Commits ------- e86ffe3 [Uid] Add component-specific exception classes
Hello
This PR introduces two exception classes:
InvalidUuidException
andInvalidUlidException
that replace\InvalidArgumentException
thrown from the constructor.In particular, this is useful if we rely on exceptions as a primary source of validation.