0Pricing
PHP Academy · Lektion

Von Layered Architecture zur Clean Architecture

Verstehen Sie, warum Abhängigkeiten nach innen zeigen sollten.

Von Layered Architecture zur Clean Architecture ist eine kostenlose PHP Academy-Lektion auf CoddyKit. Dies ist Lektion 1 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.

Warum Clean Architecture

Sie kennen bereits den klassischen dreischichtigen PHP-Stack: Controller → Service → Repository → Database. Er funktioniert, aber die Geschäftslogik wird an Eloquent, Doctrine, die HTTP-Anfrage und den Lebenszyklus des Frameworks gekoppelt. Clean Architecture kehrt die Abhängigkeitsrichtung um, sodass Ihre Domäne nichts über die Infrastruktur weiß. Das Ergebnis: testbare Use Cases, austauschbare Adapter und eine Codebasis, die Framework-Upgrades übersteht.

Die Dependency Rule

Die einzige Regel der Clean Architecture lautet: Quelltextabhängigkeiten zeigen nur nach innen. Innere Kreise (Entities, Use Cases) dürfen niemals auf äußere Kreise (Controller, ORMs, Frameworks) verweisen. Zur Laufzeit kann der Kontrollfluss über Interfaces nach außen verlaufen, aber zur Kompilier-/Importzeit importiert nichts Inneres etwas Äußeres.

  • Entities: unternehmensweite Regeln
  • Use Cases: Anwendungsregeln
  • Adapter: Controller, Presenter, Gateways
  • Frameworks & Drivers: Datenbank, HTTP, Web

Ein gekoppeltes Schichtenbeispiel

Hier sehen Sie die Art von Service, die in den meisten PHP-Anwendungen ausgeliefert wird. Beachten Sie, wie die Domänenlogik mit Eloquent und der HTTP-Antwort verknüpft ist. Sie können die Rabattregel nicht als Unit-Test prüfen, ohne eine Datenbank und ein Framework zu benötigen.

<?php
class OrderService
{
    public function place(Request $request)
    {
        $user = User::find($request->user_id); // Eloquent
        $total = 0;
        foreach ($request->items as $i) {
            $total += Product::find($i['id'])->price * $i['qty'];
        }
        if ($user->is_vip) {
            $total *= 0.9; // business rule trapped in infra code
        }
        Order::create(['user_id' => $user->id, 'total' => $total]);
        return response()->json(['total' => $total]);
    }
}

Entities: Framework-freie Domäne

Eine Entity bildet unternehmensweite Regeln ab und hängt von nichts ab. Reines PHP, keine Annotations und keine Basisklasse des ORMs. Sie lässt sich vollständig in einem Test konstruieren.

<?php
final class Money
{
    public function __construct(public readonly int $cents) {
        if ($cents < 0) throw new InvalidArgumentException('negative money');
    }
    public function multiply(float $factor): self {
        return new self((int) round($this->cents * $factor));
    }
}

final class Order
{
    /** @param array<int,int> $lineCents */
    public function __construct(private array $lineCents, private bool $vip) {}
    public function total(): Money {
        $sum = array_sum($this->lineCents);
        $money = new Money($sum);
        return $this->vip ? $money->multiply(0.9) : $money;
    }
}

echo (new Order([1000, 2000], true))->total()->cents, PHP_EOL; // 2700

Use Cases besitzen den Ablauf

Ein Use Case (Interactor) orchestriert Entities und kommuniziert mit der Außenwelt ausschließlich über Interfaces (Ports). Er empfängt ein Request-DTO und gibt ein Response-DTO zurück – niemals ein HTTP-Objekt.

<?php
interface OrderRepository {
    public function save(Order $order): void;
}

final class PlaceOrder
{
    public function __construct(private OrderRepository $orders) {}

    public function execute(array $lineCents, bool $vip): int {
        $order = new Order($lineCents, $vip);
        $this->orders->save($order);
        return $order->total()->cents;
    }
}

Die Grenze ist ein Interface

Der Use Case deklariert das benötigte Interface OrderRepository. Das Interface befindet sich im inneren Kreis; die konkrete Eloquent-/Doctrine-Implementierung liegt außerhalb und hängt nach innen ab. Das ist das Dependency Inversion Principle, angewendet an einer architektonischen Grenze.

Richtung der Quelltextabhängigkeit: EloquentOrderRepository → OrderRepository (Interface), niemals umgekehrt.

<?php
// Lives in infrastructure layer, points INWARD to the domain interface
final class EloquentOrderRepository implements OrderRepository
{
    public function save(Order $order): void {
        OrderModel::create(['total' => $order->total()->cents]);
    }
}

Testen ohne Infrastruktur

Da der Use Case von einem Interface abhängt, injizieren Tests ein Fake. Keine Datenbank, kein Framework-Start – blitzschnelle Unit-Tests im Mikrosekundenbereich, die reines fachliches Verhalten prüfen.

