Because it handles the complex, tedious and repetitive task of creating an API infrastructure for you, API Platform lets you focus on what matters the most for the end user: the business logic. To do so, API Platform provides a lot of extension points you can use to hook your own code. Those extensions points are taken into account both by the REST and GraphQL subsystems.

The following tables summarizes which extension point to use depending on what you want to do:

Extension PointUsage
State Providersadapters for custom persistence layers, virtual fields, custom hydration
Denormalizerspost-process objects created from the payload sent in the HTTP request body
Symfony Voterscustom authorization logic
Laravel Policiescustom authorization logic
Validation constraintscustom validation logic
State Processorscustom business logic and computations to trigger before or after persistence (ex: mail, call to an external API…)
Normalizerscustomize the resource sent to the client (add fields in JSON documents, encode codes, dates…)
Filterscreate filters for collections and automatically document them (OpenAPI, GraphQL, Hydra)
Serializer Context Builderschange the Serialization context (e.g. groups) dynamically
Messenger Handlerscreate 100% custom, RPC, async, service-oriented endpoints (should be used in place of custom controllers because the messenger integration is compatible with both REST and GraphQL, while custom controllers only work with REST)
DTOsuse a specific class to represent the input or output data structure related to an operation
Kernel Eventscustomize the HTTP request or response (REST only, other extension points must be preferred when possible)

# Doctrine Specific Extension Points

Extension PointUsage
ExtensionsAccess to the query builder to change the DQL query
FiltersAdd 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.

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, use the decorator design pattern to wrap the native state processor in your own class sending the mail, as demonstrated in this example.

To replace existing API Platform services with your decorators, check out how to decorate services.

Service Decoration screencast
Watch the Service Decoration screencast

# System Providers and Processors

The system is based on a workflow composed of state providers and state processors.

The schema below describes them:

---
title: System providers and processors
---
flowchart TB
    C1(ContentNegotiationProvider) --> C2(ReadProvider)
    C2 --> C3(AccessCheckerProvider)
    C3 --> C4(DeserializeProvider)
    C4 --> C5(ParameterProvider)
    C5 --> C6(ValidateProcessor)
    C6 --> C7(WriteProcessor)
    C7 --> C8(SerializeProcessor)
    

# Symfony Access Checker Provider

When using Symfony, the access checker provider is used at three different stages:

  • api_platform.state_provider.access_checker.post_validate decorates the ValidateProvider
  • api_platform.state_provider.access_checker.post_deserialize decorates the DeserializeProvider
  • api_platform.state_provider.access_checker decorates the ReadProvider

ℹ️ Note

For graphql use: api_platform.graphql.state_provider.access_checker.post_deserialize, api_platform.graphql.state_provider.access_checker.post_validate, api_platform.graphql.state_provider.validate and api_platform.graphql.state_provider.access_checker.after_resolver

# Decoration Example

Here is an example of the decoration of the RespondProcessor:

Starts by creating your CustomRespondProcessor:

<?php
namespace App\State;

use ApiPlatform\State\ProcessorInterface;

final class CustomRespondProcessor implements ProcessorInterface
{
    public function __construct(private readonly ProcessorInterface $processor) {}

    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): void
    {
        // You can add pre-write code here.

        // Call the decorated processor's process method.
        $writtenObject = $this->processor->process($data, $operation, $uriVariables, $context);

        // You can add post-write code here.

        return $writtenObject;
    }
}

Now decorate the RespondProcessor with the CustomRespondProcessor using Symfony or Laravel:

# Symfony Processor Decoration

With Symfony you can simply do that by adding the #[AsDecorator] attribute as following:

namespace App\State;

use ApiPlatform\State\ProcessorInterface;

#[AsDecorator(decorates: 'api_platform.state.processor.respond_processor')]
final class CustomRespondProcessor implements ProcessorInterface
{
    // ...
}

or in the services.yaml by defining:

# api/config/services.yaml
services:
  # ...
  App\State\CustomRespondProcessor:
    decorates: api_platform.state.processor.respond_processor

And that’s it!

# Laravel Processor Decoration

<?php

namespace App\Providers;

use App\State\CustomRespondProcessor;
use ApiPlatform\State\Processor\RespondProcessor;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->extend(RespondProcessor::class, function (RespondProcessor $respondProcessor) {
            return new CustomRespondProcessor($respondProcessor);
        });
    }
}

You can also help us improve the documentation of this page.

Made with love by

Les-Tilleuls.coop can help you design and develop your APIs and web projects, and train your teams in API Platform, Symfony, Next.js, Kubernetes and a wide range of other technologies.

Learn more

Copyright © 2023 Kévin Dunglas

Sponsored by Les-Tilleuls.coop