M1: CRUD-Slice 'Mahlzeit' Schritt 1 – Logik + Daten + Tests
Rich-Model Mahlzeit (MahlzeitStatus, Wochentag) mit Invarianten und Zustandsübergängen; DTOs + MahlzeitFactory (reine Mapping-Funktionen); Manager (create hängt an Ende an: maxSortOrder+1, find → 404) und Create/Update/Delete-UseCases mit Transaktion + Events nach Commit. Data-Layer: MahlzeitEntity (Tabelle essens_mahlzeiten, wochentage als CSV), Mapper (CSV/Enum/null), Provider (find/list/maxSortOrder), Processor (create/save/delete). DI-Bindings in services.yaml. ClockInterface (Logic) + SystemClock (Data). Tests: Model/Factory/UseCase-Unit + Provider/Processor/Mapper-Integration (In-Mem-SQLite). composer test grün (53 Tests, 161 Assertions).
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Logic\Essen\Mapping;
|
||||
|
||||
use App\Logic\Common\Exception\BusinessRuleViolationException;
|
||||
use App\Logic\Essen\Dto\MahlzeitCreateRequest;
|
||||
use App\Logic\Essen\Dto\MahlzeitUpdateRequest;
|
||||
use App\Logic\Essen\Model\Mahlzeit;
|
||||
|
||||
/**
|
||||
* Reine Funktionen: Request-DTO → Modell. Keine Framework-/DB-Abhängigkeit.
|
||||
*
|
||||
* Fachregeln (Pflichtfelder) werden hier als Business-Validierung geprüft.
|
||||
* Zeitstempel (`erstelltAm`/`aktualisiertAm`) liefert der UseCase über `ClockInterface`.
|
||||
*/
|
||||
final class MahlzeitFactory
|
||||
{
|
||||
/**
|
||||
* Erstellt ein neues, geplantes Modell aus einem Anlegen-DTO.
|
||||
*
|
||||
* `sortOrder` wird bewusst auf 0 gesetzt – der Manager hängt den Eintrag
|
||||
* anschließend am Ende an (max+1).
|
||||
*/
|
||||
public function createFromCreateRequest(MahlzeitCreateRequest $req, \DateTimeImmutable $jetzt): Mahlzeit
|
||||
{
|
||||
$this->assertRequired($req->name, 'name');
|
||||
|
||||
return Mahlzeit::anlegen(
|
||||
name: $req->name,
|
||||
notiz: $req->notiz,
|
||||
wochentage: $req->wochentage,
|
||||
sortOrder: 0,
|
||||
jetzt: $jetzt,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wendet ein Teil-Update auf ein bestehendes Modell an. `null`-Felder bleiben unverändert.
|
||||
*/
|
||||
public function updateFromUpdateRequest(Mahlzeit $mahlzeit, MahlzeitUpdateRequest $req, \DateTimeImmutable $jetzt): Mahlzeit
|
||||
{
|
||||
if ($req->name !== null) {
|
||||
$this->assertRequired($req->name, 'name');
|
||||
}
|
||||
|
||||
$name = $req->name ?? $mahlzeit->name;
|
||||
$notiz = $req->notiz ?? $mahlzeit->notiz;
|
||||
$wochentage = $req->wochentage ?? $mahlzeit->wochentage;
|
||||
|
||||
return $mahlzeit->aktualisieren(
|
||||
name: $name,
|
||||
notiz: $notiz,
|
||||
wochentage: $wochentage,
|
||||
sortOrder: $mahlzeit->sortOrder,
|
||||
jetzt: $jetzt,
|
||||
);
|
||||
}
|
||||
|
||||
private function assertRequired(?string $value, string $feld): void
|
||||
{
|
||||
if ($value === null || \trim($value) === '') {
|
||||
throw new BusinessRuleViolationException(\sprintf('%s ist ein Pflichtfeld.', $feld));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user