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

Skip to content

[Validator] Added error codes to all constraints with multiple error causes #12021

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

Merged
merged 1 commit into from
Sep 30, 2014

Conversation

webmozart
Copy link
Contributor

Q A
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets #7276
License MIT
Doc PR TODO

This PR depends on #12015 and #12016 being merged first. However, a few changes in 52cb7df first must be backported to #12016.

This PR introduces error codes for all constraints with multiple error paths. This lets you determine what exactly the reason was that a constraint failed:

$validator = Validation::createValidator();

$violations = $validator->validate('0-4X19-92619812', new Isbn());

foreach ($violations as $violation) {
    var_dump($violation->getCode());
    // => int(3)
    var_dump(Isbn::getErrorName($violation->getCode()));
    // => string(24) "ERROR_INVALID_CHARACTERS"
    var_dump($violation->getConstraint()->getErrorName($violation->getCode()));
    // => string(24) "ERROR_INVALID_CHARACTERS"
}

The getErrorName() method is especially helpful for REST APIs, where you can return both an error code and a description of that error now.

Todos

@Koc
Copy link
Contributor

Koc commented Sep 24, 2014

What way to determinate what concrete constraint failed? foreach + instanceof?

@Koc
Copy link
Contributor

Koc commented Sep 24, 2014

Maybe can we use reflection for generation error names from constants name by they values?

@webmozart
Copy link
Contributor Author

@Koc Just check $violation->getConstraint().

Reflection is slow. We won't change the error codes very often, so an explicit mapping should be fine.

@stof
Copy link
Member

stof commented Sep 25, 2014

@fabpot can you merge older branches into master so that this PR can be rebased ? It will be easier to review it when the diff does not include the changes of previous PRs

@webmozart
Copy link
Contributor Author

@stof As soon as I have a go on #12016, I'll merge everything and rebase this PR.

@stof
Copy link
Member

stof commented Sep 25, 2014

I think you used the wrong issue number in your comment

@webmozart
Copy link
Contributor Author

@stof No I haven't... ;)

@stof
Copy link
Member

stof commented Sep 25, 2014

you cheated 😄

fabpot added a commit that referenced this pull request Sep 25, 2014
…lper for BC with the 2.4 API (webmozart)

This PR was merged into the 2.5 branch.

Discussion
----------

[Validator] Added ConstraintValidator::buildViolation() helper for BC with the 2.4 API

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | -

This PR adds a `buildViolation()` helper method to the base `ConstraintValidator` to remove the API checks (2.4 vs. 2.5) from the constraint validator implementations. Once the 2.4 API is removed, this helper method will be removed as well.

**Todos**

- [x] Backport changes from #12021

Commits
-------

6b0c24a [Validator] Added ConstraintValidator::buildViolation() helper for BC with 2.4 API
@webmozart
Copy link
Contributor Author

This is rebased now. I also improved the UUID validator to give more helpful error codes.

@webmozart
Copy link
Contributor Author

Ready for review+merge.

ping @symfony/deciders

@webmozart webmozart force-pushed the issue7276-2 branch 7 times, most recently from de62121 to 18d8ece Compare September 25, 2014 18:04
@webmozart
Copy link
Contributor Author

@weaverryan Could you check the names of the error constants for linguistic mistakes?

*/
const ERR_INVALID = 1;

