# Backend Laravel — Conventions

## Principe fondamental : Controllers fins

```
Request → Controller → Service → Model/BDD → Response
```

Le Controller :
1. Valide la requête (`FormRequest`)
2. Appelle le Service
3. Retourne une réponse Inertia ou JSON

**Jamais** de logique métier dans le Controller.

---

## Structure d'un Controller Admin

```php
// app/Http/Controllers/Admin/PageController.php

class PageController extends Controller
{
    public function __construct(private PageService $pageService) {}

    public function index(): Response
    {
        return Inertia::render('Admin/PageList', [
            'pages' => $this->pageService->paginate(),
        ]);
    }

    public function store(StorePageRequest $request): RedirectResponse
    {
        $page = $this->pageService->create($request->validated());
        return redirect()->route('admin.pages.edit', $page)->with('success', 'Page créée.');
    }

    public function edit(Page $page): Response
    {
        return Inertia::render('Admin/PageBuilder', [
            'page'      => $page->load(['blocks', 'template']),
            'templates' => Template::all(),
        ]);
    }

    public function update(UpdatePageRequest $request, Page $page): RedirectResponse
    {
        $this->pageService->update($page, $request->validated());
        return back()->with('success', 'Page mise à jour.');
    }

    public function destroy(Page $page): RedirectResponse
    {
        $this->pageService->delete($page);
        return redirect()->route('admin.pages.index')->with('success', 'Page supprimée.');
    }
}
```

---

## Structure d'un Service

```php
// app/Services/PageService.php

class PageService
{
    public function paginate(int $perPage = 20): LengthAwarePaginator
    {
        return Page::with('template')->latest()->paginate($perPage);
    }

    public function create(array $data): Page
    {
        return DB::transaction(function () use ($data) {
            return Page::create($data);
        });
    }

    public function update(Page $page, array $data): Page
    {
        return DB::transaction(function () use ($page, $data) {
            $page->update($data);
            return $page->fresh();
        });
    }

    public function delete(Page $page): void
    {
        DB::transaction(function () use ($page) {
            $page->delete();  // blocs supprimés par cascadeOnDelete
        });
    }
}
```

---

## Service : gestion des blocs (Page Builder)

```php
// app/Services/BlockService.php

class BlockService
{
    // Remplace TOUS les blocs d'une page en une seule opération
    // Appelé lors de la sauvegarde du Page Builder
    public function sync(Page $page, array $blocksData): void
    {
        DB::transaction(function () use ($page, $blocksData) {
            $page->blocks()->delete();

            foreach ($blocksData as $index => $blockData) {
                $this->validateBlock($blockData);

                $page->blocks()->create([
                    'zone'  => $blockData['zone'],
                    'type'  => $blockData['type'],
                    'order' => $index,
                    'props' => $blockData['props'],
                ]);
            }
        });
    }

    private function validateBlock(array $block): void
    {
        $allowedTypes = ['text', 'hero', 'image', 'gallery', 'property-list', 'booking-form', 'map'];

        if (!in_array($block['type'], $allowedTypes)) {
            throw new \InvalidArgumentException("Type de bloc invalide : {$block['type']}");
        }

        if (empty($block['zone'])) {
            throw new \InvalidArgumentException("Zone manquante pour le bloc {$block['type']}");
        }
    }
}
```

---

## Routes

```php
// routes/admin.php
Route::prefix('admin')
    ->name('admin.')
    ->middleware(['auth', 'verified', 'admin'])
    ->group(function () {

        Route::get('/', [DashboardController::class, 'index'])->name('dashboard');

        // Pages
        Route::resource('pages', PageController::class);
        Route::post('pages/{page}/blocks', [BlockController::class, 'sync'])->name('pages.blocks.sync');

        // Templates
        Route::get('templates', [TemplateController::class, 'index'])->name('templates.index');

        // Menus
        Route::resource('menus', MenuController::class);
        Route::post('menus/{menu}/items/reorder', [MenuController::class, 'reorder'])->name('menus.reorder');

        // Médias
        Route::get('media',          [MediaController::class, 'index'])->name('media.index');
        Route::post('media/upload',  [MediaController::class, 'upload'])->name('media.upload');
        Route::delete('media/{media}', [MediaController::class, 'destroy'])->name('media.destroy');

        // Réglages
        Route::get('settings',  [SettingsController::class, 'index'])->name('settings.index');
        Route::put('settings',  [SettingsController::class, 'update'])->name('settings.update');
    });

// routes/web.php
Route::get('/', [Front\HomeController::class, 'index']);
Route::get('/{slug}', [Front\PageController::class, 'show'])->where('slug', '[a-z0-9\-]+');
```

---

## FormRequests

Créer un `FormRequest` pour chaque action store/update :

```php
// app/Http/Requests/Admin/StorePageRequest.php
class StorePageRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'title'       => ['required', 'string', 'max:255'],
            'slug'        => ['required', 'string', 'unique:pages,slug', 'max:255'],
            'template_id' => ['required', 'exists:templates,id'],
            'status'      => ['required', 'in:draft,published'],
            'meta'        => ['nullable', 'array'],
        ];
    }
}
```

---

## Cache — règles

| Donnée | Clé cache | TTL | Invalidée par |
|---|---|---|---|
| Menus | `menus.all` | 3600s | `MenuService::clearCache()` |
| Settings partagés | `settings.shared` | 3600s | `SettingsService::clearCache()` |
| Pages publiées | `page.{slug}` | 3600s | `PageService::clearCache($slug)` |

**Règle** : toujours invalider le cache dans le Service, jamais dans le Controller.
