Das weiße Blatt

Technologie-Entscheidungen wenn man frei wählen kann – und warum jede Wahl auch eine Ablehnung ist

Wer lange mit gewachsenen Systemen gearbeitet hat, entwickelt eine bestimmte Art zu denken. Man sieht nicht nur, was ein Werkzeug kann – man sieht, was es einem aufzwingt. Welche Entscheidungen es vorwegnimmt. Welche Wege es versperrt, bevor man überhaupt angefangen hat.

Das weiße Blatt ist selten. In der Praxis arbeitet man fast immer im Bestand – mit Legacy-Code, mit bestehenden Abhängigkeiten, mit Entscheidungen, die jemand anderes vor Jahren getroffen hat. Das ist der Alltag, und diese Serie handelt davon.

Aber manchmal gibt es das weiße Blatt. Ein neues Projekt. Keine Altlasten. Die Frage lautet nicht „wie verbessern wir was da ist" – sondern „womit fangen wir an?"

Dieser Artikel ist die Antwort auf diese Frage. Nicht als Empfehlung, sondern als Reflexion über die Entscheidungen, die ich heute treffe – und warum.


Eine Haltung, keine Liste

Ein Tech-Stack ist keine neutrale Aufzählung von Werkzeugen. Er ist eine Haltung. Jede Entscheidung für eine Technologie ist gleichzeitig eine Entscheidung gegen etwas anderes – gegen eine bestimmte Art von Komplexität, gegen eine bestimmte Art von Abhängigkeit, gegen eine bestimmte Art von Magie.

Wer Laravel wählt, wählt Konvention und Geschwindigkeit – und nimmt dafür in Kauf, dass das Framework tief in die Anwendungslogik eingreift. Wer Eloquent wählt, bekommt eine elegante API – und bindet seine Domänenmodelle an die Datenbankstruktur. Wer React wählt, bekommt ein mächtiges UI-Modell – und einen Build-Prozess, eine Dependency-Kette und ein mentales Modell, das alle Teammitglieder teilen müssen.

Keine dieser Entscheidungen ist falsch. Sie sind Kompromisse. Die Frage ist, ob man sie bewusst trifft.


Das Architekturprinzip zuerst

Alles andere folgt aus einem einzigen Satz:

Der Domänenkern kennt kein Framework.

Keine Slim-Klassen im Core. Keine Doctrine-Annotationen in den Domänenmodellen. Keinen Mailer-Aufruf in der Geschäftslogik. Der Core ist reines PHP – Klassen, Interfaces, Werttypen. Er beschreibt was das System tut, nicht, wie es mit der Außenwelt kommuniziert.

Slim, Doctrine DBAL, symfony/mailer, symfony/twig – das sind Adapter. Sie verbinden den Core mit der Infrastruktur. Sie kennen den Core. Der Core kennt sie nicht.

Das ist keine neue Idee. Es ist Hexagonale Architektur, Clean Architecture, Ports and Adapters – unter verschiedenen Namen beschrieben, aber im Kern dieselbe Überzeugung: Abhängigkeiten zeigen nach innen, nie nach außen.

Warum ist das wichtig? Weil es Testbarkeit ermöglicht ohne Infrastruktur. Weil es Austauschbarkeit ermöglicht ohne Umbau. Und weil es die Frage „was tut dieses System" von der Frage „womit tut es das" trennt.


PHP 8.3 – eine andere Sprache

PHP hat sich verändert. Wer PHP noch durch die Linse von PHP 5.x betrachtet – global functions, arrays überall, keine Typen – betrachtet eine Sprache, die es so nicht mehr gibt.

PHP 8.3 mit Enums, Readonly Classes, Typed Properties, Constructor Promotion, Named Arguments und strikter Typisierung ermöglicht ausdrucksstarke Domänenmodelle, die früher nur in Java oder C# möglich waren. Ein Survey-Wertobjekt mit readonly Properties und einem benannten Konstruktor – das ist kein Kompromiss mehr. Das ist sauberes Domain-Driven Design in PHP.

