0Pricing
PHP Academy · Lektion

Use Cases und Application Services

Drücken Sie Geschäftsaktionen als frameworkunabhängige Use Cases aus.

Use Cases und Application Services ist eine kostenlose PHP Academy-Lektion auf CoddyKit. Dies ist Lektion 3 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des PHP Academy-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der PHP Academy-Kurs umfasst insgesamt 4 Lektionen.

Was ein Use Case wirklich ist

Ein Use Case (auch Application Service oder Interactor genannt) bildet genau eine anwendungsspezifische Operation ab: Register User, Place Order, Cancel Subscription. Er koordiniert Entitäten und Ports, um eine einzelne Absicht umzusetzen. Entscheidend ist, dass er frei von Frameworks bleibt: kein Request, keine Response, keine globalen Hilfsfunktionen — nur einfaches PHP, das Sie von überall aus aufrufen können.

Command- und Result-DTOs

Ein Use Case nimmt ein unveränderliches Command-DTO als Eingabe entgegen und gibt ein Result-DTO zurück. DTOs sind einfache Datenträger — ohne Verhalten und ohne Validierungslogik, die über die Struktur hinausgeht. Readonly-Eigenschaften (ab PHP 8.1) machen sie manipulationssicher.

<?php
final class RegisterUserCommand
{
    public function __construct(
        public readonly string $email,
        public readonly string $plainPassword,
    ) {}
}

final class RegisterUserResult
{
    public function __construct(public readonly string $userId) {}
}

Der Rumpf des Application Service

Der Service übersetzt das Command in Domänenoperationen. Er übernimmt die Orchestrierung auf Anwendungsebene — Eindeutigkeitsprüfungen, Persistierung und die Rückgabe von Bezeichnern — und delegiert Regeln an die Entitäten.

<?php
final class RegisterUser
{
    public function __construct(
        private Users $users,
        private PasswordHasher $hasher,
    ) {}

    public function __invoke(RegisterUserCommand $c): RegisterUserResult {
        if ($this->users->existsByEmail($c->email)) {
            throw new EmailAlreadyRegistered($c->email);
        }
        $user = User::register(
            UserId::generate(),
            new Email($c->email),
            $this->hasher->hash($c->plainPassword),
        );
        $this->users->add($user);
        return new RegisterUserResult((string) $user->id());
    }
}

Logik in Entitäten belassen

Hüten Sie sich vor dem anämischen Domänenmodell: Entitäten bestehen nur noch aus Gettern und Settern, während die gesamte Logik in Services steckt. Invarianten gehören auf die Entität. Der Use Case sollte wie ein kurzes Skript von Absichten lesbar sein, nicht wie eine Wand aus Geschäftsregeln.

<?php
final class User
{
    private function __construct(
        private UserId $id,
        private Email $email,
        private string $passwordHash,
        private bool $active = false,
    ) {}

    public static function register(UserId $id, Email $e, string $hash): self {
        return new self($id, $e, $hash); // invariants enforced here
    }
    public function activate(): void {
        if ($this->active) throw new AlreadyActive();
        $this->active = true;
    }
    public function id(): UserId { return $this->id; }
}

Transaktionsgrenzen

Ein Use Case ist die natürliche Transaktionsgrenze: Ein Use Case entspricht einer konsistenten Arbeitseinheit. Anstatt Services mit beginTransaction() zu durchsetzen, umschließen Sie sie mit einem Transaktionsdekorator, damit der Kern unabhängig von der Persistierung bleibt.

<?php
interface TransactionManager {
    public function transactional(callable $work): mixed;
}

final class TransactionalRegisterUser
{
    public function __construct(
        private RegisterUser $inner,
        private TransactionManager $tx,
    ) {}

    public function __invoke(RegisterUserCommand $c): RegisterUserResult {
        return $this->tx->transactional(fn() => ($this->inner)($c));
    }
}

Validierung: Wo sie hingehört

Teilen Sie die Validierung in zwei Ebenen auf:

  • Eingabevalidierung (Format, Pflichtfelder) erfolgt im eingehenden Adapter oder in einem dedizierten Validator, bevor der Use Case ausgeführt wird.
  • Domänenvalidierung (Invarianten, Geschäftsregeln) gehört in Wertobjekte und Entitäten und löst Domänenausnahmen aus.

Der Use Case setzt wohlgeformte Eingaben voraus und erzwingt deren Bedeutung.

<?php
final class Email
{
    public function __construct(public readonly string $value) {
        if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException("Invalid email: $value");
        }
    }
}

try { new Email('nope'); } catch (Throwable $e) { echo $e->getMessage(), PHP_EOL; }
echo (new Email('a@b.com'))->value, PHP_EOL;

Ausgabe ohne HTTP zurückgeben

Zwei Muster, um Daten zurückzugeben und dabei frei von Frameworks zu bleiben:

  • Ein Result-DTO zurückgeben (einfach, synchron).
  • Output-Port / Presenter — der Use Case übergibt das Ergebnis an eine injizierte Ausgabeschnittstelle, sodass der Adapter die Formatierung (JSON, HTML, CLI) festlegen kann. Dadurch bleibt sogar die Form der Antwort außerhalb des Kerns.
<?php
interface RegisterUserOutput {
    public function present(RegisterUserResult $r): void;
}

