Providers et Processors dans API Platform 4

Providers et Processors dans API Platform 4

Providers et Processors dans API Platform 4 : Maîtrisez la logique métier de vos APIs

API Platform 4 a introduit une architecture révolutionnaire avec les Providers et Processors, remplaçant l’ancien système basé sur les DataProviders et DataPersisters. Cette nouvelle approche offre plus de flexibilité et de clarté dans la gestion de la logique métier de vos APIs.

Qu’est-ce qu’un Provider ?

Un Provider est responsable de la récupération des données dans votre API. Il s’agit du point d’entrée pour toutes les opérations de lecture (GET). Contrairement aux anciens DataProviders, les Providers sont plus granulaires et permettent une meilleure séparation des responsabilités.

Anatomie d’un Provider

<?php

declare(strict_types=1);

namespace App\Provider;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Entity\Article;
use App\Repository\ArticleRepository;

final class ArticleProvider implements ProviderInterface
{
    public function __construct(
        private readonly ArticleRepository $articleRepository
    ) {}

    public function provide(
        Operation $operation, 
        array $uriVariables = [], 
        array $context = []
    ): object|array|null {
        // Logique de récupération des données
        if ($operation instanceof GetCollection) {
            return $this->articleRepository->findPublished();
        }
        
        if ($operation instanceof Get) {
            return $this->articleRepository->find($uriVariables['id']);
        }
        
        return null;
    }
}

Qu’est-ce qu’un Processor ?

Un Processor gère les opérations de modification des données (POST, PUT, PATCH, DELETE). Il encapsule la logique métier nécessaire pour traiter les données avant et après leur persistance.

Anatomie d’un Processor

<?php

declare(strict_types=1);

namespace App\Processor;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Entity\Article;
use App\Service\SlugGenerator;
use Doctrine\ORM\EntityManagerInterface;

final class ArticleProcessor implements ProcessorInterface
{
    public function __construct(
        private readonly EntityManagerInterface $entityManager,
        private readonly SlugGenerator $slugGenerator
    ) {}

    public function process(
        mixed $data, 
        Operation $operation, 
        array $uriVariables = [], 
        array $context = []
    ): mixed {
        if (!$data instanceof Article) {
            return $data;
        }
        
        // Logique métier avant persistance
        if ($operation instanceof Post) {
            $data->setSlug($this->slugGenerator->generate($data->getTitle()));
            $data->setCreatedAt(new \DateTimeImmutable());
        }
        
        if ($operation instanceof Put || $operation instanceof Patch) {
            $data->setUpdatedAt(new \DateTimeImmutable());
        }
        
        // Persistance
        $this->entityManager->persist($data);
        $this->entityManager->flush();
        
        return $data;
    }
}

Configuration dans votre entité

Pour utiliser vos Providers et Processors, configurez-les directement dans vos entités avec les attributs API Platform :

<?php

declare(strict_types=1);

namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
use App\Provider\ArticleProvider;
use App\Processor\ArticleProcessor;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ApiResource(
    operations: [
        new GetCollection(provider: ArticleProvider::class),
        new Get(provider: ArticleProvider::class),
        new Post(processor: ArticleProcessor::class),
        new Put(processor: ArticleProcessor::class),
    ]
)]
class Article
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    private ?string $title = null;

    #[ORM\Column(length: 255)]
    private ?string $slug = null;

    #[ORM\Column(type: 'datetime_immutable')]
    private ?\DateTimeImmutable $createdAt = null;

    // ... getters et setters
}

Bonnes pratiques à suivre

1. Un Provider/Processor par responsabilité

Créez des Providers et Processors spécifiques pour chaque logique métier :

// Pour les articles publiés
final class PublishedArticleProvider implements ProviderInterface { /* ... */ }

// Pour les articles en brouillon
final class DraftArticleProvider implements ProviderInterface { /* ... */ }

// Pour la publication d'articles
final class PublishArticleProcessor implements ProcessorInterface { /* ... */ }

2. Injection de dépendances propre

Utilisez l’injection de dépendances pour garder vos classes testables :

public function __construct(
    private readonly ArticleRepository $repository,
    private readonly EventDispatcherInterface $eventDispatcher,
    private readonly ValidatorInterface $validator
) {}

3. Gestion des erreurs cohérente

Implémentez une gestion d’erreurs robuste :

public function provide(Operation $operation, array $uriVariables = [], array $context = []): mixed
{
    try {
        $article = $this->articleRepository->find($uriVariables['id']);
        
        if (!$article) {
            throw new NotFoundHttpException('Article not found');
        }
        
        return $article;
    } catch (Exception $e) {
        throw new BadRequestHttpException('Invalid request: ' . $e->getMessage());
    }
}

4. Validation des données

Validez toujours vos données dans les Processors :

public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
{
    $violations = $this->validator->validate($data);
    
    if (count($violations) > 0) {
        throw new ValidationException($violations);
    }
    
    // Traitement des données...
}

Avantages des Providers et Processors

Séparation claire des responsabilités

  • Providers : récupération des données
  • Processors : modification des données

Flexibilité accrue

  • Logique métier personnalisée par opération
  • Composition facilitée avec d’autres services

Testabilité améliorée

  • Classes plus petites et focalisées
  • Injection de dépendances claire

Performance optimisée

  • Chargement des données optimisé
  • Évite les requêtes inutiles

Exemple complet : Gestion d’un blog

Voici un exemple complet pour un système de blog avec authentification :

// Provider pour les articles avec gestion des permissions
final class SecureArticleProvider implements ProviderInterface
{
    public function __construct(
        private readonly ArticleRepository $repository,
        private readonly Security $security
    ) {}

    public function provide(Operation $operation, array $uriVariables = [], array $context = []): mixed
    {
        $user = $this->security->getUser();
        
        if ($operation instanceof GetCollection) {
            return $user?->hasRole('ROLE_ADMIN') 
                ? $this->repository->findAll()
                : $this->repository->findPublished();
        }
        
        $article = $this->repository->find($uriVariables['id']);
        
        if (!$article || (!$article->isPublished() && !$user?->hasRole('ROLE_ADMIN'))) {
            throw new NotFoundHttpException();
        }
        
        return $article;
    }
}

// Processor avec notifications
final class ArticleNotificationProcessor implements ProcessorInterface
{
    public function __construct(
        private readonly EntityManagerInterface $em,
        private readonly NotificationService $notificationService
    ) {}

    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
    {
        if (!$data instanceof Article) {
            return $data;
        }
        
        $this->em->persist($data);
        $this->em->flush();
        
        // Envoi de notifications
        if ($operation instanceof Post) {
            $this->notificationService->notifyNewArticle($data);
        }
        
        return $data;
    }
}

Conclusion

Les Providers et Processors d’API Platform 4 représentent une évolution majeure vers une architecture plus claire et maintenable. En suivant ces bonnes pratiques, vous créerez des APIs robustes, testables et évolutives.

Cette approche vous permet de :

  • Séparer clairement la logique de lecture et d’écriture
  • Implémenter une logique métier complexe de manière élégante
  • Maintenir un code propre et testable
  • Optimiser les performances de vos APIs

N’hésitez pas à expérimenter avec ces concepts pour tirer le meilleur parti d’API Platform 4 dans vos projets !


Cet article fait partie d’une série sur les nouvelles fonctionnalités d’API Platform 4. Restez connectés pour découvrir d’autres guides pratiques !

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *