2
votes

I have a problem with Api Platform and custom collection operation, when I need to manually require an argument in the route.

  1. My first need is to GET on this route: query/userjob/[USER UUID] and retrieve a collection of all jobs for the given user.
  2. My second need is to be able to GET on query/userjob/[USER UUID]/[JOB UUID] and retrieve details for the given user's job.

It might be important to say that I have no Api resource nor entity User, so I exclude all kind of subresource mapping or query.

So, let's say i have a UserJob ApiResource mapped as below:

App\Domain\User\Projection\UserJob:               
        itemOperations:
            get: 
                method: 'GET'
                path: '/userjob/{userId}/{jobId}'
                requirements:
                    userId: '%uuid_regex%'
                    jobId: '%uuid_regex%'
        collectionOperations:            
            get:
                method: 'GET'
                path: '/userjob/{userId}'
                requirements:
                    userId: '%uuid_regex%'
        attributes:
            route_prefix: "/query"

In the class, I have:


final class UserJob
{
    public $id; //int Auto inc
    public $userId; //a UUID
    public $jobId; //a UUID

    public function __construct($userId, $jobId)
    {
        $this->userId = $userId;
        $this->jobId = $jobId;

    }

    public function getId(): int
    {
        return $this->id;
    }

    public function getUserId()
    {
        return $this->userId;
    }

    public function getJobId()
    {
        return $this->jobId
    }   

I built a custom data provider for this class, in which I wrote the way to get the resource from the giver parameter (userId):

    public function getCollection(string $resourceClass, string $operationName = null)
    {
        $userId = $this->request->getCurrentRequest()->attributes->get('userId');
        return $this->repository->entityManager->getRepository($resourceClass)->findByUserId($userId);
    }

When i make a GET call to, let's say, query/userjob/148e3200-f793-447b-bde8-af6b7b27372c it throws an exception:

Unable to generate an IRI for App\Domain\User\Projection\UserJob

And if I debug deeper, in the IRIConverter class, I find that the original exception is thrown from Router:

Some mandatory parameters are missing ("userId") to generate a URL for route "api_user_jobs_get_collection".

Nevertheless, if i dump the result of $this->repository->entityManager->getRepository($resourceClass)->findByUserId($userId);, all the elements that i'm looking for are well fetched from database.

So my intuition is that somehow ApiPlatform process fails to build the collection IRI that we usually can find at the beginning of the payload, and which in my case would be query/userjob/148e3200-f793-447b-bde8-af6b7b27372c.

And it fails while on the normalization or serialization process, because the "extra" param of my custom operation (the user UUID) is not passed to the collection normalizer, iri converter classes, so it has no way to give to the router the missing param to build the "api_user_jobs_get_collection" route.

What am I missing here? Is this a well-known problem that has a readymade solution that I missed ?

Or do I have to look for:

  • decorate the IRI converter?
  • use a custom normalizer?
  • do something with composite ids?
  • something else?
1
Did you find a good way to solve your issue ? - BastienSander

1 Answers

0
votes

Your use case may have more solutions, and it depends what is preferred:

  1. decorate iri converter, as you are using identifier in collection and this is not supported out of the box by API platform, as per my knowledge. And this is best choice if url like this are the style of your api.
  2. use custom controller action with custom url style (docs: https://api-platform.com/docs/core/controllers/#creating-custom-operations-and-controllers), best if this is rare url in your api
  3. Annotate your ids as identifiers in your api resource class (doc: https://api-platform.com/docs/core/identifiers/#custom-identifier-normalizer)
    /**
     * @var Uuid
     * @ApiProperty(identifier=true)
     */
    public $code;
    
    but I haven't tried this with multiple ids, and this may work only for item url.

You may try to use custom data provider (docs: https://api-platform.com/docs/core/data-providers/). This will need to be done per resource or globally (supports() method) and you will need somehow (regex?) extract ids from url from $context array in getCollection() and getItem() methods. But as ApiPlatform will try to generate item iri, you may still end up with decorating iri converter.

Note: Using id in collection url may lead to other problems, like OpenAPI documentation generation. You may consider if what you want is not filtering of collection by "id" field, nicely supported, or retrieving collection of only "your" items. Which can be done by your data provider injecting security or by doctrine query extensions if someone uses doctrine.