final class RegisterUserWithPresenter {
    public function __construct(private Users $users, private PasswordHasher $h) {}
    public function __invoke(RegisterUserCommand $c, RegisterUserOutput $out): void {
        $user = User::register(UserId::generate(), new Email($c->email), $this->h->hash($c->plainPassword));
        $this->users->add($user);
        $out->present(new RegisterUserResult((string) $user->id()));
    }
}

Domänenereignisse aus Use Cases

Use Cases zeichnen häufig Domänenereignisse auf, die von Entitäten ausgelöst werden, und verteilen sie anschließend nach dem Commit der Transaktion. Dadurch werden Seiteneffekte (Willkommens-E-Mail senden, Read Model aktualisieren) vom Kernablauf entkoppelt.

<?php
trait RecordsEvents {
    private array $events = [];
    protected function record(object $e): void { $this->events[] = $e; }
    public function releaseEvents(): array {
        $e = $this->events; $this->events = []; return $e;
    }
}

final class UserRegistered {
    public function __construct(public readonly string $userId) {}
}

// Use case calls $user->releaseEvents() and hands them to a dispatcher
echo 'event recorded pattern', PHP_EOL;

Eine Klasse pro Use Case

Bevorzugen Sie eine Klasse mit einer einzigen Aktion (eine öffentliche Methode, häufig __invoke) gegenüber einem überladenen Service mit zehn Methoden. Vorteile:

  • Klare Einzelverantwortung und eindeutige Benennung (CancelSubscription statt SubscriptionService::cancel).
  • Der Konstruktor injiziert nur, was diese Operation benötigt.
  • Einfaches Umschließen mit Dekoratoren (Transaktion, Logging, Autorisierung).

Verdrahtung im Composition Root

Der Use Case erzeugt seine Abhängigkeiten niemals selbst; das übernimmt der Composition Root. Hier sehen Sie eine manuelle Verdrahtung, die Sie in einer Definition des DI-Containers hinterlegen könnten.

<?php
$pdo      = new PDO('sqlite::memory:');
$users    = new PdoUsers($pdo);
$hasher   = new BcryptHasher();
$register = new RegisterUser($users, $hasher);

// Decorate with a transaction boundary
$register = new TransactionalRegisterUser($register, new PdoTransactionManager($pdo));

// Driving adapter calls it
$result = $register(new RegisterUserCommand('dev@coddykit.com', 's3cret!'));
echo $result->userId, PHP_EOL;

Querschnittliche Belange über Dekoratoren

Logging, Metriken und Autorisierung sind querschnittliche Belange — halten Sie sie aus dem Rumpf des Use Cases heraus. Umschließen Sie den Service mit Dekoratoren, die dieselbe Schnittstelle verwenden. So bleibt der Kern auf den Ablauf konzentriert, während sich Infrastrukturbelange darum herum zusammensetzen lassen.

<?php
interface RegisterUserHandler {
    public function __invoke(RegisterUserCommand $c): RegisterUserResult;
}

final class LoggingRegisterUser implements RegisterUserHandler {
    public function __construct(
        private RegisterUserHandler $inner,
        private LoggerInterface $log,
    ) {}
    public function __invoke(RegisterUserCommand $c): RegisterUserResult {
        $this->log->info('register.start', ['email' => $c->email]);
        $r = ($this->inner)($c);
        $this->log->info('register.ok', ['id' => $r->userId]);
        return $r;
    }
}

Kurzprüfung

Wo sollte die Regel „Eine E-Mail-Adresse muss eindeutig und gültig sein“ liegen?

Zusammenfassung

Framework-freie Use Cases schaffen eine klare Anwendungsschicht:

  • Pro Operation gibt es eine Klasse mit einer einzigen Aktion, die ein Command-DTO entgegennimmt und ein Result-DTO zurückgibt (oder das Ergebnis an einen Output-Port übergibt).
  • Entitäten und Wertobjekte besitzen die Invarianten; der Service orchestriert lediglich — vermeiden Sie anämische Modelle.
  • Use Cases sind Transaktionsgrenzen, die durch Dekoratoren anstelle von inline eingefügtem beginTransaction umschlossen werden.
  • Trennen Sie Eingabevalidierung (Adapter/Wertobjekt) von Domänenvalidierung (Entitäten).
  • Domänenereignisse entkoppeln Seiteneffekte; der Composition Root verbindet die Abhängigkeiten.

Häufig gestellte Fragen

Ist die Lektion „Use Cases und Application Services“ kostenlos?

Ja — der vollständige Text von „Use Cases und Application Services“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des PHP Academy-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der PHP Academy-Kurs umfasst insgesamt 4 Lektionen.

Was lerne ich in „Use Cases und Application Services“?

Drücken Sie Geschäftsaktionen als frameworkunabhängige Use Cases aus. Du übst PHP Academy mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.

Brauche ich Erfahrung, um PHP Academy zu starten?

Keine Vorkenntnisse erforderlich. PHP Academy auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 3 von 4.

Wie lange dauert die Lektion „Use Cases und Application Services“?

Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.

Kann ich in dieser PHP Academy-Lektion Code schreiben und ausführen?

Ja. Jede PHP Academy-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.

Alle Lektionen in diesem Kurs

  1. Von Layered Architecture zur Clean Architecture
  2. Ports und Adapter erklärt
  3. Use Cases und Application Services
  4. Dependency Inversion in der Praxis
← Zurück zu PHP Academy