# Conventions de Code & Standards

## 1. Regles Generales

### Strict Types

**Obligatoire** dans tous les fichiers PHP :

```php
<?php

declare(strict_types=1);
```

Ceci est enforce par Laravel Pint. Tout fichier sans `declare(strict_types=1)` echouera au lint.

### Nommage

| Element | Convention | Exemple |
|---------|-----------|---------|
| Classes | PascalCase | `CreateAttendeeBookingAction` |
| Methodes | camelCase | `createBooking()` |
| Proprietes | camelCase | `$hourlyPrice` |
| Variables | camelCase | `$coachId` |
| Constantes | UPPER_SNAKE_CASE | `MAX_RETRY_COUNT` |
| Fichiers de classe | PascalCase.php | `BookingObserver.php` |
| Tables BDD | snake_case (pluriel) | `group_courses` |
| Colonnes BDD | snake_case | `hourly_price_amount` |
| Routes API | kebab-case | `/guest-invitations` |
| Config keys | snake_case | `booking_delay` |
| Enums | PascalCase (valeurs snake_case) | `OrderStatus::WAITING_PAYMENT` |
| Traits | PascalCase | `HasPublicId` |
| Interfaces | PascalCase | `Bookable` |
| Migrations | snake_case avec timestamp | `2023_01_15_create_bookings_table` |

### Organisation des Imports

Laravel Pint organise automatiquement les imports par groupe :
1. PHP natifs
2. Packages vendor (Illuminate, Spatie, etc.)
3. Classes applicatives (App\)

```php
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Spatie\MediaLibrary\HasMedia;

use App\Enums\OrderStatus;
use App\Models\Place;
```

---

## 2. Documentation Obligatoire (OpenAPI / Swagger)

### Principe

**Chaque nouveau controleur API DOIT etre documente** avec des annotations OpenAPI (`@OA\`). La documentation Swagger est generee automatiquement par l5-swagger et accessible a `/docs`.

### Info globale

Definie dans `app/Models/BaseModel.php` :

```php
/**
 * @OA\Info(
 *     title="Champion Spirit API",
 *     version="1.2.10",
 *     description="Champion Spirit Mobile API Documentation",
 *     @OA\Contact(email="ashwin@infozen-consulting.com", name="Ashwin Lutchmeenaraidoo"),
 *     @OA\License(name="Champion Spirit", url="https://www.championspirit.com/")
 * )
 */
```

### Schemas de securite

Deux schemas definis dans la config l5-swagger :
- `sanctum` : Bearer token (pour l'API authentifiee)
- `api_key` : Header X-API-KEY (pour le chatbot)

### Annotation sur un Controleur API

**OBLIGATOIRE pour chaque controleur :**

```php
<?php

declare(strict_types=1);

namespace App\Http\Controllers\Api\Booking;

use App\Http\Controllers\Controller;
use App\Http\Resources\CustomerBookingResource;
use App\Models\Booking;

final class ShowBookingController extends Controller
{
    /**
     * @OA\Get(
     *     path="/v1/bookings/{id}",
     *     operationId="showBooking",
     *     tags={"Bookings"},
     *     summary="Show booking details",
     *     description="Retrieves detailed information about a specific booking.",
     *     security={{"sanctum": {}}},
     *
     *     @OA\Parameter(
     *         name="id",
     *         in="path",
     *         required=true,
     *         description="Booking public UUID",
     *         @OA\Schema(type="string", format="uuid")
     *     ),
     *
     *     @OA\Response(
     *         response=200,
     *         description="Booking details",
     *         @OA\JsonContent(ref="#/components/schemas/CustomerBookingResource")
     *     ),
     *     @OA\Response(response=401, description="Unauthenticated"),
     *     @OA\Response(response=403, description="Forbidden"),
     *     @OA\Response(response=404, description="Booking not found"),
     * )
     */
    public function __invoke(Booking $booking): CustomerBookingResource
    {
        $this->authorize('view', $booking);

        return new CustomerBookingResource($booking->load(['coach', 'place', 'attendee']));
    }
}
```

### Annotation sur un Modele / Resource

**OBLIGATOIRE pour chaque modele expose dans l'API et chaque Resource :**

```php
/**
 * @OA\Schema(
 *     title="CustomerBookingResource",
 *     description="Booking resource for customer view",
 *
 *     @OA\Property(property="id", type="string", format="uuid", description="Public UUID"),
 *     @OA\Property(property="start_at", type="string", format="date-time"),
 *     @OA\Property(property="end_at", type="string", format="date-time"),
 *     @OA\Property(property="has_attended", type="boolean"),
 *     @OA\Property(property="coach", ref="#/components/schemas/CoachResource"),
 *     @OA\Property(property="place", ref="#/components/schemas/PlaceResource"),
 * )
 *
 * @mixin Booking
 */
final class CustomerBookingResource extends ApiResource { ... }
```

### Annotation RequestBody (POST/PUT)

```php
/**
 * @OA\Post(
 *     path="/v1/register",
 *     operationId="register",
 *     tags={"Account"},
 *     summary="Register a new user",
 *
 *     @OA\RequestBody(
 *         required=true,
 *         @OA\JsonContent(
 *             required={"firstname", "lastname", "email", "password"},
 *             @OA\Property(property="firstname", type="string", example="John"),
 *             @OA\Property(property="lastname", type="string", example="Doe"),
 *             @OA\Property(property="email", type="string", format="email"),
 *             @OA\Property(property="password", type="string", format="password", example="password123"),
 *             @OA\Property(property="lang", type="string", example="en"),
 *         ),
 *     ),
 *
 *     @OA\Response(response=201, description="User created"),
 *     @OA\Response(response=422, description="Validation error"),
 * )
 */
```

### Regenerer la documentation Swagger

```bash
php artisan l5-swagger:generate
```

Aussi execute automatiquement par `./reloadChanges.sh`.

---

## 3. Patterns de Code

### Controleurs

**Convention :** Un controleur = une action = une methode `__invoke`.

```php
final class ShowBookingController extends Controller
{
    /** @OA\Get(...) */
    public function __invoke(Request $request, Booking $booking): CustomerBookingResource
    {
        $this->authorize('view', $booking);

        return new CustomerBookingResource($booking->load(['coach', 'place', 'attendee']));
    }
}
```

**Points cles :**
- Classe `final`
- Methode `__invoke` unique
- Nommage : `{Verbe}{Sujet}Controller`
- Type de retour explicite (Resource, JsonResponse, Response)
- Injection de dependances via le constructeur ou les parametres de methode
- Route model binding avec public_id
- **Annotation Swagger @OA\ obligatoire**

### Format de Reponse API

Les Resources heritent de `ApiResource` (qui etend `JsonResource`) :

```php
// Pas de wrapping automatique
static $wrap = null;

// Status code personnalise
return (new UserResource($user))->statusCode(201);

// Pagination manuelle avec metadata
return CustomerBookingResource::collection($bookings)->additional([
    'totalItems' => $totalBookings,
    'totalPages' => ceil($totalBookings / $size),
]);
```

**Format de pagination standard :**
```json
{
    "data": [...],
    "totalItems": 42,
    "totalPages": 5
}
```

**Parametres de pagination :** `page` (defaut: 1) et `size` (defaut: 10), via `skip/take`.

### Actions

**Convention :** Classe invokable avec logique metier transactionnelle.

```php
final class CreateAttendeeBookingAction
{
    public function __construct(
        private readonly CheckIfPlaceIsOpenAction $checkIfPlaceIsOpen,
    ) {}

    /**
     * @throws BusyAttendeeException
     * @throws PlaceClosedException
     */
    public function __invoke(User $attendee, array $data): Booking
    {
        return DB::transaction(function () use ($attendee, $data) {
            ($this->checkIfPlaceIsOpen)($data['place'], $data['start_at']);

            if ($attendee->hasBookingAt($data['start_at'])) {
                throw new BusyAttendeeException();
            }

            return Booking::create([...]);
        });
    }
}
```

**Points cles :**
- Classe `final`
- Injection de dependances via constructeur avec `readonly`
- Methode `__invoke` ou `execute`
- **PHPDoc `@throws` pour chaque exception possible**
- **PHPDoc `@param` pour les collections generiques**
- Wrap dans `DB::transaction()` si multi-modeles
- Retry deadlock : 3 tentatives avec backoff exponentiel

### Gestion des Deadlocks

```php
private function withDeadlockRetry(Closure $callback, int $maxAttempts = 3): mixed
{
    for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
        try {
            return DB::transaction($callback);
        } catch (QueryException $e) {
            if ($attempt === $maxAttempts || ! $this->isDeadlock($e)) {
                throw $e;
            }
            usleep(100000 * ($attempt + 1) + random_int(0, 25000));
        }
    }
}
```

### Modeles

```php
/**
 * @property Money $hourly_price
 * @property-read Coach|null $coach
 * @property-read Collection<int, Booking> $bookings
 *
 * @OA\Schema(
 *     title="Booking",
 *     @OA\Property(property="id", type="string", format="uuid"),
 *     @OA\Property(property="start_at", type="string", format="date-time"),
 * )
 */
final class Booking extends BaseModel
{
    use HasFactory;
    use HasPublicId;
    use SoftDeletes;

    protected $fillable = [...];

    protected $casts = [
        'start_at' => 'immutable_datetime',
        'end_at'   => 'immutable_datetime',
    ];

    public function coach(): BelongsTo
    {
        return $this->belongsTo(Coach::class);
    }

    public function newEloquentBuilder($query): BookingBuilder
    {
        return new BookingBuilder($query);
    }
}
```

**Points cles :**
- **`@property` pour les proprietes Money et casts speciaux**
- **`@property-read` pour les relations**
- **`@OA\Schema` pour les modeles exposes dans l'API**
- Heritage de `BaseModel` (pas `Model` directement)
- `$fillable` explicite (pas de `$guarded = []`)
- `$casts` avec types explicites
- Relations avec type de retour

### Enums

```php
enum OrderStatus: string
{
    use HasEnumStaticMethods;

    case WAITING_PAYMENT = 'waiting_payment';
    case PAID = 'paid';
    case CANCELLED = 'cancelled';
}
```

### Resources API

```php
/**
 * @OA\Schema(
 *     title="PlaceResource",
 *     @OA\Property(property="id", type="string", format="uuid"),
 *     @OA\Property(property="name", type="string"),
 *     @OA\Property(property="currency", type="string", example="USD"),
 * )
 *
 * @mixin Place
 */
final class PlaceResource extends ApiResource
{
    public function toArray(Request $request): array
    {
        return [
            'id'            => $this->public_id,
            'name'          => $this->name,
            'currency'      => $this->currency,
            'coaches'       => CoachResource::collection($this->whenLoaded('coaches')),
            'created_at'    => $this->created_at?->toISOString(),
        ];
    }
}
```

**Points cles :**
- **`@OA\Schema` obligatoire**
- **`@mixin` pour indiquer le modele source**
- **Toujours retourner `public_id` comme `id`**
- `whenLoaded()` pour les relations conditionnelles
- Dates en ISO 8601

---

## 4. Systeme de Traduction (Multilingue)

### Vue d'ensemble

Le projet gere **4 langues** (en, es, fr, zh) avec **2 systemes** de traduction :

| Systeme | Stockage | Usage |
|---------|----------|-------|
| **Traductions UI** | Table `translations` | Chaines de l'app mobile et du back-office |
| **Traductions modeles** | Table `model_translations` | Champs de modeles (nom, description) |

### Traductions UI (Table `translations`)

```
Cle       : "booking.confirm.title"
Valeur    : "Confirm your booking"
Type      : APP (mobile) ou WEB (back-office)
Langue    : FK vers languages
Traduit   : is_translated (boolean, true = auto-traduit)
```

**API :** `GET /v1/languages/{lang}` retourne toutes les traductions APP pour une langue.

**Stockage JSON :** Les traductions sont aussi exportees en JSON dans :
- `storage/languages/back_office/{lang}.json` — Back-office
- `storage/languages/mobile_app/{lang}.json` — App mobile

### Traductions de Modeles (Trait HasDatabaseTranslations)

Pour les champs de modeles traduisibles (nom, description) :

```php
// Dans le modele
use HasDatabaseTranslations;

protected array $translatable = ['name', 'description'];

// Lecture
$facility->translate('fr', 'name');    // "Piscine"
$facility->translate('en', 'name');    // "Swimming Pool"

// Formulaire Filament : champs generes automatiquement
// name_en, name_fr, name_es, name_zh
```

**Modeles avec champs traduisibles :**
- `Facility` : name
- `Service` : name, description
- `Place` : description
- `Category` : name, description
- `Product` : name
- `FlexMembership` : name, description
- `Pack` : name
- `Banner` : title
- `FeaturedCategory` : name

### Ajouter un champ traduisible a un modele

1. Ajouter le champ dans `$translatable` :
```php
protected array $translatable = ['name', 'description', 'nouveau_champ'];
```

2. Dans le formulaire Filament, utiliser `HasTranslatableFields` :
```php
...HasTranslatableFields::translatableFields(
    $languages,
    $defaultLanguage,
    'nouveau_champ',
    'admin.model.nouveau_champ',
    ['type' => 'textarea', 'requiredOnDefault' => true]
)
```

3. Le trait gere automatiquement la sauvegarde dans `model_translations`.

### Auto-traduction

Le service `AutoTranslateService` traduit automatiquement via :
- **Google Translate** (gratuit, par defaut)
- **OpenAI GPT-4o-mini** (payant, meilleure qualite)

```bash
# Traduire toutes les chaines vers une langue
php artisan translations:translate {languageId} {type}

# Importer des traductions JSON
php artisan translations:import-json --type=web

# Vider le cache des traductions
php artisan translations:clear-cache {locale?}
```

### Regles pour les traductions

- **Langue par defaut :** Anglais (`en`), configurable
- **Champs requis** uniquement sur la langue par defaut
- **Placeholders preserves** : `:name`, `{variable}`, `%s` ne sont pas traduits
- **Cache** : Traductions cachees 1h avec cle `translations.{locale}.web`
- Apres modification : toujours vider le cache (`php artisan translations:clear-cache`)

---

## 5. Tests Obligatoires (Pest)

### Principe

**Chaque nouvelle fonctionnalite DOIT avoir des tests.** Le projet utilise Pest PHP.

### Structure des tests

```
tests/
|-- Unit/                    # Tests unitaires (logique isolee)
|   |-- Models/              # Tests de modeles (scopes, casts, attributs)
|   |-- OrderableConstraints/# Tests de contraintes
|   |-- Enums/               # Tests d'enums
|   |-- Traits/              # Tests de traits
|
|-- Feature/                 # Tests fonctionnels (stack complet)
|   |-- Actions/             # Tests d'actions metier
|   |-- Controllers/         # Tests d'endpoints API
|   |-- Chat/                # Tests du module chat
|   |-- Filament/            # Tests des resources admin
|   |-- Listeners/           # Tests d'event listeners
|   |-- Middleware/           # Tests de middleware
|   |-- Models/              # Tests d'integration modeles
|   |-- Observers/           # Tests d'observers
|   |-- Factories/           # Tests de booking factories
|   |-- Services/            # Tests de services
|
|-- TestSupport/             # Infrastructure de test
    |-- FakeBioStar2Service.php     # Stub BioStar2
    |-- StripeFakeHttpClient.php    # Stub Stripe
    |-- Models/                     # Modeles de test
    |-- Factories/                  # Factories de test
```

### Pattern de test API (Feature)

```php
<?php

declare(strict_types=1);

use App\Models\User;
use App\Models\Booking;
use Laravel\Sanctum\Sanctum;

it('can list user bookings', function () {
    $user = User::factory()->create();
    Sanctum::actingAs($user, [User::ABILITY_FULL]);

    Booking::factory()->count(3)->create(['attendee_id' => $user->id]);

    $this->getJson('/api/v1/bookings')
        ->assertOk()
        ->assertJsonCount(3, 'data');
});

it('cannot cancel another user booking', function () {
    $user = User::factory()->create();
    Sanctum::actingAs($user, [User::ABILITY_FULL]);

    $otherBooking = Booking::factory()->create();

    $this->deleteJson("/api/v1/bookings/{$otherBooking->public_id}")
        ->assertForbidden();
});
```

### Pattern de test Action (Feature)

```php
it('creates a booking for the attendee', function () {
    $user = User::factory()->create();
    $place = Place::factory()->create();
    $coach = Coach::factory()->create();

    $booking = app(CreateAttendeeBookingAction::class)(
        $user,
        ['place' => $place, 'coach' => $coach, 'start_at' => now()->addDay()]
    );

    expect($booking)->toBeInstanceOf(Booking::class);
    $this->assertDatabaseHas('bookings', ['attendee_id' => $user->id]);
});

it('throws BusyAttendeeException on conflict', function () {
    $user = User::factory()->create();
    Booking::factory()->create(['attendee_id' => $user->id, 'start_at' => $date]);

    expect(fn () => app(CreateAttendeeBookingAction::class)($user, $data))
        ->toThrow(BusyAttendeeException::class);
});
```

### Pattern de test Filament (Feature)

```php
use function Pest\Livewire\livewire;

beforeEach(fn () => $this->actingAs(
    User::factory()->employeeAdmin()->create(), 'filament'
));

it('can list categories', function () {
    $categories = Category::factory()->count(3)->create();

    livewire(ListCategories::class)
        ->assertCanSeeTableRecords($categories);
});

it('can create a category', function () {
    livewire(CreateCategory::class)
        ->fillForm(['name' => 'Yoga', 'personal_course' => true])
        ->call('create')
        ->assertHasNoFormErrors();

    $this->assertDatabaseHas('categories', ['name' => 'Yoga']);
});
```

### Pattern de test avec datasets

```php
it('validates place opening hours', function (Carbon $date, bool $expected) {
    $place = Place::factory()->create(['opening_hours' => [...]]);

    expect($place->isOpenAt($date))->toBe($expected);
})->with([
    'monday open'   => [fn () => Carbon::parse('2024-01-15 10:00'), true],
    'monday closed' => [fn () => Carbon::parse('2024-01-15 23:00'), false],
    'sunday'        => [fn () => Carbon::parse('2024-01-21 10:00'), false],
]);
```

### Configuration du TestCase

`tests/TestCase.php` configure automatiquement :
- `RefreshDatabase` : Reset BDD entre chaque test
- Spatie Permission cache cleared
- Notifications fakees
- Storage fake pour les uploads
- **Stripe stubbed** (StripeFakeHttpClient)
- **BioStar2 stubbed** (FakeBioStar2Service)
- Headers personnalises (build version)

### Quand ecrire quel type de test

| Nouveau code | Type de test | Repertoire |
|-------------|-------------|------------|
| Controleur API | Feature | `tests/Feature/Controllers/` |
| Action metier | Feature | `tests/Feature/Actions/` |
| Observer | Feature | `tests/Feature/Observers/` |
| Listener | Feature | `tests/Feature/Listeners/` |
| Middleware | Feature | `tests/Feature/Middleware/` |
| Resource Filament | Feature | `tests/Feature/Filament/` |
| Enum / Trait | Unit | `tests/Unit/Enums/` ou `tests/Unit/Traits/` |
| Scope / Cast | Unit | `tests/Unit/Models/` |
| Contrainte Orderable | Unit + Feature | `tests/Unit/OrderableConstraints/` + `tests/Feature/` |
| Service externe | Feature | `tests/Feature/Services/` (avec stub) |

### Assertions courantes

```php
// HTTP
->assertOk()                    // 200
->assertCreated()               // 201
->assertNoContent()             // 204
->assertUnprocessable()         // 422
->assertForbidden()             // 403
->assertNotFound()              // 404

// JSON
->assertJsonCount(3, 'data')
->assertJsonStructure(['token'])
->assertJsonFragment(['code' => 'BAD_LOGIN'])
->assertJsonPath('data.0.name', 'John')
->assertJsonMissing(['id' => 42])

// Base de donnees
$this->assertDatabaseHas('bookings', ['attendee_id' => $user->id]);
$this->assertDatabaseMissing('users', ['email' => 'deleted@example.com']);
$this->assertDatabaseCount('bookings', 1);
$this->assertSoftDeleted('messages', ['id' => $msg->id]);

// Exceptions
expect(fn () => ...)->toThrow(BusyAttendeeException::class);

// Events
Event::fake([OrderPaid::class]);
// ... action
Event::assertDispatched(OrderPaid::class);
```

### Commandes de tests

```bash
# Tous les tests
composer test

# Tests specifiques
./vendor/bin/pest --filter="can list user bookings"

# Tests par dossier
./vendor/bin/pest tests/Feature/Controllers/

# Tests avec couverture
./vendor/bin/pest --coverage

# Tests en parallele
./vendor/bin/pest --parallel
```

---

## 6. Annotations et PHPDoc

### Quand utiliser les annotations

PHPStan niveau 7 necessite des annotations dans certains cas :

```php
/**
 * @param  Collection<int, Booking>  $bookings
 * @return array<string, mixed>
 */
public function processBookings(Collection $bookings): array

/**
 * @property-read Coach|null $coach
 * @property-read Collection<int, Booking> $bookings
 */
final class User extends Authenticatable

/** @var array<string, class-string<Model>> */
protected array $morphMap = [...];

/**
 * @throws BusyAttendeeException
 * @throws PlaceClosedException
 */
public function __invoke(User $attendee, array $data): Booking
```

**Regles :**
- **`@OA\`** sur les controleurs API, modeles exposes, et Resources (Swagger)
- **`@property`** pour les casts Money et proprietes virtuelles
- **`@property-read`** pour les relations Eloquent
- **`@param`** pour les collections generiques (`Collection<int, Model>`)
- **`@return`** pour les retours `array` avec structure (`array<string, mixed>`)
- **`@throws`** pour chaque exception metier levee par une Action/Service
- **`@mixin`** sur les Resources pour indiquer le modele source
- Ne PAS sur-documenter les methodes dont le type est evident par la signature

### Annotations Spatie (Activity Log)

```php
public function getActivitylogOptions(): LogOptions
{
    return LogOptions::defaults()
        ->logOnly(['name', 'email', 'phone'])
        ->logOnlyDirty()
        ->dontSubmitEmptyLogs();
}
```

---

## 7. Gestion des Erreurs

### Exceptions Metier (ApiException)

Les exceptions metier implementent l'interface `Responsable` pour etre automatiquement converties en JSON :

```php
// Convention : nommer d'apres le PROBLEME, pas la solution
throw new BusyAttendeeException();        // BIEN
throw new PlaceClosedException();          // BIEN
throw new BookingFailedException();        // MAL (trop generique)
```

**Exception silencieuse** : Les exceptions avec `silenced = true` ne sont pas reportees a Sentry.

### Reponses d'erreur API

```json
{
    "message": "The attendee already has a booking at this time.",
    "code": "BUSY_ATTENDEE"
}
```

HTTP codes :
- `200/201` : Succes
- `400` : Erreur de validation metier
- `401` : Non authentifie
- `403` : Non autorise
- `404` : Ressource introuvable
- `409` : Conflit
- `422` : Erreur de validation de donnees
- `500` : Erreur serveur

---

## 8. Gestion des Fuseaux Horaires

### Principe

- **BDD stocke tout en UTC** via le trait `ConvertDateTimeToUTC` de BaseModel
- Chaque **Place** a un champ `timezone` (ex: `Europe/Paris`, `Asia/Dubai`)
- Les **queries temporelles** (today, past, future) utilisent le timezone du lieu
- Les **casts** `immutable_datetime` gerent la conversion automatique

### Pattern dans les Builders

```php
// BookingBuilder.php
public function today(string $timezone = 'Europe/Paris'): self
{
    $now = CarbonImmutable::now($timezone);

    return $this->where('start_at', '>=', $now->startOfDay())
                ->where('start_at', '<=', $now->endOfDay());
}
```

### Regles

- **TOUJOURS** utiliser `CarbonImmutable` (pas Carbon mutable)
- **TOUJOURS** passer le timezone du lieu pour les requetes temporelles
- **JAMAIS** stocker des dates en timezone locale dans la BDD
- Le cast `immutable_datetime` est prefere a `datetime`

---

## 9. Profilage et Performance

### Middleware WebserviceProfiler

Le middleware `WebserviceProfiler` (`app/Http/Middleware/WebserviceProfiler.php`) ajoute automatiquement des headers de performance :

```
X-Resp-Time-Ms          : Temps de reponse (ms)
X-Resp-Uncompressed-Bytes : Taille reponse (bytes)
X-Resp-Items            : Nombre d'items retournes
```

Et log dans le canal `profiler` :
- Temps de reponse, taille, nombre d'items
- Nombre et temps des requetes SQL
- Usage memoire
- IP, User Agent, contexte Place

### Rate Limiting

Defini dans `RouteServiceProvider` :

| Route | Limite | Par |
|-------|--------|-----|
| API | 60 req/min | Utilisateur (ou IP si non authentifie) |
| Chatbot | 1000 req/min | Cle API |
| Chat | 200 req/min | Utilisateur |

---

## 10. Patterns Avances

### BookingFactories (Factory Pattern)

Les `BookingFactories` (`app/BookingFactories/`) creent des bookings selon le type d'orderable :

```php
interface BookingFactoryInterface
{
    public function create(OrderItem $item, Order $order): Booking;
}

// Implementations : ActivityBookingFactory, GroupBookingFactory,
//                   ServiceBookingFactory, PersonalCourseBookingFactory, etc.
```

Appelees par le listener `CreateBookingWhenOrderConfirmed` lors du paiement.

### OrderableConstraints (Pipeline Pattern)

Les contraintes d'orderable (`app/OrderableConstraints/`) valident les items de commande en pipeline :

```php
// Sur le modele Orderable
public function orderableConstraints(): array
{
    return [
        new RequiresMetadata(['start_at', 'end_at']),
        new MustBeTodayOrFuture(),
        new ActivityMustBeAvailable(),
        new TransformPublicIdToId(),
    ];
}

// Chaque contrainte a deux methodes
public function check(OrderItem $item): bool;   // Valide
public function mutate(OrderItem $item): void;  // Transforme
```

### DTOs (Spatie Data)

Les Data Transfer Objects (`app/Data/`) utilisent Spatie LaravelData :

```php
final class OrderItemData extends Data
{
    public function __construct(
        public string $orderable_type,
        public string $orderable_id,
        public ?array $metadata = null,
        public bool $is_gift = false,
        public bool $is_guest_pass = false,
        public ?string $guest_id = null,
        public ?string $kid_id = null,
    ) {}
}
```

### Media (Spatie MediaLibrary)

```php
// Upload avatar
$user->addMediaFromRequest('avatar')
    ->toMediaCollection('avatar');

// Collections et conversions (dans le modele)
public function registerMediaCollections(): void
{
    $this->addMediaCollection('avatar')->singleFile();
    $this->addMediaCollection('pictures');
}

public function registerMediaConversions(Media $media = null): void
{
    $this->addMediaConversion('large')->width(800)->format('png');
    $this->addMediaConversion('medium')->width(400)->format('png');
    $this->addMediaConversion('small')->width(200)->format('png');
}
```

---

## 11. Regles PHPStan

### Niveau 7 (applique)

- Types de retour sur toutes les methodes
- Types des parametres
- Verification des acces aux proprietes
- Verification des appels de methodes
- Verification des types generiques
- Pas de `mixed` implicite

### Erreurs Ignorees (Baseline)

Dans `phpstan-baseline.neon` (35+ erreurs connues). Regenerer avec :
```bash
./vendor/bin/phpstan analyse --generate-baseline
```

---

## 12. Formatage (Pint)

- **Preset Laravel** : Standard officiel
- **strict_types** : Force dans chaque fichier
- **Arguments multi-lignes** : Tous sur des lignes separees
- **Ligne vide avant** : `break`, `continue`, `declare`, `return`, `throw`, `try`

```bash
# Verifier sans modifier
composer lint -- --test

# Corriger automatiquement
composer lint
```

---

## 13. Checklist Pre-Commit

Avant chaque commit, verifier :

1. **`./gitCheck.sh` passe** (lint + types)
2. **Tests passent** pour le code modifie (`composer test`)
3. **Pas de `dd()`, `dump()`, `var_dump()`** dans le code
4. **strict_types** declare dans chaque fichier PHP
5. **public_id** utilise dans l'API (jamais id auto-increment)
6. **Annotations Swagger `@OA\`** sur les nouveaux controleurs/modeles/resources
7. **Tests ecrits** pour les nouvelles fonctionnalites
8. **Traductions** : champs traduisibles declares si le modele a des champs texte user-facing
9. **Relations typees** avec type de retour
10. **Fillable explicite** (pas de `$guarded = []`)
11. **PHPDoc** : `@throws`, `@param` generiques, `@property-read` pour les relations
12. **Exceptions metier** nommees d'apres le probleme
13. **Migrations reversibles** (methode `down()`)
14. **Pas de credentials** en dur dans le code
15. **Swagger regenere** si nouveaux endpoints (`php artisan l5-swagger:generate`)
16. **Cache traductions videe** si traductions modifiees

---

## 14. Resume des Conventions Cles

| Regle | Description |
|-------|-------------|
| `declare(strict_types=1)` | Obligatoire dans chaque fichier PHP |
| Classes `final` | Par defaut sauf besoin d'heritage |
| Un controleur = une action | Methode `__invoke` unique |
| `public_id` en API | Jamais exposer l'ID auto-increment |
| **Swagger `@OA\` obligatoire** | Sur chaque controleur API, modele expose, Resource |
| **Tests obligatoires** | Pest, pour chaque nouvelle fonctionnalite |
| **Traductions** | `HasDatabaseTranslations` pour les champs texte user-facing |
| Money pattern | `{field}_amount` + `{field}_currency` |
| Timezone | UTC en BDD, timezone du Place pour les queries |
| Soft deletes | Par defaut sur les modeles metier |
| BaseModel | Heritage pour la conversion UTC |
| Enums PHP 8.1+ | Backed par string, valeurs snake_case |
| PHPStan niveau 7 | Analyse statique stricte |
| PHPDoc | `@throws`, `@param` generiques, `@property-read` relations |
| Pagination | `page` + `size`, format `data/totalItems/totalPages` |
| gitCheck.sh | Obligatoire avant commit |