protected static $errorNames = array(
Copy link
Member

Choose a reason for hiding this comment

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

could it be private instead ?

Copy link
Member

Choose a reason for hiding this comment

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

hmm, no sorry, forget it, it overwrites the parent ones

@mickaelandrieu
Copy link
Contributor

👍

*/
class CardScheme extends Constraint
{
const ERROR_NOT_NUMERIC = 1;
Copy link
Member

Choose a reason for hiding this comment

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

This will cause issues in the form binding, as it checks for $allowNonSynchronized = Form::ERROR_NOT_SYNCHRONIZED === $violation->getCode();, and Form::ERROR_NOT_SYNCHRONIZED is 1 as well.

However, we probably need to check the violation constraint instead to ensure it is a Form constraint (we cannot be sure that the whole ecosystem will never reuse the value 1)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

@webmozart
Copy link
Contributor Author

I changed all constant names as suggested. @weaverryan I'd still like to have a check from a native speaker, if possible.

@Tobion I removed the internal flag and changed the type hint to "int" for setCode(). One should always use integers as error codes, even though this is not technically enforced at the moment for BC reasons.

@Tobion
Copy link
Contributor

Tobion commented Sep 26, 2014

there are still some mixed code references like https://github.com/webmozart/symfony/blob/issue7276-2/src/Symfony/Component/Validator/ConstraintViolationInterface.php#L133 (should probably be int|null) and https://github.com/webmozart/symfony/blob/issue7276-2/src/Symfony/Component/Validator/ConstraintViolation.php#L81 and some @var mixed code properties. But as the code can also be null, getErrorName does not work with this. So your snippet var_dump($violation->getConstraint()->getErrorName($violation->getCode())); is not safe to call.

@webmozart
Copy link
Contributor Author

@Tobion We can't change those. Since the old addViolation() is tagged as API and has the "mixed" type, that must be the same for the ConstraintViolation constructor and the getCode().

$violation->getConstraint()->getErrorName($violation->getCode()) is safe to call as long as the constraint is a core constraint (and as long as $violation->getCode() is not null). The code is guaranteed to be set by $violation->getConstraint(), so it also must be known to its getErrorName().

For generic code that must work with userland constraints also, you should wrap the call to getErrorName()within a try-catch statement. If no name can be found for a code, an exception will be thrown.

@webmozart
Copy link
Contributor Author

ping @symfony/deciders

I'd like to merge this today.

@webmozart webmozart merged commit 3b50bf2 into symfony:master Sep 30, 2014
webmozart added a commit that referenced this pull request Sep 30, 2014
…multiple error causes (webmozart)

This PR was merged into the 2.6-dev branch.

Discussion
----------

[Validator] Added error codes to all constraints with multiple error causes

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #7276
| License       | MIT
| Doc PR        | TODO

This PR depends on #12015 and #12016 being merged first. However, a few changes in 52cb7df first must be backported to #12016.

This PR introduces error codes for all constraints with multiple error paths. This lets you determine what exactly the reason was that a constraint failed:

```php
$validator = Validation::createValidator();

$violations = $validator->validate('0-4X19-92619812', new Isbn());

foreach ($violations as $violation) {
    var_dump($violation->getCode());
    // => int(3)
    var_dump(Isbn::getErrorName($violation->getCode()));
    // => string(24) "ERROR_INVALID_CHARACTERS"
    var_dump($violation->getConstraint()->getErrorName($violation->getCode()));
    // => string(24) "ERROR_INVALID_CHARACTERS"
}
```

The `getErrorName()` method is especially helpful for REST APIs, where you can return both an error code and a description of that error now.

**Todos**

- [x] Backport a few structural changes to #12016
- [x] Update constraints outside of the Validator component
- [x] Rebase on master (after merging #12015 and #12016)

Commits
-------

3b50bf2 [Validator] Added error codes to all constraints with multiple error causes
@Tobion
Copy link
Contributor

Tobion commented Sep 30, 2014

@webmozart I still don't see your argument that the error code can be used in a REST API. The error code is not unique, so if a users reports he received error_code: 1 it basically doesn't say anything to you.

@@ -93,11 +93,9 @@ public function setPlural($number);
/**
* Sets the violation code.
*
* @param mixed $code The violation code
* @param int $code The violation code
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think it makes sense to only change it to int here when every other code in the validator component is said to be mixed.

Copy link
Contributor

Choose a reason for hiding this comment

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

@webmozart
Copy link
Contributor Author

@Tobion Of course you have to provide the name of the failing constraint as well, otherwise the code is useless.

We could make sure that the errors are unique by collecting them in a single class instead of within the individual constraints. Do you think that is a better solution?

Sketch:

namespace Symfony\Component\Validator;

final class Error
{
    const FILE_NOT_FOUND = 100;
    const FILE_EMPTY = 101;
    const IBAN_TOO_SHORT = 200;

    private function __construct() { ... }
}

$context->buildViolation('Error!')
    ->setCode(Error::FILE_NOT_FOUND)
    ->addViolation();

if (Error::FILE_NOT_FOUND === $violation->getCode()) {
    // ...
}

@Tobion
Copy link
Contributor

Tobion commented Sep 30, 2014

Well, the name must not really be unique as well. For example there are some as generic as INVALID_CHARACTERS which would fit to almost all violations.

Collecting errors in one place is worse IMO and also would not work with custom constraints anyway.
Maybe the error name must automatically append the constraint class or something like that to be able to really identify the source problem. On the other hand the class name + namespace is maybe not what people want to expose.

@merk
Copy link
Contributor

merk commented Sep 30, 2014

I dont see an issue with providing the constraint name along with the error code? It seems much more flexible than collecting the error codes into a single class or trying to ensure they're unique.

@aeoris
Copy link
Contributor

aeoris commented Oct 1, 2014

@webmozart thanks for your work on that.

Just my two cents: I still sort of prefer something unique-ish like an UUID, but I guess the constraint name + code is enough for me. I don't see the need of dealing with two keys (constraint, code) when the API consumers -which would be my main use case for this feature- can do fine with just a single one (code/uuid).

@webmozart
Copy link
Contributor Author

@Tobion I understand from your reply that you're not happy with the current solution, but what do you suggest to improve it?

@stof
Copy link
Member

stof commented Oct 1, 2014

IMO, the solution depends on the use case. For many cases, the error name is probably a better fit for the REST API than the numeric code. It will also make it easier to understand for people debugging their usage (as the error name can be understood). To make it useful, we just need to make sure that we don't use the same name for things which have actually different meaning (but this is not likely for names). But we don't need to ensure uniqueness. TOO_SHORT_ERROR is understandable and usable even when several constraints can complain about the value being too short.

@webmozart webmozart deleted the issue7276-2 branch October 22, 2014 17:21
@lucascourot
Copy link

👍

This reminds me of an issue I opened on the FosRestBundle repo few weeks ago FriendsOfSymfony/FOSRestBundle#842.

I needed an error code (error constant name or error numeric code) for my REST API as I wanted to obtain something like this http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api#errors

By the way, have you seen this bundle which helps handling errors in REST APIs? https://github.com/TheBigBrainsCompany/TbbcRestUtilBundle/blob/master/README.md

@stanlemon
Copy link

This would be awesome to have! 👍

@lsmith77
Copy link
Contributor

@lucascourot there are quite a few form related tickets open on the FOSRestBundle that need attention. Since I am personally not using the form component with FOSRestBundle (mostly because a large chunk of the REST related stuff I do is read only APIs) I need helping hands.

@lucascourot
Copy link

@lsmith77 I think my issue was mainly related to the fact that it wasn't possible to associate an error code to a validation constraint. Unless you extended each constraint class and added an error code attribute.
This PR fixes the problem.

@webmozart
Copy link
Contributor Author

@stanlemon This is merged already. The question is:

Right now, only those constraints which have multiple error causes have error codes. For example:

// caused by NotNull
$violation->getCode();
// => null

// caused by Length
$violation->getCode()
// => 1

Length::getErrorName($violation->getCode());
$violation->getConstraint()->getErrorName($violation->getCode());
// => "TOO_SHORT_ERROR"

Also, neither the numeric codes nor the error names are globally unique. So there are many constraints with the error code 1, there are also many constraints with a TOO_SHORT_ERROR. Hence the API output is only useful if you indicate the code/error name together with the name of the constraint causing that error.

Now my question: Is this a problem? Or do you prefer globally unique codes so that you know from the code exactly which constraint caused the error and what error it is? We could achieve this by replacing integers by UUIDs.

@stof
Copy link
Member

stof commented Oct 23, 2014

@webmozart but TOO_SHORT_ERROR is clear in all cases even if it is used in several places, because it has the same meaing in all cases: the value is too short

@Tobion
Copy link
Contributor

Tobion commented Oct 23, 2014

@stof I think you missed to point of the codes. That the value is too short is already clear from the message. The code is there so that devs can debug where (Length Constraint) and for what reason inside the constraint the validation failed.

@stof
Copy link
Member

stof commented Oct 23, 2014

@Tobion but for Rest API, most of the time, this code will be enough (while relying on the message itself to identify it is too short is not, given that the message can be updated to fix a typo in the wording for instance)

@Tobion
Copy link
Contributor

Tobion commented Oct 23, 2014

Yes "most of the time". But I prefer a system that covers it's intended functionality all the time.

@Tobion
Copy link
Contributor

Tobion commented Aug 22, 2017

For reference the follow up change: #15154

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.