<?php
final class InMemoryOrders implements OrderRepository {
    public array $saved = [];
    public function save(Order $o): void { $this->saved[] = $o; }
}

$repo = new InMemoryOrders();
$useCase = new PlaceOrder($repo);
$total = $useCase->execute([1000, 2000], true);

assert($total === 2700);
assert(count($repo->saved) === 1);
echo "PASS total=$total saved=" . count($repo->saved) . PHP_EOL;

Controller werden zu schlanken Adaptern

Der Controller ist nun ein Adapter: Er übersetzt HTTP in einen Aufruf des Use Cases und das Ergebnis wieder in HTTP. Er enthält keine Geschäftsregeln. Wenn Sie REST durch CLI oder einen Queue-Worker ersetzen, bleibt der Use Case unverändert.

<?php
final class OrderController
{
    public function __construct(private PlaceOrder $placeOrder) {}

    public function store(Request $request): JsonResponse {
        $total = $this->placeOrder->execute(
            lineCents: $request->input('lineCents'),
            vip: (bool) $request->input('vip'),
        );
        return new JsonResponse(['total' => $total], 201);
    }
}

Screaming Architecture

Die Ordnerstruktur sollte die Domäne hervorheben, nicht das Framework. Vermeiden Sie Controllers/ und Models/ auf der obersten Ebene. Organisieren Sie nach fachlichen Fähigkeiten, damit neue Teammitglieder sofort erkennen, was die Anwendung tut.

  • src/Ordering/Domain/ – Entities, Value Objects
  • src/Ordering/Application/ – Use Cases, Port-Interfaces
  • src/Ordering/Infrastructure/ – Eloquent-Repositories, HTTP-Controller

Jeder Bounded Context ist ein Ordner auf oberster Ebene; das Framework befindet sich an den Rändern.

Die Dependency Rule durchsetzen

Ohne geeignete Werkzeuge lässt die Disziplin nach. Verwenden Sie deptrac oder phparkitect in der CI, damit der Build fehlschlägt, wenn Domain Infrastructure importiert. So wird die Regel zu einer Garantie zur Compile-Zeit statt zu einer bloßen Hoffnung im Code-Review.

# deptrac.yaml
deptrac:
  layers:
    - name: Domain
      collectors: [{ type: directory, value: src/.*/Domain/.* }]
    - name: Application
      collectors: [{ type: directory, value: src/.*/Application/.* }]
    - name: Infrastructure
      collectors: [{ type: directory, value: src/.*/Infrastructure/.* }]
  ruleset:
    Domain: []                       # Domain may depend on nothing
    Application: [Domain]
    Infrastructure: [Application, Domain]

Grenzen mit DTOs überschreiten

Damit Entities nicht nach außen gelangen, werden Daten, die eine Grenze überschreiten, als einfaches DTO übertragen, nicht als Entity oder ORM-Modell. Der Use Case gibt eine flache Struktur zurück, die der Adapter serialisieren kann. So verlässt das Domänenobjekt niemals den Kern, und die äußere Schicht erhält keinen Zugriff auf den internen Zustand.

<?php
final class OrderSummary // boundary DTO, no behavior, no domain types
{
    public function __construct(
        public readonly string $orderId,
        public readonly int $totalCents,
    ) {}
}

final class PlaceOrderV2 {
    public function __construct(private OrderRepository $orders) {}
    public function execute(array $lineCents, bool $vip): OrderSummary {
        $order = new Order($lineCents, $vip);
        $this->orders->save($order);
        return new OrderSummary('ord_1', $order->total()->cents);
    }
}

Kurztest

Welche Abhängigkeitsrichtung ist nach der Dependency Rule zulässig?

Zusammenfassung

Sie sind von einem gekoppelten Schichten-Stack zur Clean Architecture übergegangen:

  • Die Dependency Rule: Quelltextabhängigkeiten zeigen nur nach innen.
  • Entities enthalten unternehmensweite Regeln in framework-freiem PHP.
  • Use Cases orchestrieren über Port-Interfaces und geben DTOs statt HTTP-Objekten zurück.
  • Controller und ORM-Repositories sind äußere Adapter, die nach innen abhängen (DIP).
  • Die Struktur sollte die Domäne hervorheben, und Werkzeuge wie deptrac setzen die Regel in der CI durch.

Häufig gestellte Fragen

Ist die Lektion „Von Layered Architecture zur Clean Architecture“ kostenlos?

Ja — der vollständige Text von „Von Layered Architecture zur Clean Architecture“ 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 „Von Layered Architecture zur Clean Architecture“?

Verstehen Sie, warum Abhängigkeiten nach innen zeigen sollten. 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 1 von 4.

Wie lange dauert die Lektion „Von Layered Architecture zur Clean Architecture“?

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