diff --git a/core/extending.md b/core/extending.md index d7c52e0ebd0..74486008435 100644 --- a/core/extending.md +++ b/core/extending.md @@ -7,10 +7,11 @@ Those extensions points are taken into account both by the REST and [GraphQL](gr The following tables summarizes which extension point to use depending on what you want to do: | Extension Point | Usage | -| ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +|------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | [State Providers](state-providers.md) | adapters for custom persistence layers, virtual fields, custom hydration | | [Denormalizers](serialization.md) | post-process objects created from the payload sent in the HTTP request body | -| [Voters](security.md#hooking-custom-permission-checks-using-voters) | custom authorization logic | +| [Symfony Voters](../symfony/security.md#hooking-custom-permission-checks-using-voters) | custom authorization logic | +| [Laravel Policies](../laravel/security.md#policies) | custom authorization logic | | [Validation constraints](validation.md) | custom validation logic | | [State Processors](state-processors) | custom business logic and computations to trigger before or after persistence (ex: mail, call to an external API...) | | [Normalizers](serialization.md#decorating-a-serializer-and-adding-extra-data) | customize the resource sent to the client (add fields in JSON documents, encode codes, dates...) | @@ -22,16 +23,16 @@ The following tables summarizes which extension point to use depending on what y ## Doctrine Specific Extension Points -| Extension Point | Usage | -| ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | -| [Extensions](extensions.md) | Access to the query builder to change the DQL query | -| [Filters](filters.md#doctrine-orm-and-mongodb-odm-filters) | Add filters documentations (OpenAPI, GraphQL, Hydra) and automatically apply them to the DQL query | +| Extension Point | Usage | +|--------------------------------|----------------------------------------------------------------------------------------------------| +| [Extensions](extensions.md) | Access to the query builder to change the DQL query | +| [Filters](doctrine-filters.md) | Add filters documentations (OpenAPI, GraphQL, Hydra) and automatically apply them to the DQL query | ## Leveraging the Built-in Infrastructure Using Composition While most API Platform classes are marked as `final`, built-in services are straightforward to reuse and customize [using composition](https://en.wikipedia.org/wiki/Composition_over_inheritance). -For instance, if you want to send a mail after a resource has been persisted, but still want to benefit from the native Doctrine ORM [state processor](state-processors.md), use [the decorator design pattern](https://en.wikipedia.org/wiki/Decorator_pattern#PHP) to wrap the native state processor in your own class sending the mail, as demonstrated in [this example](state-processors.md#decorating-the-built-in-state-processors). +For instance, if you want to send a mail after a resource has been persisted, but still want to benefit from the native Doctrine ORM [state processor](state-processors.md), use [the decorator design pattern](https://en.wikipedia.org/wiki/Decorator_pattern#PHP) to wrap the native state processor in your own class sending the mail, as demonstrated in [this example](../core/state-processors.md#creating-a-custom-state-processor). To replace existing API Platform services with your decorators, [check out how to decorate services](https://symfony.com/doc/current/service_container/service_decoration.html). diff --git a/outline.yaml b/outline.yaml index 42d4cd8c70f..946601e7f79 100644 --- a/outline.yaml +++ b/outline.yaml @@ -15,6 +15,7 @@ chapters: - user - jwt - file-upload + - controllers - title: "API Platform for Laravel" path: laravel items: @@ -64,7 +65,6 @@ chapters: - identifiers - mongodb - elasticsearch - - controllers - events - jwt - form-data diff --git a/core/controllers.md b/symfony/controllers.md similarity index 95% rename from core/controllers.md rename to symfony/controllers.md index 283b4bf78ca..caf6613fe7d 100644 --- a/core/controllers.md +++ b/symfony/controllers.md @@ -1,11 +1,13 @@ -# Creating Custom Operations and Controllers +# Creating Custom Operations and Symfony 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). +> [!NOTE] +> Using custom Symfony 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](../core/design.md). +> We recommend to use [System providers and processors](../core/extending.md#system-providers-and-processors) to extend API Platform internals. 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](https://symfony.com/doc/current/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) +Symfony controllers extending the [`Symfony\Bundle\FrameworkBundle\Controller\AbstractController`](https://symfony.com/doc/current/controller.html#the-base-controller-class-services) helper class. To enable this feature use `use_symfony_listeners: true` in your `api_platform` configuration file: @@ -39,7 +41,7 @@ If your resource has any identifier, this operation will look like `/books/{id}` Those routes are not exposed from any documentation (for instance OpenAPI), but are anyway declared on the Symfony routing and always return a HTTP 404. If you create a custom operation, you will probably want to properly document it. -See the [OpenAPI](openapi.md) part of the documentation to do so. +See the [OpenAPI](../core/openapi.md) part of the documentation to do so. First, let's create your custom operation: @@ -90,7 +92,7 @@ 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 +In this case, the entity will pass through [all built-in event listeners](../core/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. @@ -165,7 +167,7 @@ Complex use cases may lead you to create multiple custom operations. In such a case, you will probably create the same amount of custom controllers while you may not need to perform custom logic inside. -To avoid that, API Platform provides the `ApiPlatform\Action\PlaceholderAction` which behaves the same when using the [built-in operations](operations.md#operations). +To avoid that, API Platform provides the `ApiPlatform\Action\PlaceholderAction` which behaves the same when using the [built-in operations](../core/operations.md#operations). You just need to set the `controller` attribute with this class. Here, the previous example updated: @@ -370,7 +372,7 @@ resources: -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) +This way, it will skip the `ReadListener`. You can do the same for some other built-in listeners. See [Built-in Event Listeners](../core/events.md#built-in-event-listeners) for more information. In your custom controller, the `__invoke()` method parameter should be called the same as the entity identifier. diff --git a/symfony/migrate-from-fosrestbundle.md b/symfony/migrate-from-fosrestbundle.md index ed58c337b36..ba24de384fa 100644 --- a/symfony/migrate-from-fosrestbundle.md +++ b/symfony/migrate-from-fosrestbundle.md @@ -33,7 +33,7 @@ Same as above. **In API Platform** -Even though this is not recommended, API Platform allows you to [create custom controllers](../core/controllers.md) and declare them in your entity's `ApiResource` attribute. +Even though this is not recommended, API Platform allows you to [create custom controllers](controllers.md) and declare them in your entity's `ApiResource` attribute. You can use them as you migrate from FOSRestBundle, but you should consider [switching to Symfony Messenger](../core/messenger.md) as it will give you more benefits, such as compatibility with both REST and GraphQL and better performances of your API on big tasks.