Skip to content

Allow GraphQL to filter on nested property #1868

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 2 commits into from
Apr 20, 2018

Conversation

antograssiot
Copy link
Contributor

@antograssiot antograssiot commented Apr 15, 2018

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

Possible replacement for #1743 without updating the filter but just the way filters are passed to the data provider.

ping @alanpoulain @IonBazan

Copy link
Contributor

@IonBazan IonBazan left a comment

Choose a reason for hiding this comment

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

We should remember that it can produce a conflict when someone creates a filter with such name.

@IonBazan
Copy link
Contributor

Please also add a test for order and date filter to make sure it's ok.

@antograssiot
Copy link
Contributor Author

I will add it for date, ordering on nested property doesn't make much sense but I might be missing the point ?

@IonBazan
Copy link
Contributor

Consider fetching posts ordered by author's rating (stars).

@alanpoulain
Copy link
Member

alanpoulain commented Apr 15, 2018

I don't really like this way. The issue will be too obscure for an user if it used a custom filter with an underscore. I would prefer the other method.
Edit: Sorry I didn't see you add an alternative like the other method.

@antograssiot
Copy link
Contributor Author

Sure I figured out just after commenting. this patch will need more work because the keys of value would need to be cleaned when they are arrays and for the key order. I'll have a look tonight maybe.

@antograssiot
Copy link
Contributor Author

antograssiot commented Apr 15, 2018

So should I spend more time on this to get also the nested filed ordering to work @alanpoulain or should I close it of the other PR is a prefered solution and should be extented for OrderFilter ?

@alanpoulain
Copy link
Member

IMO, since it's a GraphQL-related issue, this solution is better.

@antograssiot
Copy link
Contributor Author

I've added a naive workaround so ordering on nesting property value would work now.
What's your opinion ?

@IonBazan
Copy link
Contributor

I wonder if it's possible to use nested property filters like:
dummies(relatedDummy: {name: "foo"}) and dummies(order: {relatedDummy: {name: "foo"}}) because in your solution, filters named: related_name and related.name are ambiguous.

@antograssiot
Copy link
Contributor Author

@IonBazan I don't know much about the graphQL part yet, but at least the solution is the same for all filters used in graphQL context without changing the way it works for standard Rest API, that's why I went with that.
Whatever the solution will be at the end, it should be properly documented I think...

@antograssiot
Copy link
Contributor Author

I do understand that for people using GraphQL only, it might be tricky to define filters using the dot notation and use and underscored notation when using it but I don't really see any other better solution for now...

