M1: CRUD-Slice 'Mahlzeit' Schritt 2 – UI (Controller, Templates, Routen, Feature-Tests)

This commit is contained in:
2026-09-19 10:17:55 +02:00
parent 1589088379
commit 160b89f5a1
11 changed files with 536 additions and 9 deletions
+148
View File
@@ -0,0 +1,148 @@
<?php
declare(strict_types=1);
namespace App\UI\Http\Essen;
use App\Logic\Essen\Dto\MahlzeitCreateRequest;
use App\Logic\Essen\Dto\MahlzeitListRequest;
use App\Logic\Essen\Dto\MahlzeitUpdateRequest;
use App\Logic\Essen\Manager\MahlzeitManagerInterface;
use App\Logic\Essen\Model\Mahlzeit;
use App\Logic\Essen\Model\Wochentag;
use App\Logic\Essen\Query\MahlzeitListQuery;
use App\Logic\Essen\UseCase\MahlzeitCreateUseCase;
use App\Logic\Essen\UseCase\MahlzeitDeleteUseCase;
use App\Logic\Essen\UseCase\MahlzeitUpdateUseCase;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* CRUD-Slice "Mahlzeit" (M1) – Web-UI.
*
* Stil: klassisches POST/Redirect/Flash (htmx-Toggles kommen in M2).
*
* - Reads über `MahlzeitListQuery` / `MahlzeitManagerInterface::find`.
* - Writes über die UseCases (Create/Update/Delete).
* - Syntaktische Pflichtfeld-Prüfung (Name) passiert hier im UI-Layer
* (rein PHP, s. architektur.md §1.13); fachliche Invarianten bleiben
* im UseCase/Model und laufen zentral über den ExceptionSubscriber.
*/
final class MahlzeitController extends AbstractController
{
public function __construct(
private readonly MahlzeitCreateUseCase $create,
private readonly MahlzeitUpdateUseCase $update,
private readonly MahlzeitDeleteUseCase $delete,
private readonly MahlzeitListQuery $list,
private readonly MahlzeitManagerInterface $mahlzeiten,
) {}
/** GET /mahlzeiten — Planungsliste. */
public function index(): Response
{
return $this->render('mahlzeiten/index.html.twig', [
'mahlzeiten' => $this->list->execute(new MahlzeitListRequest())->items,
]);
}
/** GET /mahlzeiten/neu — Anlegen-Formular. */
public function neu(): Response
{
return $this->render('mahlzeiten/form.html.twig', $this->formKontext(null, null, [
'name' => '',
'notiz' => '',
'wochentage' => [],
]));
}
/** POST /mahlzeiten — Mahlzeit anlegen. */
public function speichern(Request $request): Response
{
[$name, $notiz, $wochentage] = $this->binde($request);
if ($name === '') {
return $this->render('mahlzeiten/form.html.twig', $this->formKontext(
null,
'Name ist ein Pflichtfeld.',
['name' => '', 'notiz' => $notiz, 'wochentage' => $wochentage],
));
}
$this->create->execute(new MahlzeitCreateRequest(name: $name, notiz: $notiz, wochentage: $wochentage));
$this->addFlash('notice', sprintf('Mahlzeit „%s“ angelegt.', $name));
return $this->redirectToRoute('mahlzeiten_index');
}
/** GET /mahlzeiten/{id}/bearbeiten — Bearbeiten-Formular. */
public function bearbeiten(int $id): Response
{
$mahlzeit = $this->mahlzeiten->find($id);
return $this->render('mahlzeiten/form.html.twig', $this->formKontext($mahlzeit, null, [
'name' => $mahlzeit->name,
'notiz' => $mahlzeit->notiz,
'wochentage' => $mahlzeit->wochentage,
]));
}
/** POST /mahlzeiten/{id} — Mahlzeit bearbeiten. */
public function aktualisieren(Request $request, int $id): Response
{
$mahlzeit = $this->mahlzeiten->find($id);
[$name, $notiz, $wochentage] = $this->binde($request);
if ($name === '') {
return $this->render('mahlzeiten/form.html.twig', $this->formKontext(
$mahlzeit,
'Name ist ein Pflichtfeld.',
['name' => '', 'notiz' => $notiz, 'wochentage' => $wochentage],
));
}
$this->update->execute($id, new MahlzeitUpdateRequest(name: $name, notiz: $notiz, wochentage: $wochentage));
$this->addFlash('notice', sprintf('Mahlzeit „%s“ aktualisiert.', $name));
return $this->redirectToRoute('mahlzeiten_index');
}
/** POST /mahlzeiten/{id}/loeschen — Mahlzeit hart löschen. */
public function loeschen(Request $request, int $id): Response
{
$mahlzeit = $this->mahlzeiten->find($id);
$this->delete->execute($id);
$this->addFlash('notice', sprintf('Mahlzeit „%s“ gelöscht.', $mahlzeit->name));
return $this->redirectToRoute('mahlzeiten_index');
}
/**
* Bindet die Formularfelder (Name/Notiz/Wochentage) aus dem Request.
*
* @return array{0: string, 1: string, 2: list<Wochentag>}
*/
private function binde(Request $request): array
{
$name = trim((string) $request->request->get('name', ''));
$notiz = (string) $request->request->get('notiz', '');
$wochentage = Wochentag::ausWerten((array) $request->request->all('wochentage'));
return [$name, $notiz, $wochentage];
}
/**
* @param array{name: string, notiz: string, wochentage: list<Wochentag>} $werte
* @return array<string, mixed>
*/
private function formKontext(?Mahlzeit $mahlzeit, ?string $fehler, array $werte): array
{
return [
'mahlzeit' => $mahlzeit,
'fehler' => $fehler,
'werte' => $werte,
'wochentage_choices' => Wochentag::alle(),
];
}
}