Michael Korte – Senior Full-Stack Developer Freelancer aus Dortmund

Ziel dieses Teils

In diesem Teil entsteht erstmals etwas, das sich wie eine „Seite“ anfühlt.

Technisch tun wir aber nur eines: Wir treffen eine eindeutige Entscheidung anhand der URL.

  • Routing explizit registrieren
  • erste Route definieren
  • Controller als Ziel
  • Error-Controller als regulären Standardweg

5.1 Routing ist Entscheidung – nicht Darstellung

Der Router beantwortet exakt eine Frage:

Was wird entschieden?

Welcher Controller ist zuständig?
Nicht: welches Template.
Nicht: welches HTML.
Nicht: wie die Seite aussieht.

Alles andere ist explizit nicht Aufgabe des Routers.

5.2 Router registrieren (bewusst im Bootstrap)

Der Router wird nicht „irgendwo“ erstellt, sondern explizit während der Bootstrap-Phase.

<?php
// Core/Bootstrap.php

use Core\Router;
use App\Controller\HomeController;
use App\Controller\ErrorController;

private static function register(App $app): void
{
    $router = new Router();

    $router->get('/', [HomeController::class, 'index']);

    // Kein Match ist kein Sonderfall
    $router->fallback([ErrorController::class, 'error404']);

    $app->setRouter($router);
}

Determinismus an dieser Stelle

Jede Anfrage endet immer in genau einem Controller. Es gibt keinen „leeren“ Pfad.

Hinweis zu Teil 5: Controller-Referenzen im Bootstrap

Aufmerksamen Lesern fällt an dieser Stelle etwas auf: Der Bootstrap kennt konkrete Controller-Klassen.

Das wirkt wie ein Bruch – und das ist es auch. Allerdings bewusst und ausschließlich für v0.1.

Warum das irritieren darf

  • Der Bootstrap sollte langfristig keine Domain kennen
  • Controller gehören fachlich zu Capabilities / Components
  • Routing sollte deklarativ, nicht hart verdrahtet sein

In einer idealen Zielarchitektur würde der Bootstrap keinen einzelnen Controller referenzieren. Er würde lediglich Mechanismen bereitstellen, über die Capabilities ihre Routen selbst registrieren.

In v0.1 existieren diese Mechanismen jedoch noch nicht:

  • keine Component-Discovery
  • keine Route-Provider
  • keine deklarative Capability-Registrierung

Statt impliziter Magie zeigt das Tutorial daher einen ehrlichen, sichtbaren Zwischenschritt: Der Bootstrap bindet die Controller explizit.

Wichtig für das Verständnis

Dieses Vorgehen ist kein Pattern und kein Zielzustand, sondern ein temporärer Kompromiss für v0.1.

In späteren Versionen (v0.2+) wird der Bootstrap keine Controller mehr kennen, und Routing erfolgt ausschließlich über registrierte Capabilities.

Für v0.1 gilt bewusst: Lieber ein erklärbarer Bruch als eine unsichtbare Abkürzung.

5.3 Der erste Controller

Controller treffen Entscheidungen. Sie erzeugen noch kein HTML und rufen keine Templates auf.

<?php
namespace App\Controller;

use Core\PageContext;

final class HomeController
{
    public function index(): PageContext
    {
        $page = new PageContext();

        $page->withData([
            'message' => 'Hallo Welt'
        ]);

        return $page;
    }
}

Bewusster Bruch mit klassischem MVC

Der Controller gibt keinen String, kein View-Objekt und kein Response-Objekt zurück, sondern reinen Seitenzustand.

5.4 ErrorController ist kein Sonderfall

Fehlerseiten folgen exakt denselben Regeln wie normale Seiten.

<?php
namespace App\Controller;

use Core\PageContext;

final class ErrorController
{
    public function error404(): PageContext
    {
        $page = new PageContext();

        $page
            ->withStatus(404)
            ->withData([
                'title' => 'Seite nicht gefunden'
            ]);

        return $page;
    }
}

Der Router entscheidet, dass kein Match existiert.
Der Controller entscheidet, welcher Zustand daraus entsteht.

5.5 Warum hier bewusst nichts gerendert wird

An diesem Punkt existiert:

  • kein Template
  • kein HTML
  • keine Assets

Das ist kein Mangel, sondern der Kern der Architektur.

Trennung greifbar

Entscheidung ≠ Darstellung
Routing ≠ Rendering
Controller ≠ Output

Zwischenstand

Der aktuelle Ablauf ist jetzt vollständig – aber noch ohne Ausgabe:

  • Request
  • Router
  • Controller
  • PageContext

HTML existiert noch nicht. Und genau deshalb ist es später eindeutig erklärbar.

Übergang zu Teil 6

In Teil 6 kommt der entscheidende Schritt:

Rendering – und warum nur der Renderer HTML erzeugt und alle anderen Schichten es nicht dürfen.

Projekt & Quellcode

Der Bootstrap und der Einstiegspunkt sind im Repository vollständig nachvollziehbar umgesetzt: