Symfony15 min

API Platform + Symfony : creer une API REST/GraphQL

Par Pierre-Arthur Demengel
SymfonyAPI PlatformRESTGraphQL

API Platform est le framework API le plus puissant de l'ecosysteme PHP. Couple a Symfony 7.2, il permet de creer une API REST complete avec documentation OpenAPI, pagination, filtres et validation en quelques minutes - puis d'ajouter GraphQL avec un seul paquet. Voici comment l'utiliser concretement, au-dela du tutorial "Hello World".

Installation et configuration

API Platform s'installe via Composer et s'integre automatiquement dans Symfony grace a Flex :

composer require api

# Pour le support GraphQL (optionnel)
composer require api-platform/graphql

La recette Flex configure tout automatiquement : le routing, la serialisation, la documentation. Votre API est immediatement accessible sur /api avec une interface Swagger UI.

Definir une ressource API

La force d'API Platform reside dans la declaration des ressources directement sur les entites Doctrine via des attributs PHP :

// src/Entity/Article.php
namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\ApiFilter;
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
use ApiPlatform\Doctrine\Orm\Filter\DateFilter;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Validator\Constraints as Assert;

#[ORM\Entity]
#[ApiResource(
    operations: [
        new GetCollection(normalizationContext: ['groups' => ['article:list']]),
        new Get(normalizationContext: ['groups' => ['article:read']]),
        new Post(security: "is_granted('ROLE_EDITOR')"),
        new Put(security: "is_granted('ROLE_ADMIN') or object.author == user"),
        new Delete(security: "is_granted('ROLE_ADMIN')"),
    ],
    paginationItemsPerPage: 20,
    order: ['publishedAt' => 'DESC'],
)]
#[ApiFilter(SearchFilter::class, properties: [
    'title' => 'partial',
    'author.name' => 'exact',
    'category' => 'exact',
])]
#[ApiFilter(OrderFilter::class, properties: ['publishedAt', 'title'])]
#[ApiFilter(DateFilter::class, properties: ['publishedAt'])]
class Article
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    #[Groups(['article:list', 'article:read'])]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    #[Assert\NotBlank]
    #[Assert\Length(min: 5, max: 255)]
    #[Groups(['article:list', 'article:read'])]
    private string $title = '';

    #[ORM\Column(type: 'text')]
    #[Assert\NotBlank]
    #[Groups(['article:read'])]
    private string $content = '';

    #[ORM\Column(type: 'datetime_immutable')]
    #[Groups(['article:list', 'article:read'])]
    private \DateTimeImmutable $publishedAt;

    #[ORM\ManyToOne(targetEntity: User::class)]
    #[Groups(['article:read'])]
    public ?User $author = null;

    // Getters et setters...
}

Avec cette seule entite, API Platform genere automatiquement 5 endpoints REST (GET /api/articles, GET /api/articles/{id}, POST, PUT, DELETE), les filtres de recherche, le tri, la pagination et la documentation OpenAPI complete.

Groupes de serialisation

Les groupes de serialisation controlent quelles proprietes sont exposees selon l'operation. Dans l'exemple ci-dessus, le listing (article:list) retourne l'id, le titre et la date, mais pas le contenu complet. La vue detaillee (article:read) inclut tout. C'est crucial pour les performances - ne retournez jamais toutes les donnees sur un listing.

Pour aller plus loin avec la serialisation Symfony, consultez le guide complet du Serializer qui couvre les normaliseurs personnalises et les contextes avances.

Filtres et pagination

API Platform integre une dizaine de filtres Doctrine prets a l'emploi :

# Exemples de requetes avec filtres
GET /api/articles?title=symfony           # Recherche partielle
GET /api/articles?author.name=Pierre      # Filtre sur une relation
GET /api/articles?order[publishedAt]=desc  # Tri
GET /api/articles?publishedAt[after]=2026-01-01  # Filtre par date
GET /api/articles?page=2                   # Pagination

La pagination est automatique. Par defaut, elle utilise le format Hydra (JSON-LD) qui inclut les liens hydra:first, hydra:next, hydra:last pour la navigation. Vous pouvez aussi utiliser le format cursor-based pour de meilleures performances sur les grandes collections.

Operations personnalisees

Pour les operations qui ne rentrent pas dans le CRUD standard, creez des operations personnalisees avec un controller ou un State Processor :

// src/State/PublishArticleProcessor.php
namespace App\State;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Entity\Article;

class PublishArticleProcessor implements ProcessorInterface
{
    public function __construct(
        private ProcessorInterface $persistProcessor,
    ) {}

    /**
     * @param Article $data
     */
    public function process(
        mixed $data,
        Operation $operation,
        array $uriVariables = [],
        array $context = [],
    ): Article {
        $data->setPublishedAt(new \DateTimeImmutable());
        $data->setStatus('published');

        return $this->persistProcessor->process(
            $data, $operation, $uriVariables, $context
        );
    }
}

Puis declarez l'operation sur l'entite :

#[ApiResource(
    operations: [
        // ... operations standard
        new Post(
            uriTemplate: '/articles/{id}/publish',
            processor: PublishArticleProcessor::class,
            security: "is_granted('ROLE_EDITOR')",
        ),
    ],
)]

Support GraphQL

Si vous avez installe api-platform/graphql, toutes vos ressources API sont automatiquement accessibles via GraphQL sur /graphql :

# Query GraphQL
{
  articles(first: 10, order: { publishedAt: "DESC" }) {
    edges {
      node {
        id
        title
        publishedAt
        author {
          name
        }
      }
    }
    totalCount
  }
}

# Mutation GraphQL
mutation {
  createArticle(input: {
    title: "Mon article",
    content: "Le contenu..."
  }) {
    article {
      id
      title
    }
  }
}

Les filtres, la pagination et les regles de securite definies sur vos ressources s'appliquent identiquement en REST et en GraphQL. Vous n'avez rien a reconfigurer.

Authentification JWT

Pour securiser votre API, le standard est JWT via le bundle LexikJWT :

# Installation
composer require lexik/jwt-authentication-bundle

# Generer les cles RSA
php bin/console lexik:jwt:generate-keypair

Configuration du firewall dans config/packages/security.yaml :

# config/packages/security.yaml
security:
    firewalls:
        api:
            pattern: ^/api
            stateless: true
            jwt: ~

    access_control:
        - { path: ^/api/login, roles: PUBLIC_ACCESS }
        - { path: ^/api, roles: IS_AUTHENTICATED_FULLY }

Les attributs security sur chaque operation affinent le controle d'acces au niveau de la ressource. Pour une couverture complete de la securite Symfony, consultez l'article sur l'authentification et l'autorisation.

Documentation OpenAPI

API Platform genere une documentation OpenAPI 3.1 complete automatiquement. Elle inclut les schemas des entites, les parametres de filtres, les codes de reponse et les exemples. Vous pouvez la personnaliser via des attributs :

#[ApiResource(
    description: 'Gestion des articles du blog',
    extraProperties: [
        'standard_put' => true,
    ],
)]

La documentation est servie par Swagger UI sur /api et exportable en JSON sur /api/docs.json. C'est un outil precieux pour les developpeurs front-end qui consomment votre API.

State Providers : sources de donnees alternatives

API Platform n'est pas lie a Doctrine. Vous pouvez connecter n'importe quelle source de donnees en implementant un State Provider :

// src/State/ExternalArticleProvider.php
namespace App\State;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;

class ExternalArticleProvider implements ProviderInterface
{
    public function __construct(
        private HttpClientInterface $httpClient,
    ) {}

    public function provide(
        Operation $operation,
        array $uriVariables = [],
        array $context = [],
    ): iterable|object|null {
        $response = $this->httpClient->request('GET', 'https://api.externe.com/articles');

        return $response->toArray();
    }
}

Pour les appels a des APIs externes, le guide du HTTP Client Symfony detaille les bonnes pratiques de consommation d'APIs tierces.

Bonnes pratiques

  • Groupes de serialisation - definissez toujours des groupes, ne retournez jamais toutes les proprietes
  • Pagination - limitez le nombre d'items par page (20-50 maximum)
  • Validation - utilisez les contraintes Symfony, API Platform les execute automatiquement
  • Versionning - preferez le versionning par header (Accept: application/vnd.api.v2+json) au versionning par URL
  • Cache HTTP - activez les en-tetes Cache-Control et ETag pour les operations GET
  • Securite - appliquez le principe du moindre privilege sur chaque operation

Aller plus loin

API Platform est un ecosysteme complet qui merite d'etre explore en profondeur. Cet article couvre les bases solides pour demarrer un projet d'API serieuse. Pour les sujets connexes, consultez le guide du Serializer, le HTTP Client pour consommer des APIs, et la securite Symfony.

Vous avez un projet d'API a construire avec Symfony et API Platform ? Consultez mes services de developpement, mes tarifs et contactez-moi. En tant que developpeur freelance en France et Belgique, je concois des APIs robustes et bien documentees.

Questions fréquentes

13 projets livrésGrand-Est & BelgiqueLighthouse >90Disponible immédiatement

Un projet en tête ?

Discutons de votre site web. Réponse garantie sous 24h.

Ou appelez directement :06 95 41 30 25

WhatsApp
Appeler