Core: SearchFilter exact subresource on another identifier with IRI fails

Created on 24 May 2020  路  1Comment  路  Source: api-platform/core

API Platform version(s) affected: 2.5.5

Description
SearchFilter subresource with changed identifiers works with identifier but not with IRI identifier

How to reproduce
Add 2 classe like in https://api-platform.com/docs/core/identifiers/#changing-identifier-in-a-doctrine-entity , Person and Process

in class Person add

/**
     * @ORM\ManyToOne(targetEntity="App\Entity\Process", inversedBy="persons")
     */
    private $process;

NOT working

http://localhost/api/person?process=%2Fapi%2Fprocesses%2F65b810c4-9db0-11ea-a2bd-a683e78c6b85

with error:
The identifier id is missing for a query of App\\Entity\\Process

Possible Solution
The first request hits vendor/doctrine/orm/lib/Doctrine/ORM/EntityManager.php line 487 where the entity has no code set as identifier, only the id

Update 1:
Ohh my gosh, the SQL is formed around the id of the relation, that's why filtering by UUID only will give an empty array. Must be a filter of the field of subresource `process.uuid'. There is any way to use the IRI for it?

Most helpful comment

I am encountering the same issue when filtering on entities that have both numeric ID + uuid fields.

Reproduction

Here's a sandbox project reproducing the bug via the api-platform template with two entities: https://github.com/alexdo/api-platform-uuid-relation
Fixtures included; so make sure you run those when trying to reproduce ;)

In the example, a Greeting has many Greeters and Greeters have many Greetings.
The uuid-Fields of both entities are marked as @ApiProperty(identifier=true).
There's an @ApiFilter(SearchFilter::class, properties={"greeters": "exact"}) annotation on the Greeting class.

I'd expect GET https://localhost:8443/greetings?greeters=[some greeter IRI] to return a collection of greetings present in the many-to-many relation but instead see the same exception as described above: Pastebin link to the full stacktrace

Interestingly, GET https://localhost:8443/greetings?greeters=[some greeter UUID] is working fine. So @ApiProperty(identifier=true) resolution appears to work correctly but IRI resolution + custom identifier doesn't (pure speculation tbh).

Tracking it down

It appears that it all begins in https://github.com/api-platform/core/blob/master/src/Bridge/Doctrine/Orm/Filter/SearchFilter.php#L143
That's calling getIdFromValue() on SearchFilterTrait
https://github.com/api-platform/core/blob/master/src/Bridge/Doctrine/Common/Filter/SearchFilterTrait.php#L120-L130
which, in turn, tries to resolve the IRI => ID by hitting the IriConverter->getItemFromIri($value, ['fetch_data' => false])
But, as fetch_data is set to false, the ItemDataProvider will try to create a Reference:
https://github.com/api-platform/core/blob/master/src/Bridge/Doctrine/Orm/ItemDataProvider.php#L81-L84
Call with arguments written out:

 $manager->getReference(Greeter::class, ["uuid" => (uuid of my greeter)]);

However, the Doctrine ORM will, of course, only look at the @ORM\Id property - and not at the API one ;)

This isn't a problem when using UUIDs directly, as IriConverter->getItemFromIri just bails out when looking for a matching Route ;)

Fixing this - where to begin?

I'd really like to provide a PR for this but don't really know where to start.

An option could be to prevent resolving the Itemdata in this problematic case (when doctrine id field !== api platform id), just resolve the IRI route attributes and adjust the final query to use uuid instead of the primary key.
In other words:

  1. Try to catch or prevent Doctrine\ORM\ORMException: The identifier id is missing for a query of X somehow
  2. Use the raw route "id" attribute instead
  3. => Result: Same behaviour as when just using the UUID (instead of the IRI)

The best way of implementing this could possibly be splitting IriConverter->getItemFromIri into two methods: One that does raw route lookups and another one that uses these lookups + creates the reference.

WDYT?

>All comments

I am encountering the same issue when filtering on entities that have both numeric ID + uuid fields.

Reproduction

Here's a sandbox project reproducing the bug via the api-platform template with two entities: https://github.com/alexdo/api-platform-uuid-relation
Fixtures included; so make sure you run those when trying to reproduce ;)

In the example, a Greeting has many Greeters and Greeters have many Greetings.
The uuid-Fields of both entities are marked as @ApiProperty(identifier=true).
There's an @ApiFilter(SearchFilter::class, properties={"greeters": "exact"}) annotation on the Greeting class.

I'd expect GET https://localhost:8443/greetings?greeters=[some greeter IRI] to return a collection of greetings present in the many-to-many relation but instead see the same exception as described above: Pastebin link to the full stacktrace

Interestingly, GET https://localhost:8443/greetings?greeters=[some greeter UUID] is working fine. So @ApiProperty(identifier=true) resolution appears to work correctly but IRI resolution + custom identifier doesn't (pure speculation tbh).

Tracking it down

It appears that it all begins in https://github.com/api-platform/core/blob/master/src/Bridge/Doctrine/Orm/Filter/SearchFilter.php#L143
That's calling getIdFromValue() on SearchFilterTrait
https://github.com/api-platform/core/blob/master/src/Bridge/Doctrine/Common/Filter/SearchFilterTrait.php#L120-L130
which, in turn, tries to resolve the IRI => ID by hitting the IriConverter->getItemFromIri($value, ['fetch_data' => false])
But, as fetch_data is set to false, the ItemDataProvider will try to create a Reference:
https://github.com/api-platform/core/blob/master/src/Bridge/Doctrine/Orm/ItemDataProvider.php#L81-L84
Call with arguments written out:

 $manager->getReference(Greeter::class, ["uuid" => (uuid of my greeter)]);

However, the Doctrine ORM will, of course, only look at the @ORM\Id property - and not at the API one ;)

This isn't a problem when using UUIDs directly, as IriConverter->getItemFromIri just bails out when looking for a matching Route ;)

Fixing this - where to begin?

I'd really like to provide a PR for this but don't really know where to start.

An option could be to prevent resolving the Itemdata in this problematic case (when doctrine id field !== api platform id), just resolve the IRI route attributes and adjust the final query to use uuid instead of the primary key.
In other words:

  1. Try to catch or prevent Doctrine\ORM\ORMException: The identifier id is missing for a query of X somehow
  2. Use the raw route "id" attribute instead
  3. => Result: Same behaviour as when just using the UUID (instead of the IRI)

The best way of implementing this could possibly be splitting IriConverter->getItemFromIri into two methods: One that does raw route lookups and another one that uses these lookups + creates the reference.

WDYT?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

breitsmiley picture breitsmiley  路  3Comments

silverbackdan picture silverbackdan  路  3Comments

lukasluecke picture lukasluecke  路  3Comments

mahmoodbazdar picture mahmoodbazdar  路  3Comments

rockyweng picture rockyweng  路  3Comments