{
$filters = $args;
foreach ($filters as $name => &$value) {
if ($name === $this->orderParameterName && \is_array($value)) {
Copy link
Member

Choose a reason for hiding this comment

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

Why not generalizing it for all filters (i.e. only verifying if $value is an array)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My first idea was that this is not necessary fo all filters (like DateFilter) for example and wanted to keep the modification as small as possible... but on a second thought, I don't see any issue in generalizing it.

Copy link
Member

Choose a reason for hiding this comment

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

And it simplifies the code 😉

When I add "Accept" header equal to "application/hal+json"
And I send a "GET" request to "/dummies?relatedDummies.name=RelatedDummy31"
Then the response status code should be 200
And the response should be in JSON
Copy link
Member

Choose a reason for hiding this comment

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

This test shouldn't be here.

When I send the following GraphQL request:
"""
{
dummies(order:{relatedDummy_name: "DESC"}) {
Copy link
Member

Choose a reason for hiding this comment

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

Please add a space after order:.

{
foreach ($args as $key => $value) {
if ($normalizeKeys && (strpos($key, '.'))) {
Copy link
Member

Choose a reason for hiding this comment

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

Same, why not generalizing it?

@alanpoulain
Copy link
Member

Maybe we could use another character than the underscore to avoid confusion?

@IonBazan
Copy link
Contributor

IonBazan commented Apr 15, 2018

@alanpoulain I would go for dummies?relatedDummy[anotherRelatedDummy][name]=foo for REST and dummies(relatedDummy: {anotherRelatedDummy: {name: "foo"}}) for GraphQL instead of dots and underscores because it ensures consistency between the APIs but it is also a BC break.

@alanpoulain
Copy link
Member

Seems fine to me. I think there is no BC break for now because GraphQL is treated as an experimental feature. It's not really clear enough for end-users though.

@IonBazan
Copy link
Contributor

@alanpoulain actually it would be a BC break for REST. Currently, nested property filters are built like: relatedDummy.name - not relatedDummy[name].

@alanpoulain
Copy link
Member

I think I get it. You want to avoid characters replacement by having the same kind of notation for the REST part and the GraphQL one?

@IonBazan
Copy link
Contributor

@alanpoulain Exactly, but I'm not sure if there are any drawbacks of this solution. This would require a total filters refactor and a lot of testing. I think the solution proposed by @antograssiot is good enough for now.

Scenario: Retrieve a collection filtered using the related search filter
Given there are 1 dummy objects having each 2 relatedDummies
And there are 1 dummy objects having each 3 relatedDummies
When I add "Accept" header equal to "application/hal+json"
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it shouldn't be here as we are testing GraphQL endpoint, not HAL.

@antograssiot antograssiot force-pushed the issue-1867 branch 2 times, most recently from fee0d3f to 08f169d Compare April 15, 2018 19:01
@antograssiot
Copy link
Contributor Author

all comments so far have been addressed.

@@ -292,6 +292,9 @@ private function mergeFilterArgs(array $args, array $parsed, ResourceMetadata $r
private function convertFilterArgsToTypes(array $args): array
{
foreach ($args as $key => $value) {
if (strpos($key, '.')) {
$args[str_replace('.', '_', $key)] = $value;
}
Copy link
Member

Choose a reason for hiding this comment

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

Please add a line break after.

@@ -164,4 +164,22 @@ private function getSubresource(string $rootClass, array $rootResolvedFields, ar
'collection' => $isCollection,
]);
}

private function getNormalizedFilters($args)
Copy link
Member

Choose a reason for hiding this comment

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

Add :array at the end and array for the parameter.

private function getNormalizedFilters($args)
{
$filters = $args;
foreach ($filters as $name => &$value) {
Copy link
Member

Choose a reason for hiding this comment

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

Please don't use references.

}

if (strpos($name, '_')) {
// Gives a chance to relations/nested fields
Copy link
Member

Choose a reason for hiding this comment

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

Please add a final point.

@antograssiot
Copy link
Contributor Author

done @alanpoulain

@@ -292,6 +292,11 @@ private function mergeFilterArgs(array $args, array $parsed, ResourceMetadata $r
private function convertFilterArgsToTypes(array $args): array
{
foreach ($args as $key => $value) {
if (strpos($key, '.')) {
// Declare relations/nested fields in a GraphQL compatible syntax.
$args[str_replace('.', '_', $key)] = $value;
Copy link
Member

Choose a reason for hiding this comment

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

Is this really working? Because we are not looping with the added value.

@dunglas dunglas merged commit c139586 into api-platform:2.2 Apr 20, 2018
@dunglas
Copy link
Member

dunglas commented Apr 20, 2018

Good one, thank you very much @antograssiot. Can you also open a doc PR? It looks important to have this mentioned in the documentation PR (especially for REST actually).

@antograssiot antograssiot deleted the issue-1867 branch April 20, 2018 09:56
@antograssiot
Copy link
Contributor Author

Sure, I'll prepare a doc update this weekend or next week @dunglas

teohhanhui pushed a commit to teohhanhui/api-platform-core that referenced this pull request May 2, 2018
* Allow GraphQL to filter on nested property

fixes api-platform#1714, api-platform#1867

* Allow ordering on nested property values
teohhanhui pushed a commit to teohhanhui/api-platform-core that referenced this pull request May 2, 2018
* Allow GraphQL to filter on nested property

fixes api-platform#1714, api-platform#1867

* Allow ordering on nested property values
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants