Custom Operations: Action-Domain-Responder with Symfony4 doesn't seem to find Action · Issue #537 · api-platform/api-platform · GitHub
Skip to content

Custom Operations: Action-Domain-Responder with Symfony4 doesn't seem to find Action #537

Closed

Description

@Rebolon

I was trying to create a custom operations following this docs and when i go to /api then it says that book_special route is unknown.

If i do the same thing but with a controller, then it works.

Here is my code for ADR pattern:

Entity Book:

namespace App\Entity\Library;

use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiSubresource;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ApiResource(
 *     iri="http://bib.schema.org/ComicStory",
 *     attributes={"access_control"="is_granted('ROLE_USER')"},
 *     itemOperations={
 *         "get"={"method"="GET"},
 *         "special"={"route_name"="book_special"}
 *     }
 * )
 * @ORM\Entity
 */
class Book
{
...

And the Action:

<?php
namespace App\Action;

use App\Entity\Book;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Component\Routing\Annotation\Route;

/**
 * @todo Doesn't seem to be catched by Api-Platform
 *
 * Class BookSpecial
 * @package App\Action
 */
class BookSpecial
{
    public function __construct()
    {

    }

    /**
     * @Route(
     *     name="book_special",
     *     path="/books/{id}/special",
     *     defaults={"_api_resource_class"=Book::class, "_api_item_operation_name"="special"}
     * )
     * @Method("GET")
     */
    public function __invoke($data) // API Platform retrieves the PHP entity using the data provider then (for POST and
                                    // PUT method) deserializes user data in it. Then passes it to the action. Here $data
                                    // is an instance of Book having the given ID. By convention, the action's parameter
                                    // must be called $data.
    {
        return $data; // API Platform will automatically validate, persist (if you use Doctrine) and serialize an entity
                      // for you. If you prefer to do it yourself, return an instance of Symfony\Component\HttpFoundation\Response
    }
}

And with the same entity, here is the controller that make all works:

<?php
namespace App\Controller\Api;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use \Symfony\Component\HttpFoundation\JsonResponse;

class Book extends Controller
{
    /**
     * @Route("/api/books/{id}/special", name="book_special", requirements={"id": "\d*"})
     * @Method({"GET"})
     *
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function specialAction($id)
    {
        return JsonResponse(['it works' => 'yes', ]);
    }
}

When i read the docs, i see that ApiPlatform does introspection and look for src/AppBundle/Action/* classes. But in Sf3+ (i'm running on 4) my Action is in src/Action , so i expected the framework to be unable to find the Action.

Am i doing something wrong ?

Activity

  1. soyuka commented on Jan 24, 2018

    @soyuka
    Member

    I think that what you did should work

    Service declaration

    # services.yaml
    services:
        _defaults:
            autoconfigure: true
            autowire: true
            public: false
    
        App\Controller\:
            resource: './src/App/Controller/*' # this path may be different obviously
            tags: ['controller.service_arguments']

    Might need to be loaded in config/routes/annotations:

    foo:
        resource: ../../src/App/Controller/
        type: annotation

    Controller using the "Action" pattern

    <?php
    
    declare(strict_types=1);
    
    namespace App\Controller;
    
    use App\Entity\Foo;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpFoundation\JsonResponse;
    use Symfony\Component\Routing\Annotation\Route;
    
    /**
     * @Route("/api/foos/{param}", defaults={"_api_resource_class"=Foo::class, "_api_item_operation_name"="foo_custom"})
     */
    class FooController
    {
        public function __invoke($param, Request $request)
        {
            return new Foo();
        }
    }

    Yes I declare my route on the class because the controller is handling only one route.

    Link to Api Platform

    If you want the api platform listeners to handle the route:

    resources:
        App\Product\Entity\Product:
            itemOperations:
                foo_custom:
                    route_name: 'app_foo__invoke' # or add name in the route annotation
  2. DougHayward commented on Jan 29, 2018

    @DougHayward

    To fix the issue you simply need to add the Action directory to the service loader:

    # config/services.yaml
    services:
       App\Action\:
            resource: '../src/Action'
            tags: ['controller.service_arguments']
    

    And then add the Action folder to your annotations loader:

    # config/routes/annotations.yaml
    actions:
        resource: ../../src/Action/
        type: annotation
    
  3. Rebolon commented on Jan 29, 2018

    @Rebolon
    Author

    Ok i'll test it tomorrow.
    Is it in the documentation, i don't see anythong about this in https://api-platform.com/docs/core/operations ?
    If you need i may do a PR on the docs project.

  4. DougHayward commented on Jan 29, 2018

    @DougHayward

    No, I've just worked it out as I was also having the same issue. Problem with the docs is they are predominantly targeted at PRE-Flex Symfony, so not as simple as just adding the above to the docs.
    By this I mean there is a lot of mention of AppBundle/ which no longer exists.

  5. Rebolon commented on Jan 29, 2018

    @Rebolon
    Author
  6. alifs-dev commented on Feb 1, 2018

    @alifs-dev

    Hi,
    It work fine, but this custom operation return the entity collection/item operations already filtered.
    How to make a top ten book for example, instead of return the complete $data ,
    thanks

  7. Rebolon commented on Feb 2, 2018

    @Rebolon
    Author

    Cool @BonnieDoug i've tested it and it works well.

    Not sure i'm telling the truth (and i would like to know how to do this in a correct way), but maybe you could inject the the service like EntityManagerInterface
    and then in your method do the query you want and return a JsonResponse built by yourself. Maybe a better way would be to build a serializer instead of using the JsonResponse but wthat would be valuable only if you re-use this serializer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions