Files
Mangarr/.claude/skills/api-platform/SKILL.md
ext.jeremy.guillot@maxicoffee.domains dae215dd3d
All checks were successful
Build and Deploy / deploy (push) Successful in 9m36s
feat: ajout de claude + correction des tests
2026-03-09 17:09:31 +01:00

7.6 KiB

name, description, allowed-tools
name description allowed-tools
api-platform Conventions API Platform du projet Mangarr — brancher un State Processor sur une Command, un State Provider sur une Query, nommage des Resources, gestion des DTOs. Utiliser quand on crée ou modifie une Resource, un State Processor/Provider, ou un DTO API Platform. Read, Grep, Glob

API Platform — Mangarr

Tout le code API Platform vit dans Infrastructure/ApiPlatform/ du domaine concerné.

Infrastructure/ApiPlatform/
  Resource/
    {FeatureName}Resource.php     ← classe vide avec attribut #[ApiResource]
  State/
    Processor/
      {DoSomething}Processor.php  ← implémente ProcessorInterface → Command
    Provider/
      {GetSomething}StateProvider.php ← implémente ProviderInterface → Query
  Dto/
    {Name}.php                    ← données entrantes ou sortantes
  Controller/
    {Action}Controller.php        ← uniquement pour cas non-standards

Resource

Classe vide — elle ne contient que l'attribut #[ApiResource]. Aucune logique.

// Infrastructure/ApiPlatform/Resource/MangaResource.php
namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Delete;
use App\Domain\Manga\Infrastructure\ApiPlatform\Dto\MangaDetail;
use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\GetMangaStateProvider;
use App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor\CreateMangaProcessor;

#[ApiResource(
    shortName: 'Manga',
    operations: [
        new Get(
            uriTemplate: '/mangas/by-id/{id}',
            provider: GetMangaStateProvider::class,
            output: MangaDetail::class,
        ),
        new Post(
            uriTemplate: '/mangas',
            input: CreateMangaDto::class,
            processor: CreateMangaProcessor::class,
        ),
        new Delete(
            uriTemplate: '/mangas/{id}',
            provider: DeleteMangaProvider::class,   // ← provider requis pour Delete
            processor: DeleteMangaProcessor::class,
        ),
    ]
)]
class MangaResource {}

Règles Resource :

  • shortName = nom du concept métier (ex: 'Manga', 'Chapter').
  • uriTemplate explicite (pas de génération automatique depuis le nom de classe).
  • output = DTO de sortie, input = DTO d'entrée.
  • provider et processor référencés par ::class.

State Processor → Command (écriture)

// Infrastructure/ApiPlatform/State/Processor/{DoSomething}Processor.php
namespace App\Domain\{Domain}\Infrastructure\ApiPlatform\State\Processor;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Domain\{Domain}\Application\Command\{DoSomething};
use App\Domain\{Domain}\Application\CommandHandler\{DoSomething}Handler;
use App\Domain\{Domain}\Domain\Exception\{Something}NotFoundException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

readonly class {DoSomething}Processor implements ProcessorInterface
{
    public function __construct(
        private {DoSomething}Handler $handler,
    ) {}

    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): void
    {
        try {
            $this->handler->handle(new {DoSomething}(
                $uriVariables['id'] ?? $data->someField,
            ));
        } catch ({Something}NotFoundException $e) {
            throw new NotFoundHttpException($e->getMessage());
        }
    }
}

Règles Processor :

  • Injecte le Handler concret (pas une interface, car l'Infrastructure peut dépendre de l'Application).
  • Traduit les Domain Exceptions en HTTP Exceptions Symfony (NotFoundHttpException, UnprocessableEntityHttpException…).
  • Retourne void pour les opérations sans corps de réponse, ou le DTO de sortie si nécessaire.

State Provider → Query (lecture)

// Infrastructure/ApiPlatform/State/Provider/{GetSomething}StateProvider.php
namespace App\Domain\{Domain}\Infrastructure\ApiPlatform\State\Provider;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Domain\{Domain}\Application\Query\{GetSomething};
use App\Domain\{Domain}\Application\QueryHandler\{GetSomething}Handler;
use App\Domain\{Domain}\Infrastructure\ApiPlatform\Dto\{Something}Detail;

readonly class {GetSomething}StateProvider implements ProviderInterface
{
    public function __construct(
        private {GetSomething}Handler $handler,
    ) {}

    public function provide(Operation $operation, array $uriVariables = [], array $context = []): {Something}Detail
    {
        $query = new {GetSomething}($uriVariables['id']);
        $response = $this->handler->handle($query);

        return new {Something}Detail(
            id: $response->id,
            title: $response->title,
            // mapper Response → DTO ici
        );
    }
}

Règles Provider :

  • Construit la Query depuis $uriVariables et/ou $context['filters'].
  • Mappe le Response (Application) vers un DTO (Infrastructure) — ne jamais retourner un Response directement.
  • Pour les collections, retourner un tableau ou un objet Paginator.

DTOs

Les DTOs sont des classes de données spécifiques à la couche HTTP. Ils ne doivent pas contenir de logique.

// Infrastructure/ApiPlatform/Dto/MangaDetail.php
namespace App\Domain\Manga\Infrastructure\ApiPlatform\Dto;

readonly class MangaDetail
{
    public function __construct(
        public string $id,
        public string $title,
        public string $slug,
        public ?string $imageUrl,
        // ...
    ) {}
}

Règles DTO :

  • readonly class.
  • Uniquement des types PHP natifs.
  • DTO d'entrée : contient les champs que le client envoie (input:).
  • DTO de sortie : contient les champs que l'API retourne (output:).
  • Ne jamais réutiliser un Response Application comme DTO API Platform (couches séparées).

Conventions de nommage

Fichier Pattern de nom Exemple
Resource (opération GET) {Concept}Resource.php MangaResource.php
Resource (opération spécifique) {Action}{Concept}Resource.php CreateMangaResource.php
Processor {DoSomething}Processor.php CreateMangaProcessor.php
Provider (GET item) Get{Concept}StateProvider.php GetMangaStateProvider.php
Provider (GET collection) Get{Concept}ListStateProvider.php GetMangaListStateProvider.php
Provider (Delete, nécessite item) Delete{Concept}Provider.php DeleteMangaProvider.php
DTO de sortie (item) {Concept}Detail.php MangaDetail.php
DTO de sortie (liste) {Concept}ListItem.php MangaListItem.php
DTO de sortie (collection) {Concept}Collection.php MangaCollection.php

Flux complet : une opération POST

HTTP POST /mangas
  → CreateMangaResource (#[ApiResource] avec processor:)
    → CreateMangaProcessor::process($data, ...)
      → CreateMangaFromMangadexHandler::handle(new CreateMangaFromMangadex(...))
        → Domain : Manga::__construct(...) + invariants
        → MangaRepositoryInterface::save($manga)
        → MessageBus::dispatch(new MangaCreated(...))