--- description: globs: alwaysApply: true --- # API Platform dans Mangarr ## Structure de l'API L'API est organisée dans la couche Infrastructure de chaque domaine : ``` Domain/Manga/Infrastructure/ApiPlatform/ ├── Resource/ # Configuration des ressources API │ └── MangaResource.php ├── State/ # Providers et Processors ├── Provider/ # State Providers └── Processor/ # State Processors ``` ## Règles d'Organisation ### 1. Resources - Localisation : `Infrastructure/ApiPlatform/Resource/` - Principes : - Une classe = une ressource API - Documentation exhaustive avec les attributs PHP 8 - Validation contraintes avec les attributs Symfony - Nommage : `{Nom}Resource` ### 2. State Providers/Processors - Localisation : `Infrastructure/ApiPlatform/State/` - Principes : - Utiliser les cas d'utilisation du domaine (Commands/Queries) - Ne pas contenir de logique métier - Conversion Resource ↔ Command/Query - Nommage : `{Action}{Resource}StateProvider/Processor` ## Exemples de Code ### 1. Resource API ```php namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\Post; use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\GetMangaStateProvider; use App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor\CreateMangaStateProcessor; use Symfony\Component\Validator\Constraints as Assert; #[ApiResource( shortName: 'Manga', operations: [ new Get( uriTemplate: '/mangas/{id}', provider: GetMangaStateProvider::class, description: 'Récupère un manga par son identifiant', openapi: [ 'summary' => 'Récupère un manga', '200' => [ 'description' => 'Manga trouvé', 'content' => [ 'application/json' => [ 'schema' => [ 'type' => 'object', 'properties' => [ 'id' => ['type' => 'string', 'format' => 'uuid'], 'title' => ['type' => 'string'], 'description' => ['type' => 'string', 'nullable' => true], 'authors' => [ 'type' => 'array', 'items' => ['type' => 'string'] ], 'coverUrl' => ['type' => 'string', 'format' => 'uri', 'nullable' => true] ], 'required' => ['id', 'title', 'authors'] ] ] ] ], '404' => [ 'description' => 'Manga non trouvé' ] ] ), new Post( uriTemplate: '/mangas', processor: CreateMangaStateProcessor::class, description: 'Crée un nouveau manga', openapi: [ 'requestBody' => [ 'content' => [ 'application/json' => [ 'schema' => [ 'type' => 'object', 'properties' => [ 'title' => ['type' => 'string'], 'description' => ['type' => 'string', 'nullable' => true], 'authors' => [ 'type' => 'array', 'items' => ['type' => 'string'] ], 'coverUrl' => ['type' => 'string', 'format' => 'uri', 'nullable' => true] ], 'required' => ['title'] ] ] ] ], 'responses' => [ '201' => [ 'description' => 'Manga créé' ], '400' => [ 'description' => 'Données invalides' ] ] ] ) ] )] class MangaResource { } ``` ### 2. State Provider ```php namespace App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProviderInterface; use App\Domain\Manga\Application\Query\GetMangaByIdQuery; use App\Domain\Shared\Contract\Response; use Symfony\Component\Messenger\MessageBusInterface; class GetMangaStateProvider implements ProviderInterface { public function __construct( private readonly MessageBusInterface $queryBus ) {} public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?Response { $query = new GetMangaByIdQuery($uriVariables['id']); $response = $this->queryBus->dispatch($query); if (null === $response) { return null; } return $response; } } ``` ### 3. State Processor ```php namespace App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProcessorInterface; use App\Domain\Manga\Application\Command\CreateMangaCommand; use Symfony\Component\Messenger\MessageBusInterface; class CreateMangaStateProcessor implements ProcessorInterface { public function __construct( private readonly MessageBusInterface $commandBus ) {} public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): void { assert($data instanceof MangaResource); $command = new CreateMangaCommand( title: $data->title, description: $data->description, authors: $data->authors, coverUrl: $data->coverUrl ); $this->commandBus->dispatch($command); } } ``` ## Bonnes Pratiques ### 1. Documentation - Documentation exhaustive des endpoints - Description claire des paramètres - Exemples de requêtes/réponses - Documentation des codes d'erreur ### 2. Validation - Validation stricte des entrées - Groupes de validation par contexte - Messages d'erreur explicites - Validation des types et formats ### 3. Sécurité - Définition claire des accès - Validation des permissions - Sanitization des entrées - Gestion des erreurs sécurisée ### 4. Performance - Pagination par défaut - Sélection des champs (sparse fieldsets) - Gestion des includes (relationships) - Cache approprié