Case Study · B2B-Plattform eines Automobilherstellers
Aus einem Serverumzug wurde eine Modernisierung von Laravel 8 auf 13.
Der Kunde wollte eine gewachsene B2B-Anwendung auf einen neuen Server bringen. Ich hätte sie unverändert umziehen können. Damit wäre sie zwar auf neuer Infrastruktur gelaufen, aber weiterhin auf einem alten Framework mit veralteten Abhängigkeiten und kaum Schutz für ihre wichtigsten Abläufe.
Ich schlug deshalb keinen Rewrite vor, sondern einen kontrollierten Upgrade-Pfad. Das Ziel war eine wieder wartbare Anwendung, ohne Bestellung, Lager, Logistik, Dokumente oder Abrechnung unterwegs neu zu erfinden.
Ausgangslage
Der Auftrag war kein gewöhnlicher Serverumzug
Die Plattform steuert Bestellungen, Lagerbewegungen, Logistik, Dokumente und Abrechnung. Sie tauscht außerdem Daten mit externen Systemen aus. Ein Fehler betrifft deshalb nicht irgendeine alte Verwaltungssoftware. Er kann echte Arbeit stoppen oder falsche Geschäftsdaten erzeugen.
Bei der Bestandsaufnahme fand ich Laravel 8, PHP 8.0, Livewire 2, Laravel Mix und mehrere Abhängigkeiten, die neue Framework-Versionen blockierten. Gleichzeitig gab es zu wenig automatisierte Absicherung, um einen großen Versionssprung guten Gewissens durchzuführen.
Das änderte die Aufgabenstellung. Der Serverumzug blieb notwendig, aber er wurde zum letzten Schritt einer größeren Modernisierung.
Entscheidung
Warum ich die Anwendung modernisiert und nicht neu geschrieben habe
Ein Rewrite hätte aufgeräumter gewirkt. Er hätte aber auch jahrelang gewachsene Regeln wieder zu Vermutungen gemacht. Welche Bestellpositionen dürfen zusammengeführt werden? Wann darf eine Rechnung erneut versendet werden? Welche Berechtigung gilt noch, wenn sie zwischen zwei Requests entzogen wurde?
Solche Regeln stehen selten vollständig in einem Pflichtenheft. Sie stecken im Code, in Datenbankstrukturen und in den Erwartungen der Menschen, die täglich mit dem System arbeiten.
Deshalb blieb der fachliche Kern bestehen. Ich modernisierte den technischen Unterbau schrittweise und änderte Geschäftslogik nur dort, wo Tests einen Fehler oder eine unsichere Grenze sichtbar machten.
Vorgehen
Zuerst musste die alte Anwendung ihr Verhalten erklären
Bevor ich Composer aktualisierte, arbeitete ich mich durch Routen, Livewire-Komponenten, Jobs, Migrationen und die wichtigsten Abläufe in der laufenden Anwendung. Daraus entstanden Tests, Factories und Hilfscode für die vorhandenen Geschäftsregeln.
Diese erste Phase fügte 10.423 Zeilen hinzu. Die Zahl ist kein Selbstzweck. Entscheidend war, was die Tests festhielten: Bestellungen, Berechtigungen, Warenkorbverhalten, Dokumente, Hintergrundjobs und externe Übergaben.
Die Arbeit fand auch Fehler, die schon unter Laravel 8 vorhanden waren. Ich behob sie vor dem Upgrade. So musste ich später nicht raten, ob ein Problem neu war oder nur zum ersten Mal sichtbar wurde.
Umsetzung
Fünf Laravel-Versionen, aber kein großer Sprung ins Ungewisse
Ich ging von Laravel 8 über jede Hauptversion bis Laravel 13. Nach jeder Stufe mussten PHP-Tests, Frontend-Build und Browser-Flows wieder funktionieren. Erst dann kam die nächste Version.
Das klingt langsamer als ein großer Sprung. In der Praxis spart es Sucharbeit. Wenn nach einem Upgrade etwas bricht, liegt die Ursache in einem kleinen Änderungssatz statt irgendwo zwischen fünf Framework-Releases und einem komplett neuen Frontend-Build.
Die Modernisierung blieb dadurch in 35 fokussierten Commits nachvollziehbar. Jeder Schritt hatte eine erkennbare Aufgabe und einen überprüfbaren Endzustand.
| Bereich | Vorher | Nachher |
|---|---|---|
| Backend | Laravel 8 · PHP 8.0 | Laravel 13 · PHP 8.3 |
| UI | Livewire 2 · Tailwind 2 | Livewire 4 · Tailwind 4 |
| Build / Tests | Mix · PHPUnit 9 | Vite 8 · PHPUnit 12 |
Die schwierigen Stellen
Die eigentliche Arbeit lag zwischen den Framework-Upgrades
Laravel selbst war selten das größte Problem. Schwieriger waren die Stellen, an denen das alte System stillschweigende Annahmen mitgebracht hatte.
Ein aufgegebenes Warenkorb-Paket blockierte das Upgrade
Ich kopierte weder das ganze Paket noch schrieb ich den Checkout gleichzeitig neu. Stattdessen übernahm die Anwendung nur den kleinen Vertrag, den sie tatsächlich brauchte. Dazu gehörten Warenkorb-Instanzen, Artikel, Optionen, Events, Summen und gespeicherte Warenkörbe. Bestehende Tests hielten das Verhalten fest.
Livewire machte Browserzustand zu einer Sicherheitsfrage
Öffentliche Properties behandelte ich wie Request-Daten. Preise wurden serverseitig neu berechnet, Datensätze beim Speichern erneut dem Benutzer zugeordnet und entzogenes Zugriffsrecht auch zwischen zwei Requests geprüft. Render-Tests hätten diese Fehler nicht gefunden, deshalb testete die Suite vollständige Aktionen.
Hintergrundjobs mussten Wiederholungen aushalten
Doppelte Requests durften keine zweite Bestellung erzeugen. Ein abgebrochener Worker durfte Dokumente oder E-Mails nicht doppelt ausliefern. Persistente Statusdaten, eindeutige Jobs und Sperren machten diese Abläufe wiederholbar.
Ein grüner PHP-Test beweist keinen funktionierenden Produktions-Build
Beim Wechsel von Mix zu Vite und von Tailwind 2 auf 4 konnten dynamisch erzeugte Klassen aus dem fertigen CSS verschwinden. Deshalb prüft ein Build-Test 22 benötigte Selektoren in der tatsächlich erzeugten Datei. Außerdem ersetzte ich 124 Verwendungen externer Icon-Komponenten durch lokale SVGs und entfernte 17 Composer-Pakete.
Produktionswechsel
Der neue Code war erst fertig, als er sicher live lief
Die aktualisierte Anwendung lief zuerst isoliert auf dem neuen Runtime-Stack. Anonymisierte Daten machten reale Geschäftsabläufe prüfbar. Mailpit fing ausgehende Nachrichten ab, damit ein Testlauf keinen Kunden erreichte.
Vor dem Wechsel prüfte ich Scheduler, Queue, öffentliche Einstiegspunkte, sechs Browser-Flows, Blade-Kompilierung, Composer-Audit und den Produktions-Build. Nach der Kundenabnahme folgten Daten-Freeze, finale Datenbankmigration und Domain-Umschaltung.
Auf dem neuen Host prüfte ich Anmeldung, Bestellablauf, Dokumente, E-Mail-Übergaben, Queue und Scheduler noch einmal. Erst danach war die Modernisierung für mich abgeschlossen.
Ergebnis
Was der Kunde am Ende bekommen hat
Der Kunde bekam keine neue Anwendung, die erst wieder lernen musste, wie das Geschäft funktioniert. Er bekam die vertrauten Abläufe auf einem aktuellen technischen Fundament, mit deutlich mehr Schutz für künftige Änderungen.
- Laravel 8 → 13
- Auf einen unterstützten Framework-Stand gebracht
- 698 PHP-Tests
- Mit 4.593 Assertions für die Geschäftsabläufe
- 6 Browser-Flows
- Auf dem echten Produktions-Build geprüft
- 17 Pakete entfernt
- Weniger Abhängigkeiten im laufenden System
Die Anwendung läuft heute mit Laravel 13 auf dem neuen Host. Die Tests dokumentieren nicht nur, dass Seiten rendern. Sie halten die Geschäftsabläufe fest, die bei der nächsten Änderung nicht verloren gehen dürfen.
Rückblick
Was ich aus dieser Modernisierung mitnehme
Ich würde wieder mit dem Verhalten beginnen, nicht mit der Zielversion. Laravel lässt sich nach einem dokumentierten Pfad aktualisieren. Schwieriger ist es, die Regeln einer gewachsenen Anwendung so gut zu verstehen, dass man erkennt, was sich ändern darf und was nicht.
Ich würde auch wieder einen Hauptversionsschritt nach dem anderen machen. Große Sprünge sehen in einem Projektplan kürzer aus. Kleine, vollständig geprüfte Schritte sind in einer geschäftskritischen Anwendung meist schneller, weil Fehler einen klaren Ursprung haben.
Und ich würde den Produktionswechsel wieder als Teil der Entwicklung behandeln. Eine Modernisierung endet nicht bei grünem CI. Sie endet, wenn die Menschen, Jobs und Schnittstellen auf dem neuen System zuverlässig weiterarbeiten.
Meine Rolle
Ich habe die Modernisierung geplant, umgesetzt und bis in Produktion begleitet
Ich bin Mathias Onea, Software Engineer mit Schwerpunkt Laravel, PHP und gewachsenen Geschäftsanwendungen. In diesem Projekt verantwortete ich Bestandsaufnahme, Upgrade-Strategie, Tests, Sicherheitskorrekturen, Frontend-Migration und Produktionswechsel.
Die Zahlen auf dieser Seite wurden gegen den finalen Upgrade-Branch und den erfolgreichen CI-Lauf geprüft.