7.6 KiB
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').uriTemplateexplicite (pas de génération automatique depuis le nom de classe).output= DTO de sortie,input= DTO d'entrée.provideretprocessorré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
voidpour 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
$uriVariableset/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
ResponseApplication 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(...))