Core: Custom action "_api_item_operation_name" not respected when generating @id route

Created on 23 Aug 2016  ยท  12Comments  ยท  Source: api-platform/core

I have a custom operation (and data provider) on a User resource which allows me to get the current authenticated user under the /auth/me route. However, when generating the link for the @id property, it uses the wrong route. Instead of using the route given in the @Route default _api_item_operation_name property, it uses the route it finds first that matches the GET method on the resource.

In my case, I wanted it to use the default get route instead of the custom get-authenticated route I created, but no matter what value I put in under the _api_item_operation_name property, it was ignored.

An easy way to verify this is to create two routes/operations for the resource. In the example below when sending a request to /auth2/me, it will generate an @id of the /auth/me route instead of the one given through _api_item_operation_name.


/**
 * @ApiResource(
 *     itemOperations={
 *          "get"={"method"="GET"},
 *          "put"={"method"="PUT"},
 *          "delete"={"method"="DELETE"},
 *          "get-authenticated"={"route_name"="api_users_get_authenticated"},
 *          "get-authenticated2"={"route_name"="api_users_get_authenticated2"}
 *     }
 * )
 */
class User
{
 ...
}

class AuthenticatedUser
{

    /**
     * @Route(
     *     name="api_users_get_authenticated",
     *     path="/auth/me",
     *     defaults={"_api_resource_class"=User::class, "_api_item_operation_name"="get-authenticated"}
     * )
     *
     * @Route(
     *     name="api_users_get_authenticated2",
     *     path="/auth2/me",
     *     defaults={"_api_resource_class"=User::class, "_api_item_operation_name"="get-authenticated2"}
     * )
     *
     * @Method("GET")
     *
     * @param $data mixed
     *
     * @return mixed
     */
    public function __invoke($data = null)
    {
        return $data;
    }

}

It appears that the issue is in IriConverter. It does not actually use the operation name to find the correct route, just verifies that it is not null.

https://github.com/api-platform/core/blob/44e5fc0cb7bfdcc090086dcb53e4da4772c8a097/src/Bridge/Symfony/Routing/IriConverter.php#L183

doc enhancement question โญ EU-FOSSA Hackathon

Most helpful comment

For what it's worth, I think it's better to specify the route to use as well. I understand that adding an /auth/me route makes the API stateful, and I have since rewritten it to use JWT and a request to /users/{id} instead.

However, specifying the route to use makes the project more flexible and reduces issues that can occur from specifying custom app routes before API routes. I've moved my app routes to after the api routes in routing.yml, but this is pretty unintuitive, undocumented, and has a huge potential for bugs. Just imagine a developer adding a custom GET route for /user/{id}/action. If they have defined these routes before the API routes, all @id IRI's will now use this GET route instead of the proper one.

All 12 comments

Futhermore, you can actually put a bunch of garbage in the _api_item_operation_name property, and it doesn't actually matter. API Platform will just find the first GET route for the resource and incorrectly assume that it is the correct one.

class AuthenticatedUser
{

    /**
     * @Route(
     *     name="api_users_get_authenticated",
     *     path="/auth/me",
     *     defaults={"_api_resource_class"=User::class, "_api_item_operation_name"="THISDOESNTMATTERANDISIGNORED"}
     * )
     *
     * @Method("GET")
     *
     * @param $data mixed
     *
     * @return mixed
     */
    public function __invoke($data = null)
    {
        return $data;
    }

}

@bwegrzyn Do you want to create an PR about this bug ? We could help you if you need to ;)

@Simperfit I'll try to do it this week if I can find enough free time.

Hi
I have the same issue ! @Simperfit do you have a PR plan ?

The _api_item_operation_name attribute isn't intended to be used by the normalizer. It is only needed to map a controller with a resource class through the Symfony router.

The serialization can occur in different contexts (CLI, POST or PUT requests...). The value of the @id property must always remain the same, in all contexts. It doesn't depend of the operation that has been called (e.g. if you have 2 URLs to retrieve different views of the same resource, the resource's @id property must be the same for both URLs, think to the @id property like a primary key).
It's why the first GET item operation is always used. It's on purpose.

But there is something weird here, if you register the typical get operation first, it should always be used to generate the URL. If it's not the case, it's a bug and we must fix it.

cc @soyuka

By the way, using an URL like /auth/me is not in the spirit of REST and is generally speaking a bad idea. It makes your application stateful and can be very hard to optimize (adding a cache reverse proxy for instance) and scale.

Prefer using something like JWT to get the user id during the authentification process and store it client-side.

Needs to be documented:

@AlexandreHagen This means that the IRI url matches the first declared GET operation (collection or item is the same). At least, this is what I understood from the code and from @dunglas comment. And, this is the wanted behavior here.

source

I think it's better if we can specify the route to use for IRI generation, instead of depending on the order in which the operations are declared.

For what it's worth, I think it's better to specify the route to use as well. I understand that adding an /auth/me route makes the API stateful, and I have since rewritten it to use JWT and a request to /users/{id} instead.

However, specifying the route to use makes the project more flexible and reduces issues that can occur from specifying custom app routes before API routes. I've moved my app routes to after the api routes in routing.yml, but this is pretty unintuitive, undocumented, and has a huge potential for bugs. Just imagine a developer adding a custom GET route for /user/{id}/action. If they have defined these routes before the API routes, all @id IRI's will now use this GET route instead of the proper one.

I agree with both of you :) PR welcome!

@teohhanhui, @bwegrzyn is this fixed ?

@Simperfit I haven't seen anything that I would consider a fix for this issue. api-platform/api-platform#141 looks like a workaround instead of an actual fix, especially since its in the platform code. This is actually what I ended up doing in my own project for the time being.

I'm still not sure why @dunglas does not consider this a bug. Why is using the first available GET route the desired behaviour if we use _api_item_operation_name to specify the route we want? @dunglas mentioned that we should keep the @id property the same in all cases, which is exactly what I'm trying to do here. By not fixing this bug, the @id property is different/incorrect in some cases (depending on the order in which you register routes). This is very confusing DX, even if it is documented somewhere.

Was this page helpful?
0 / 5 - 0 ratings