PHPStan auf maximalem Level ist dabei keine Schikane. Es ist die zweite Sicherheitsschicht nach den Tests. Typfehler, unbehandelte Nullables, nicht erreichbare Pfade – das sind Fehler die zur Entwicklungszeit gefunden werden, nicht zur Laufzeit beim Kunden.


Kein Full-Stack-Framework – eine Entscheidung für die Zukunft

Es gibt ein Argument gegen Laravel, Symfony und Yii, das selten so direkt ausgesprochen wird: ein Full-Stack-Framework koppelt die Lebensdauer des Projekts an die Lebensdauer des Frameworks.

Wir haben das in dieser Serie hautnah beschrieben. Der Sprung von Yii 1.1 auf Yii 2 war kein Upgrade – er war ein kompletter Rewrite des Frameworks, mit inkompatiblen APIs, einem anderen Komponentenmodell, anderen Konventionen. LimeSurvey hat diesen Sprung nie gemacht. Nicht weil niemand es wollte, sondern weil das Framework so tief in die Anwendungslogik eingewachsen war, dass ein Versionssprung das gesamte Projekt in Frage gestellt hätte. Yii 3 ist inzwischen erschienen – und LimeSurvey läuft noch immer auf Yii 1.1.

Das ist kein Einzelfall. Es ist ein Muster.

Ein Full-Stack-Framework übernimmt viele Entscheidungen – und mit jeder Entscheidung, die es trifft, wächst die Oberfläche, die bei einem Major-Update migriert werden muss. Routing, DI-Container, ORM, Template-Engine, Auth-System, Console-Commands – alles auf einmal, alles inkompatibel, alles dringend.

Der Stack, den wir hier beschreiben, vermeidet genau das. Slim hat eine schmale, stabile API – ein Major-Update betrifft das Routing, nicht die Domäne. Symfony Components werden einzeln aktualisiert – symfony/mailer 7 betrifft nicht symfony/twig. PHP-DI ist austauschbar – der Core kennt es nicht. Und der Domänenkern selbst bleibt bei jedem dieser Updates vollständig unberührt.

Darin liegt die eigentliche Freiheit des weißen Blatts: nicht nur heute die richtigen Entscheidungen zu treffen, sondern morgen noch die Kontrolle darüber zu haben.


Slim – das Framework das verschwindet

Slim PHP nimmt HTTP-Requests entgegen, mappt sie auf Handler und gibt Responses zurück. Das ist alles. Kein ORM, kein Template-System, kein Auth-Modul, keine Konventionen über Verzeichnisstruktur oder Benennungen.

Das klingt nach Einschränkung. Es ist das Gegenteil.

Ein Full-Stack-Framework wie Laravel trifft hunderte Entscheidungen bevor man die erste eigene Zeile schreibt. Manche davon passen. Manche nicht. Und mit der Zeit – wenn das Projekt wächst, wenn die Anforderungen sich verändern – zahlt man für jede Entscheidung die das Framework getroffen hat und die nicht zur eigenen Domäne passt.

Slim trifft keine dieser Entscheidungen. Und genau darin liegt die eigentliche Entscheidung: Verantwortung nicht auszulagern.


PHP-DI – Abhängigkeiten explizit machen

Dependency Injection ist kein Framework-Feature. Es ist ein Designprinzip: Abhängigkeiten werden übergeben, nicht selbst erzeugt. Keine Service-Locator-Antipatterns. Kein Yii::app(). Kein globaler State.

PHP-DI ist ein vollständiger DI-Container mit Autowiring. Er bindet den Anwendungskern zusammen, ohne ihn an ein Framework zu koppeln. Die Konfiguration ist explizit – wer welche Abhängigkeit bekommt, ist nachlesbar, nicht magisch.

Wer lange mit Yii::app()->getComponent('anything') gearbeitet hat, weiß, warum das ein Unterschied ist.


Symfony Components – ohne Symfony

Das ist eine der unterschätzten Entscheidungen im PHP-Ökosystem: Symfony-Components lassen sich einzeln verwenden. Man braucht nicht das gesamte Framework um von seiner Qualität zu profitieren.

