--- description: globs: *.php alwaysApply: false --- ``` Domain/Manga/Infrastructure/ApiPlatform/ ├── Resource/ # Resources API par opération │ └── GetMangaResource.php # Resources pour l'opération Get │ └── CreateMangaResource.php # Resources pour l'opération Create ├── State/ # Providers et Processors par opération ├── Provider/ # State Providers │ └── GetMangaStateProvider.php └── Processor/ # State Processors └── CreateMangaStateProcessor.php ``` ## Règles d'Organisation ### 1. Resources - Localisation : `Infrastructure/ApiPlatform/Resource/` - Principes : - Une Resource par Operation - Validation des données avec les attributs Symfony dans la Resource - Documentation exhaustive avec les attributs PHP 8 - Nommage : `{Operation}Resource` - Contient tous les attributs nécessaires en public - Doit implémenter les interfaces de validation appropriées ### 2. State Providers - Localisation : `Infrastructure/ApiPlatform/State/Provider/` - Principes : - Un Provider par Operation de type Query - Utilise les QueryHandler du domaine - Convertit la Response du QueryHandler en Resource - Renvoie toujours une Resource - Nommage : `{Operation}StateProvider` ### 3. State Processors - Localisation : `Infrastructure/ApiPlatform/State/Processor/` - Principes : - Un Processor par Operation de type Command - Utilise les CommandHandler du domaine - Convertit la Resource en Command - Renvoie uniquement un code HTTP - Nommage : `{Operation}StateProcessor` ## Exemples de Code ### 1. Resource API ```php namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Get; use App\Domain\Manga\Infrastructure\ApiPlatform\State\Provider\GetMangaStateProvider; use Symfony\Component\Validator\Constraints as Assert; #[ApiResource( shortName: 'Manga', operations: [ new Get( uriTemplate: '/mangas/{id}', provider: GetMangaStateProvider::class, output: GetMangaResource::class, description: 'Récupère un manga par son identifiant' ) ] )] class GetMangaResource { public function __construct( #[Assert\NotBlank] #[Assert\Uuid] public readonly string $id, #[Assert\NotBlank] public readonly string $title, public readonly ?string $description = null, #[Assert\NotBlank] #[Assert\All([ new Assert\Type('string') ])] public readonly array $authors = [], #[Assert\Url] public readonly ?string $coverUrl = null ) {} } ``` ### 2. State Provider ```php namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource\CreateManga; 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\Manga\Infrastructure\ApiPlatform\Resource\GetMangaResource; 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 = []): ?GetMangaResource { $query = new GetMangaByIdQuery($uriVariables['id']); $response = $this->queryBus->dispatch($query); if (null === $response) { return null; } return new GetMangaResource( id: $response->id, title: $response->title, description: $response->description, authors: $response->authors, coverUrl: $response->coverUrl ); } } ``` ### 3. Resource CreateManga ```php namespace App\Domain\Manga\Infrastructure\ApiPlatform\Resource; use ApiPlatform\Metadata\ApiResource; use ApiPlatform\Metadata\Post; use App\Domain\Manga\Infrastructure\ApiPlatform\State\Processor\CreateMangaStateProcessor; use Symfony\Component\Validator\Constraints as Assert; #[ApiResource( shortName: 'Manga', operations: [ new Post( uriTemplate: '/mangas', processor: CreateMangaStateProcessor::class, input: CreateMangaResource::class, status: 201, description: 'Crée un nouveau manga' ) ] )] class CreateMangaResource { public function __construct( #[Assert\NotBlank(message: 'Le titre est obligatoire')] #[Assert\Length(min: 1, max: 255)] public readonly string $title, #[Assert\Length(max: 1000)] public readonly ?string $description = null, #[Assert\NotNull] #[Assert\Count(min: 1, max: 10)] #[Assert\All([ new Assert\Type('string'), new Assert\Length(min: 1, max: 100) ])] public readonly array $authors = [], #[Assert\Url] #[Assert\Length(max: 255)] public readonly ?string $coverUrl = null ) {} } ``` ### 4. 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 App\Domain\Manga\Infrastructure\ApiPlatform\Resource\CreateMangaResource; use Symfony\Component\HttpFoundation\Response; 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 = []): int { assert($data instanceof CreateMangaResource); $command = new CreateMangaCommand( title: $data->title, description: $data->description, authors: $data->authors, coverUrl: $data->coverUrl ); $this->commandBus->dispatch($command); return Response::HTTP_CREATED; } } ``` ## 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 dans les Resources uniquement - Groupes de validation par contexte - Messages d'erreur explicites - Validation des types et formats