# Creating Custom Operations and Controllers Note: using custom controllers with API Platform is **discouraged**. Also, GraphQL is **not supported**. [For most use cases, better extension points, working both with REST and GraphQL, are available](design.md). API Platform can leverage the Symfony routing system to register custom operations related to custom controllers. Such custom controllers can be any valid [Symfony controller](http://symfony.com/doc/current/book/controller.html), including standard Symfony controllers extending the [`Symfony\Bundle\FrameworkBundle\Controller\AbstractController`](http://api.symfony.com/4.1/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.html) helper class. However, API Platform recommends to use **action classes** instead of typical Symfony controllers. Internally, API Platform implements the [Action-Domain-Responder](https://github.com/pmjones/adr) pattern (ADR), a web-specific refinement of [MVC](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller). The distribution of API Platform also eases the implementation of the ADR pattern: it automatically registers action classes stored in `api/src/Controller` as autowired services. Thanks to the [autowiring](http://symfony.com/doc/current/components/dependency_injection/autowiring.html) feature of the Symfony Dependency Injection container, services required by an action can be type-hinted in its constructor, it will be automatically instantiated and injected, without having to declare it explicitly. In the following examples, the built-in `GET` operation is registered as well as a custom operation called `post_publication`. By default, API Platform uses the first `GET` operation defined in `itemOperations` to generate the IRI of an item and the first `GET` operation defined in `collectionOperations` to generate the IRI of a collection. If you create a custom operation, you will probably want to properly document it. See the [OpenAPI](swagger.md) part of the documentation to do so. First, let's create your custom operation: ```php bookPublishingHandler = $bookPublishingHandler; } public function __invoke(Book $data): Book { $this->bookPublishingHandler->handle($data); return $data; } } ``` This custom operation behaves exactly like the built-in operation: it returns a JSON-LD document corresponding to the id passed in the URL. Here we consider that [autowiring](https://symfony.com/doc/current/service_container/autowiring.html) is enabled for controller classes (the default when using the API Platform distribution). This action will be automatically registered as a service (the service name is the same as the class name: `App\Controller\CreateBookPublication`). API Platform automatically retrieves the appropriate PHP entity using the data provider then deserializes user data in it, and for `POST` and `PUT` requests updates the entity with data provided by the user. **Warning: the `__invoke()` method parameter [MUST be called `$data`](https://symfony.com/doc/current/components/http_kernel.html#getting-the-controller-arguments)**, otherwise, it will not be filled correctly! Services (`$bookPublishingHandler` here) are automatically injected thanks to the autowiring feature. You can type-hint any service you need and it will be autowired too. The `__invoke` method of the action is called when the matching route is hit. It can return either an instance of `Symfony\Component\HttpFoundation\Response` (that will be displayed to the client immediately by the Symfony kernel) or, like in this example, an instance of an entity mapped as a resource (or a collection of instances for collection operations). In this case, the entity will pass through [all built-in event listeners](events.md#built-in-event-listeners) of API Platform. It will be automatically validated, persisted and serialized in JSON-LD. Then the Symfony kernel will send the resulting document to the client. The routing has not been configured yet because we will add it at the resource configuration level: ```php POST /books/{id}/publication App\Controller\CreateBookPublication ``` It is mandatory to set the `method`, `path` and `controller` attributes. They allow API Platform to configure the routing path and the associated controller respectively. ## Using Serialization Groups You may want different serialization groups for your custom operations. Just configure the proper `normalization_context` and/or `denormalization_context` in your operation: ```php POST /books/{id}/publication App\Controller\CreateBookPublication publication ``` ## Retrieving the Entity If you want to bypass the automatic retrieval of the entity in your custom operation, you can set `"read"=false` in the operation attribute: ```php POST /books/{id}/publication App\Controller\CreateBookPublication false ``` This way, it will skip the `ReadListener`. You can do the same for some other built-in listeners. See [Built-in Event Listeners](events.md#built-in-event-listeners) for more information. ## Alternative Method There is another way to create a custom operation. However, we do not encourage its use. Indeed, this one disperses the configuration at the same time in the routing and the resource configuration. The `post_publication` operation references the Symfony route named `book_post_publication`. Since version 2.3, you can also use the route name as operation name by convention, as shown in the following example for `book_post_discontinuation` when neither `method` nor `route_name` attributes are specified. First, let's create your resource configuration: ```php book_post_publication ``` API Platform will automatically map this `post_publication` operation to the route `book_post_publication`. Let's create a custom action and its related route using annotations: ```php bookPublishingHandler = $bookPublishingHandler; } /** * @Route( * name="book_post_publication", * path="/books/{id}/publication", * methods={"POST"}, * defaults={ * "_api_resource_class"=Book::class, * "_api_item_operation_name"="post_publication" * } * ) */ public function __invoke(Book $data): Book { $this->bookPublishingHandler->handle($data); return $data; } } ``` It is mandatory to set `_api_resource_class` and `_api_item_operation_name` (or `_api_collection_operation_name` for a collection operation) in the parameters of the route (`defaults` key). It allows API Platform to work with the Symfony routing system. Alternatively, you can also use a traditional Symfony controller and YAML or XML route declarations. The following example does the same thing as the previous example: ```php handle($data); } } ``` ```yaml # api/config/routes.yaml book_post_publication: path: /books/{id}/publication methods: ['POST'] defaults: _controller: App\Controller\BookController::createPublication _api_resource_class: App\Entity\Book _api_item_operation_name: post_publication ```