symfony/console für CLI-Anwendungen – Commands, Argument-Parsing, Output-Formatierung. symfony/mailer für E-Mail-Transport – DSN-basiert, austauschbar, der Anwendungscode kennt keinen konkreten Mail-Provider. symfony/twig für Templates – typsicher, klare Trennung von Logik und Darstellung. symfony/expression-language für konfigurierbare Geschäftsregeln – dort wo Endnutzer oder Konfiguration steuern soll was zur Laufzeit passiert. symfony/yaml für das Parsen von YAML und Markdown-Frontmatter – diese Seite selbst wertet damit die Metadaten ihrer Artikel aus.

Jede dieser Components ist unabhängig versioniert, unabhängig testbar, und folgt denselben Qualitätsstandards. Das ist das Composer-Prinzip auf Framework-Ebene.


Doctrine DBAL – SQL ohne ORM-Overhead

Die Entscheidung gegen ein vollständiges ORM ist bewusst. Doctrine ORM ist mächtig – und es kauft diese Macht mit einer Komplexität, die sich tief in die Domänenmodelle einschreibt. Annotationen, Proxy-Klassen, Lazy Loading – das ist Infrastruktur-Wissen, das in der Domäne nichts zu suchen hat.

Doctrine DBAL ist die Datenbankabstraktion ohne ORM-Overhead. Typsichere Queries, saubere Parameter-Bindung, Unterstützung für verschiedene Datenbanken. Repositories werden selbst implementiert – SQL bleibt explizit und nachvollziehbar.

Das ist mehr Aufwand als ActiveRecord. Es ist auch mehr Kontrolle. Und in einem System das saubere Schichtenarchitektur ernst nimmt, ist es die konsequentere Entscheidung.


Frontend: Bootstrap und Alpine.js

Bootstrap 5 für konsistente, schnelle UIs ohne eigenes Design-System. Kein Build-Step, keine Abhängigkeiten, die gewartet werden müssen. Alpine.js für Interaktivität direkt im HTML – ohne JavaScript-Framework-Overhead, ohne State-Management-Komplexität.

Das ist eine bewusste Entscheidung gegen React, Vue und die gesamte Single-Page-Application-Welt. Nicht weil diese Werkzeuge schlecht sind – sondern weil sie für serverseitig gerenderte Anwendungen meistens überdimensioniert sind. Eine Umfrage-Anwendung rendert serverseitig. Die meiste Interaktivität lässt sich mit Alpine.js vollständig abbilden.


Qualitätssicherung als Designprinzip

PHPUnit mit dem Ziel 100% Abdeckung im Domain- und Application-Layer. Nicht als Metrik, die um ihrer selbst willen verfolgt wird – sondern als Signal. Was sich nicht isoliert testen lässt, ist falsch strukturiert. Der Test zeigt das Problem, nicht umgekehrt.

PHPStan auf maximalem Level als zweite Sicherheitsschicht. Zusammen mit strikter Typisierung entsteht ein System, das viele Fehlerklassen strukturell ausschließt, bevor sie entstehen können.


Was dieser Stack sagt

Jede dieser Entscheidungen ist eine Ablehnung. Die Ablehnung von Framework-Magie. Die Ablehnung von impliziten Abhängigkeiten. Die Ablehnung von Konventionen, die man nicht versteht. Die Ablehnung von Komplexität, die niemand bestellt hat.

Das weiße Blatt ist eine Gelegenheit. Nicht um perfekten Code zu schreiben – perfekten Code gibt es nicht. Sondern um die Entscheidungen bewusst zu treffen, die in einem gewachsenen System irgendwann jemand anders getroffen hat.

Wer lange mit Legacy-Code gearbeitet hat, weiß, was auf dem Spiel steht. Jede Entscheidung, die heute getroffen wird, ist morgen der Kontext, in dem jemand anderes arbeitet.

Das ist die eigentliche Verantwortung des weißen Blatts.