Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 17:58:52 +08:00
commit eac17f89fa
40 changed files with 4048 additions and 0 deletions

311
skills/make-story/README.md Normal file
View File

@@ -0,0 +1,311 @@
# Framework Make Story
Génère une Story Foundry pour fixtures de tests.
## Vue d'ensemble
Cette skill crée une Story Zenstruck Foundry pour orchestrer la création de fixtures de tests cohérentes et réutilisables.
## Caractéristiques
### Classe Story générée
- Extends Story
- Implements StoryInterface
- Classe `final`
- Méthode `build()` avec scénarios
- Utilise Factories pour créer instances
- Scénarios prédéfinis
### Classe AppStory
- Point d'entrée global
- Attribut #[AsFixture(name: 'main')]
- Charge toutes les stories
- Gère les dépendances entre stories
## Utilisation
```bash
Use skill framework:make:story
```
Vous serez invité à fournir le nom de l'entité.
## Exemple d'utilisation
```bash
EntityName: Product
```
Génère :
```php
// src/Story/ProductStory.php
final class ProductStory extends Story implements StoryInterface
{
public function build(): void
{
// Produit par défaut
ProductFactory::createOne();
// Produits avec IDs spécifiques
ProductFactory::new()
->withSpecificId('01234567-89ab-cdef-0123-456789abcdef')
->create();
// Plusieurs produits
ProductFactory::createMany(10);
}
}
// src/Story/AppStory.php (updated)
#[AsFixture(name: 'main')]
final class AppStory extends Story implements StoryInterface
{
public function build(): void
{
ProductStory::load();
// ... autres stories
}
}
```
## Structure créée
```
src/
└── Story/
├── {EntityName}Story.php
└── AppStory.php
```
## Prérequis
- L'entité doit exister
- La Factory doit exister (créée automatiquement si absente)
- StoryInterface doit exister dans Contracts
- Zenstruck Foundry installé
## Usage dans les tests
### Tests unitaires
```php
use App\Story\ProductStory;
final class ProductServiceTest extends TestCase
{
protected function setUp(): void
{
ProductStory::load();
}
public function testCalculateTotalPrice(): void
{
$products = ProductFactory::repository()->findAll();
$service = new ProductService();
$total = $service->calculateTotal($products);
self::assertGreaterThan(0, $total);
}
}
```
### Tests fonctionnels
```php
use App\Story\AppStory;
final class ProductControllerTest extends WebTestCase
{
protected function setUp(): void
{
AppStory::load();
}
public function testListProducts(): void
{
$client = static::createClient();
$client->request('GET', '/products');
self::assertResponseIsSuccessful();
self::assertSelectorExists('.product-item');
}
}
```
### Fixtures Doctrine
```bash
# Charger toutes les fixtures
php bin/console doctrine:fixtures:load --append
# Ou juste AppStory via attribut #[AsFixture]
```
## Enrichissement (principe YAGNI)
### Scénarios métier
```php
final class ProductStory extends Story implements StoryInterface
{
public function build(): void
{
// Scénario : catalogue actif
ProductFactory::new()
->active()
->createMany(20);
// Scénario : produits en promotion
ProductFactory::new()
->active()
->createMany(5, ['price' => 9.99]);
// Scénario : rupture de stock
ProductFactory::new()
->outOfStock()
->createMany(3);
// Scénario : nouveau produit vedette
ProductFactory::createOne([
'name' => 'Featured Product',
'price' => 199.99,
'stock' => 100,
'isActive' => true,
]);
}
}
```
### Avec relations
```php
final class ProductStory extends Story implements StoryInterface
{
public function build(): void
{
// Charger dépendances
CategoryStory::load();
// Récupérer catégories
$electronics = CategoryFactory::find(['name' => 'Electronics']);
$books = CategoryFactory::find(['name' => 'Books']);
// Produits par catégorie
ProductFactory::new()
->inCategory($electronics)
->createMany(10);
ProductFactory::new()
->inCategory($books)
->createMany(15);
}
}
```
### États nommés
```php
final class ProductStory extends Story implements StoryInterface
{
public function build(): void
{
// États nommés pour réutilisation
$this->addState('premium', ProductFactory::createOne([
'name' => 'Premium Product',
'price' => 999.99,
]));
$this->addState('cheap', ProductFactory::createOne([
'name' => 'Cheap Product',
'price' => 9.99,
]));
$this->addState('test_product', ProductFactory::createOne([
'id' => Uuid::fromString('01234567-89ab-cdef-0123-456789abcdef'),
]));
}
}
// Usage
$premium = ProductStory::load()->get('premium');
$cheap = ProductStory::load()->get('cheap');
```
### Scénarios complexes
```php
final class OrderStory extends Story implements StoryInterface
{
public function build(): void
{
// Charger dépendances
UserStory::load();
ProductStory::load();
$users = UserFactory::repository()->findAll();
$products = ProductFactory::repository()->findAll();
// Commandes par utilisateur
foreach ($users as $user) {
OrderFactory::createOne([
'user' => $user,
'items' => array_slice($products, 0, rand(1, 5)),
]);
}
// Commande de test spécifique
$this->addState('test_order', OrderFactory::createOne([
'user' => UserStory::load()->get('test_user'),
'items' => [ProductStory::load()->get('test_product')],
'status' => 'pending',
]));
}
}
```
## AppStory orchestration
```php
#[AsFixture(name: 'main')]
final class AppStory extends Story implements StoryInterface
{
public function build(): void
{
// Ordre important : dépendances d'abord
CategoryStory::load();
ProductStory::load();
UserStory::load();
OrderStory::load();
ReviewStory::load();
}
}
```
## Configuration Foundry
```yaml
# config/packages/zenstruck_foundry.yaml
when@dev:
zenstruck_foundry:
auto_refresh_proxies: false
when@test:
zenstruck_foundry:
auto_refresh_proxies: false
make_factory:
default_namespace: 'App\Factory'
```
## Commandes utiles
```bash
# Charger fixtures
php bin/console doctrine:fixtures:load --append
# Purger DB puis charger
php bin/console doctrine:fixtures:load
# Dans tests
AppStory::load();
ProductStory::load();
```
## Principes Elegant Objects appliqués
- Classe finale
- Implements StoryInterface
- Méthode `build()` claire et concise
- Scénarios nommés explicitement
- DRY : fixtures centralisées
- États nommés pour réutilisation