throw an exception if required filter is not set#1692
Conversation
653213d to
e982f80
Compare
|
I'm glad that you work on this! Thanks. Do we really need a dependency to the Symfony validator for that? Can't we just use the filter extension? It will reusing API Platform in a non-Symfony context (PSR-7 and Laravel support is on our roadmap). About the namespace, I would suggest to put it in the |
e982f80 to
af2baa6
Compare
|
@dunglas I used Symfony validation for several reason:
Or maybe I did not really understand your comment ? |
af2baa6 to
1930621
Compare
|
The integration with the validator is 100% optional (as with Doctrine etc), in fact it's only used in the bridge for convenience for the end user, but IIRC, not for any "core" feature like this one. Regarding the 400, it's just one line to add to support a new exception: https://github.com/api-platform/core/blob/master/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php#L251 |
|
@dunglas OK for the I think it will be a big copy/paste from symfony's constraints, don't you ? About the |
1930621 to
d43e385
Compare
|
@dunglas I remove the dependency on Symfony validator service. About using the FilterExtension, I did not, because FilterExtension is Doctrine-specific and will not work when using resources that are not Doctrine's. I think it's good for merge. I will make other PR's implementing the rest of OpenApi parameters specification after that. |
72845d9 to
e2212dc
Compare
| namespace ApiPlatform\Core\Exception; | ||
|
|
||
| /** | ||
| * Deserialization exception. |
| /** | ||
| * Gets constraint violations related to this exception. | ||
| * | ||
| * @return ConstraintViolationListInterface |
There was a problem hiding this comment.
You can use a return type instead.
| use Symfony\Component\HttpKernel\Event\GetResponseEvent; | ||
|
|
||
| /** | ||
| * Validate query parameters depending on filter description. |
| } | ||
|
|
||
| foreach ($filter->getDescription($attributes['resource_class']) as $name => $data) { | ||
| if ($data['required'] ?? false) { |
There was a problem hiding this comment.
Can you invert this condition to reduce the complexity?
if (!($data['required'] ?? false)) {
continue;
}There was a problem hiding this comment.
I changed a litte this part to prepare the next step that will validate several parameter rules, but this should be more understandable though
| $errorList = []; | ||
|
|
||
| if (null === $request->query->get($name)) { | ||
| $errorList[] = sprintf('Query parameter `%s` is required', $name); |
There was a problem hiding this comment.
Can you change the backticks with double quotes for consistency with other exceptions?
| $errorList[] = sprintf('Query parameter `%s` is required', $name); | ||
| } | ||
|
|
||
| if (count($errorList) > 0) { |
There was a problem hiding this comment.
Can be simplified in if ($errorList) {
e2212dc to
47877d6
Compare
|
@dunglas done |
3d05d37 to
c76a40f
Compare
7add831 to
40b6fe2
Compare
| foreach ($filter->getDescription($attributes['resource_class']) as $name => $data) { | ||
| $errorList = []; | ||
|
|
||
| if (($data['required'] ?? false) && null === $request->query->get($name)) { |
There was a problem hiding this comment.
This condition will not work with serializer's filters (assuming they are required) for example. Indeed, property[] is not equal to property.
There was a problem hiding this comment.
Sorry but I did not really understand what you mean here ?
Can you provide an example when this does not work ?
There was a problem hiding this comment.
Let's take the description of the property filter:
core/src/Serializer/Filter/PropertyFilter.php
Lines 68 to 77 in 63d555a
And let's say
required is equal to true for our example.
So, the $name var will be equal to property[] here. But with ?property['relatedDummy'][]=name the name will be property in the parameter bag and $request->query->get('property[]') will return null...
| * | ||
| * @author Julien Deniau <julien.deniau@gmail.com> | ||
| */ | ||
| class QueryParameterValidateListener |
There was a problem hiding this comment.
Can you add a unit test please?
There was a problem hiding this comment.
Is it usefull ? It's fully tested with behat tests.
There was a problem hiding this comment.
Yes, Behat is only for integration and acceptance tests. You also have to unit test your class.
40b6fe2 to
8a39942
Compare
|
@meyerbaptiste Done for the unit tests and the "array notation" (but I'm not really happy with the implementation though :| ) |
bb98949 to
0ddfa2b
Compare
| continue; | ||
| } | ||
|
|
||
| if (false !== strpos($name, '[')) { // array notation of filter |
There was a problem hiding this comment.
I have another dirty solution:
parse_str($name, $query);
$name = array_keys($query)[0] ?? '';It works with:
'''foo''foo[]''foo[bar]''foo[bar][]''foo[bar][baz]'- etc.
😄
6c5432c to
e8375de
Compare
| private function isRequiredFilterValid($name, $request): bool | ||
| { | ||
| $matches = []; | ||
| if (false === mb_parse_str($name, $matches)) { |
There was a problem hiding this comment.
Huum, this requires the mbstring extension... And the mb_parse_str() function is not implemented by symfony/polyfill-mbstring!
There was a problem hiding this comment.
Oh yes, I though it was included since PHP7, my bad, I remove this
1388e44 to
a04ee65
Compare
|
@meyerbaptiste changed regex → parse_str |
a04ee65 to
ffb46ea
Compare
| use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; | ||
| use Doctrine\ORM\QueryBuilder; | ||
|
|
||
| class ArrayRequiredFilter extends AbstractFilter |
| use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; | ||
| use Doctrine\ORM\QueryBuilder; | ||
|
|
||
| class RequiredFilter extends AbstractFilter |
|
|
||
| ApiPlatform\Core\Tests\Fixtures\TestBundle\Filter\ArrayRequiredFilter: | ||
| arguments: [ '@doctrine' ] | ||
| tags: [ 'api_platform.filter' ] |
There was a problem hiding this comment.
we're not using spaces after [ IIRC in this case?
| private function isRequiredFilterValid($name, $request): bool | ||
| { | ||
| $matches = []; | ||
| parse_str($name, $matches); |
There was a problem hiding this comment.
I've mentioned this a long time ago, but we really should stop using bad built-in PHP functions like parse_str. We should seriously think about using a proper URI parsing library instead: https://uri.thephpleague.com/5.0/
https://uri.thephpleague.com/5.0/components/parsers/#queryparserextract
There was a problem hiding this comment.
Is this really necessary for this use case? Maybe can we do that at some point in another PR and replace all calls to parse_str in 1 time.
|
@dunglas I think it's better if we avoid giving the filter implementations the responsibility of checking required filters. It's not their job and it doesn't need to be. But I agree with extracting all logic into a separate class, so as to keep the event listeners really thin / "dumb". |
|
Introducing a new service and using it in the listener looks good to me. |
|
@soyuka about the "light" listener, the separation between the listener and the validation has been made in #1723 ( https://github.com/api-platform/core/pull/1723/files#diff-91ffc72a7db30ec816871e7141558def exactly ) : I prefer keeping code simple first, and re-factor it when needed. @dunglas About the namespace, we talked about it previously, but I can move the listener in EventListener and move back the Filter validator in Filter namespace. Can we keep the listener "as-is" for this PR, and make the refactoring in #1723, as the purpose of this PR is like a POC before #1723 ? (it's quite painful to rebase each time, and I think it will be more painful this time as the code is several months old). |
|
LGTM. |
c5cc04b to
462751b
Compare
|
@soyuka done |
462751b to
1e353af
Compare
|
@dunglas @meyerbaptiste @Simperfit @soyuka everything is OK on my part I think. Waiting for a merge on master to work on the second PR that will check all documented fields |
|
Thanks @jdeniau ! |
…ation More query parameter validation (follow #1692)
required=trueThis PR is a start of query parameter validation.
It does only check
required=truefilter for now but I will open other PR to include the rest of OpenAPI specification on field validation.There are some points that I am not really sure of:
QueryParameterValidateLister: I put it inCore\Validator\EventListenerbut maybe inSymfony\Bridge?Otherwise, the code is quite simple and leverage Symfony validation system.
Thanks !