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

View File

@@ -0,0 +1,237 @@
# Framework Make Collection
Génère une classe Collection typée avec traits Atournayre.
## Vue d'ensemble
Cette skill crée une classe Collection type-safe pour gérer des ensembles d'entités selon les principes Elegant Objects.
## Caractéristiques
### Classe Collection générée
- Classe `final`
- Type-safe (collection d'objets typés)
- Interfaces Atournayre complètes
- Traits pour fonctionnalités de base
- Factory statique `asList()`
- Méthode `toLog()` pour logging
## Utilisation
```bash
Use skill framework:make:collection
```
Vous serez invité à fournir le nom de l'entité.
## Exemple d'utilisation
```bash
EntityName: Product
```
Génère :
```php
// src/Collection/ProductCollection.php
final class ProductCollection implements AsListInterface, ToArrayInterface, CountInterface, ...
{
use Collection;
use Collection\ToArray;
use Collection\Countable;
public static function asList(array $collection): self
{
return new self(PrimitiveCollection::of($collection));
}
public function toLog(): array
{
return [
'count' => $this->count()->value(),
'items' => $this->collection->map(fn (Product $item) => $item->toLog()),
];
}
}
```
## Structure créée
```
src/
└── Collection/
└── {EntityName}Collection.php
```
## Prérequis
- L'entité doit exister dans `src/Entity/{EntityName}.php`
- Framework `atournayre/framework` installé
## Interfaces implémentées
- **AsListInterface** - Factory `asList()`
- **ToArrayInterface** - Conversion en array
- **CountInterface** - Comptage d'éléments
- **CountByInterface** - Comptage conditionnel
- **AtLeastOneElementInterface** - Vérification présence
- **HasSeveralElementsInterface** - Vérification multiple
- **HasNoElementInterface** - Vérification vide
- **HasOneElementInterface** - Vérification unique
- **HasXElementsInterface** - Vérification nombre exact
- **LoggableInterface** - Support logging
## Méthodes disponibles via traits
```php
// Création
$products = ProductCollection::asList([$product1, $product2]);
// Comptage
$products->count(); // Number
$products->hasNoElement(); // bool
$products->hasOneElement(); // bool
$products->hasSeveralElements(); // bool
$products->hasAtLeastOneElement(); // bool
$products->hasXElements(5); // bool
// Comptage conditionnel
$activeCount = $products->countBy(fn (Product $p) => $p->isActive());
// Conversion
$array = $products->toArray();
// Logging
$log = $products->toLog();
```
## Enrichissement (principe YAGNI)
**IMPORTANT** : N'ajouter que les méthodes **explicitement demandées**.
### Exemple : filtrage
```php
public function active(): self
{
return new self(
$this->collection->filter(fn (Product $p) => $p->isActive())
);
}
public function inStock(): self
{
return new self(
$this->collection->filter(fn (Product $p) => $p->stock() > 0)
);
}
```
### Exemple : calculs
```php
public function totalPrice(): float
{
return $this->collection
->map(fn (Product $p) => $p->price())
->reduce(fn (float $sum, float $price) => $sum + $price, 0.0);
}
public function averagePrice(): float
{
if ($this->hasNoElement()) {
return 0.0;
}
return $this->totalPrice() / $this->count()->value();
}
```
### Exemple : tri
```php
public function sortedByName(): self
{
return new self(
$this->collection->sort(fn (Product $a, Product $b) =>
$a->name() <=> $b->name()
)
);
}
public function sortedByPriceDesc(): self
{
return new self(
$this->collection->sort(fn (Product $a, Product $b) =>
$b->price() <=> $a->price()
)
);
}
```
### Exemple : recherche
```php
public function findById(Uuid $id): ?Product
{
return $this->collection
->filter(fn (Product $p) => $p->id()->equals($id))
->first();
}
public function findByName(string $name): self
{
return new self(
$this->collection->filter(fn (Product $p) => $p->name() === $name)
);
}
```
## Usage dans le code
### Depuis un repository
```php
final class ProductRepository extends ServiceEntityRepository
{
public function findAllAsCollection(): ProductCollection
{
return ProductCollection::asList($this->findAll());
}
public function findActiveAsCollection(): ProductCollection
{
return ProductCollection::asList(
$this->createQueryBuilder('p')
->where('p.isActive = true')
->getQuery()
->getResult()
);
}
}
```
### Dans un service
```php
final readonly class ProductService
{
public function calculateTotalStock(ProductCollection $products): int
{
return $products->collection
->map(fn (Product $p) => $p->stock())
->reduce(fn (int $sum, int $stock) => $sum + $stock, 0);
}
}
```
### Dans un contrôleur
```php
public function index(ProductRepository $repository): Response
{
$products = $repository->findAllAsCollection();
return $this->render('product/index.html.twig', [
'products' => $products->active()->sortedByName(),
'total' => $products->count()->value(),
]);
}
```
## Principes Elegant Objects appliqués
- Classe finale
- Factory statique
- Type-safety
- Immutabilité (nouvelles instances pour transformations)
- Pas de méthodes génériques anticipées (YAGNI)
- LoggableInterface pour observabilité

View File

@@ -0,0 +1,192 @@
---
name: framework:make:collection
description: Génère classe Collection typée avec traits Atournayre
license: MIT
version: 1.0.0
---
# Framework Make Collection Skill
## Description
Génère une classe Collection typée pour gérer des ensembles d'entités avec les traits et interfaces Atournayre.
La Collection offre des méthodes pour manipuler des ensembles d'objets de manière type-safe et respectant les principes Elegant Objects.
## Usage
```
Use skill framework:make:collection
Vous serez invité à fournir :
- Le nom de l'entité (ex: Product, User, Order)
```
## Templates
- `Collection/UtilisateurCollection.php` - Template de classe Collection
## Variables requises
- **{EntityName}** - Nom de l'entité en PascalCase (ex: Utilisateur, Product)
- **{entityName}** - Nom de l'entité en camelCase (ex: utilisateur, product)
- **{namespace}** - Namespace du projet (défaut: App)
## Dépendances
- Requiert que l'entité existe dans `src/Entity/{EntityName}.php`
- Requiert framework `atournayre/framework`
## Outputs
- `src/Collection/{EntityName}Collection.php`
## Workflow
1. Demander le nom de l'entité (EntityName)
2. Vérifier que l'entité existe dans `src/Entity/{EntityName}.php`
- Si non : arrêter et demander de créer l'entité d'abord
3. Générer la classe Collection depuis le template :
- Remplacer `{EntityName}` par le nom fourni
- Remplacer `{entityName}` par la version camelCase
4. Afficher le fichier créé
## Patterns appliqués
### Classe Collection
- Classe `final`
- Implémente interfaces Atournayre :
- AsListInterface
- ToArrayInterface
- CountInterface
- CountByInterface
- AtLeastOneElementInterface
- HasSeveralElementsInterface
- HasNoElementInterface
- HasOneElementInterface
- HasXElementsInterface
- LoggableInterface
- Utilise traits Atournayre :
- Collection
- Collection\ToArray
- Collection\Countable
- Méthode statique `asList(array $collection)`
- Méthode `toLog()` pour logging
## Exemple
```bash
Use skill framework:make:collection
# Saisies utilisateur :
EntityName: Product
# Résultat :
✓ src/Collection/ProductCollection.php
```
Fichier généré :
```php
<?php
declare(strict_types=1);
namespace App\Collection;
use App\Entity\Product;
use Atournayre\Contracts\Collection\AsListInterface;
use Atournayre\Contracts\Collection\AtLeastOneElementInterface;
use Atournayre\Contracts\Collection\CountByInterface;
use Atournayre\Contracts\Collection\CountInterface;
use Atournayre\Contracts\Collection\HasNoElementInterface;
use Atournayre\Contracts\Collection\HasOneElementInterface;
use Atournayre\Contracts\Collection\HasSeveralElementsInterface;
use Atournayre\Contracts\Collection\HasXElementsInterface;
use Atournayre\Contracts\Collection\ToArrayInterface;
use Atournayre\Contracts\Log\LoggableInterface;
use Atournayre\Primitives\Collection as PrimitiveCollection;
use Atournayre\Primitives\Traits\Collection;
final class ProductCollection implements AsListInterface, ToArrayInterface, CountInterface, CountByInterface, AtLeastOneElementInterface, HasSeveralElementsInterface, HasNoElementInterface, HasOneElementInterface, HasXElementsInterface, LoggableInterface
{
use Collection;
use Collection\ToArray;
use Collection\Countable;
public static function asList(array $collection): self
{
return new self(PrimitiveCollection::of($collection));
}
/**
* @return array<string, mixed>
*/
public function toLog(): array
{
return [
'count' => $this->count()->value(),
'items' => $this->collection->map(fn (Product $item) => $item->toLog()),
];
}
// UNIQUEMENT les méthodes EXPLICITEMENT demandées par l'utilisateur
// PAS d'anticipation de besoins futurs
// PAS de méthodes génériques (add, remove, filter, map, etc.)
}
```
## Usage
### Création d'une collection
```php
$products = ProductCollection::asList([
$product1,
$product2,
$product3,
]);
```
### Méthodes disponibles (via traits)
```php
// Comptage
$count = $products->count(); // Atournayre\Primitives\Number
$hasElements = $products->hasNoElement(); // bool
$hasOne = $products->hasOneElement(); // bool
$hasSeveral = $products->hasSeveralElements(); // bool
$hasAtLeastOne = $products->hasAtLeastOneElement(); // bool
$hasX = $products->hasXElements(5); // bool
// Conversion
$array = $products->toArray(); // array
// Comptage personnalisé
$activeCount = $products->countBy(fn (Product $p) => $p->isActive());
```
### Ajout de méthodes métier (YAGNI)
N'ajouter que les méthodes **explicitement demandées** :
```php
final class ProductCollection implements ...
{
// ... traits ...
public function active(): self
{
return new self(
$this->collection->filter(fn (Product $p) => $p->isActive())
);
}
public function totalPrice(): float
{
return $this->collection
->map(fn (Product $p) => $p->price())
->reduce(fn (float $sum, float $price) => $sum + $price, 0.0);
}
}
```
## Notes
- Respect du principe YAGNI : pas de méthodes génériques anticipées
- Seules les méthodes explicitement demandées doivent être ajoutées
- Les traits fournissent déjà beaucoup de fonctionnalités
- La collection est type-safe (typage sur l'entité)
- LoggableInterface permet le logging automatique

View File

@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace App\Collection;
use App\Entity\Utilisateur;
use Atournayre\Contracts\Collection\AsListInterface;
use Atournayre\Contracts\Collection\AtLeastOneElementInterface;
use Atournayre\Contracts\Collection\CountByInterface;
use Atournayre\Contracts\Collection\CountInterface;
use Atournayre\Contracts\Collection\HasNoElementInterface;
use Atournayre\Contracts\Collection\HasOneElementInterface;
use Atournayre\Contracts\Collection\HasSeveralElementsInterface;
use Atournayre\Contracts\Collection\HasXElementsInterface;
use Atournayre\Contracts\Collection\ToArrayInterface;
use Atournayre\Contracts\Log\LoggableInterface;
use Atournayre\Primitives\Collection as PrimitiveCollection;
use Atournayre\Primitives\Traits\Collection;
final class UtilisateurCollection implements AsListInterface, ToArrayInterface, CountInterface, CountByInterface, AtLeastOneElementInterface, HasSeveralElementsInterface, HasNoElementInterface, HasOneElementInterface, HasXElementsInterface, LoggableInterface
{
use Collection;
use Collection\ToArray;
use Collection\Countable;
public static function asList(array $collection): self
{
return new self(PrimitiveCollection::of($collection));
}
/**
* @return array<string, mixed>
*/
public function toLog(): array
{
return [
'count' => $this->count()->value(),
'items' => $this->collection->map(fn (Utilisateur $item) => $item->toLog()),
];
}
// UNIQUEMENT les méthodes EXPLICITEMENT demandées par l'utilisateur
// PAS d'anticipation de besoins futurs
// PAS de méthodes génériques (add, remove, filter, map, etc.)
}