feat: debut du domain Shared avec Contracts et Jobs + rules pour cursor
This commit is contained in:
parent
19a697c712
commit
ca9a74fe69
@@ -3,19 +3,16 @@ 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
|
||||
|
||||
├── 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
|
||||
@@ -23,18 +20,30 @@ Domain/Manga/Infrastructure/ApiPlatform/
|
||||
### 1. Resources
|
||||
- Localisation : `Infrastructure/ApiPlatform/Resource/`
|
||||
- Principes :
|
||||
- Une classe = une ressource API
|
||||
- Une Resource par Operation
|
||||
- Validation des données avec les attributs Symfony dans la Resource
|
||||
- Documentation exhaustive avec les attributs PHP 8
|
||||
- Validation contraintes avec les attributs Symfony
|
||||
- Nommage : `{Nom}Resource`
|
||||
- Nommage : `{Operation}Resource`
|
||||
- Contient tous les attributs nécessaires en public
|
||||
- Doit implémenter les interfaces de validation appropriées
|
||||
|
||||
### 2. State Providers/Processors
|
||||
- Localisation : `Infrastructure/ApiPlatform/State/`
|
||||
### 2. State Providers
|
||||
- Localisation : `Infrastructure/ApiPlatform/State/Provider/`
|
||||
- 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`
|
||||
- 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
|
||||
|
||||
@@ -44,9 +53,7 @@ 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(
|
||||
@@ -55,84 +62,44 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
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'
|
||||
]
|
||||
]
|
||||
]
|
||||
output: GetMangaResource::class,
|
||||
description: 'Récupère un manga par son identifiant'
|
||||
)
|
||||
]
|
||||
)]
|
||||
class MangaResource
|
||||
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\Shared\Contract\Response;
|
||||
use App\Domain\Manga\Infrastructure\ApiPlatform\Resource\GetMangaResource;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
|
||||
class GetMangaStateProvider implements ProviderInterface
|
||||
@@ -141,7 +108,7 @@ class GetMangaStateProvider implements ProviderInterface
|
||||
private readonly MessageBusInterface $queryBus
|
||||
) {}
|
||||
|
||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?Response
|
||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?GetMangaResource
|
||||
{
|
||||
$query = new GetMangaByIdQuery($uriVariables['id']);
|
||||
$response = $this->queryBus->dispatch($query);
|
||||
@@ -150,18 +117,72 @@ class GetMangaStateProvider implements ProviderInterface
|
||||
return null;
|
||||
}
|
||||
|
||||
return $response;
|
||||
return new GetMangaResource(
|
||||
id: $response->id,
|
||||
title: $response->title,
|
||||
description: $response->description,
|
||||
authors: $response->authors,
|
||||
coverUrl: $response->coverUrl
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. State Processor
|
||||
### 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
|
||||
@@ -170,9 +191,9 @@ class CreateMangaStateProcessor implements ProcessorInterface
|
||||
private readonly MessageBusInterface $commandBus
|
||||
) {}
|
||||
|
||||
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): void
|
||||
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): int
|
||||
{
|
||||
assert($data instanceof MangaResource);
|
||||
assert($data instanceof CreateMangaResource);
|
||||
|
||||
$command = new CreateMangaCommand(
|
||||
title: $data->title,
|
||||
@@ -182,6 +203,8 @@ class CreateMangaStateProcessor implements ProcessorInterface
|
||||
);
|
||||
|
||||
$this->commandBus->dispatch($command);
|
||||
|
||||
return Response::HTTP_CREATED;
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -195,19 +218,7 @@ class CreateMangaStateProcessor implements ProcessorInterface
|
||||
- Documentation des codes d'erreur
|
||||
|
||||
### 2. Validation
|
||||
- Validation stricte des entrées
|
||||
- Validation dans les Resources uniquement
|
||||
- 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é
|
||||
- Validation des types et formats
|
||||
Reference in New Issue
Block a user