Files
Mangarr/.cursor/rules/api_platform.mdc
ext.jeremy.guillot@maxicoffee.domains ed0a075a6c feat: route config pour les front
2025-03-24 18:21:07 +01:00

224 lines
6.4 KiB
Plaintext

---
description:
globs:
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