feat: ajout de claude + correction des tests
All checks were successful
Build and Deploy / deploy (push) Successful in 9m36s
All checks were successful
Build and Deploy / deploy (push) Successful in 9m36s
This commit is contained in:
parent
b5a832fbbc
commit
dae215dd3d
208
.claude/skills/api-platform/SKILL.md
Normal file
208
.claude/skills/api-platform/SKILL.md
Normal file
@@ -0,0 +1,208 @@
|
||||
---
|
||||
name: api-platform
|
||||
description: 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.
|
||||
allowed-tools: 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.
|
||||
|
||||
```php
|
||||
// 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)
|
||||
|
||||
```php
|
||||
// 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)
|
||||
|
||||
```php
|
||||
// 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.
|
||||
|
||||
```php
|
||||
// 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(...))
|
||||
```
|
||||
Reference in New Issue
Block a user