Es gibt erstaunlich viele Anwendungen, die zwischen 2012 und 2016 mit CakePHP 2 gebaut wurden und heute noch jeden Tag laufen. Kundenportale, Buchungssysteme, interne Verwaltungen. Damals war das eine vernünftige Wahl: klare Konventionen, schnelle Ergebnisse, ein Scaffolding, das in einer Woche eine brauchbare Oberfläche lieferte.
Das Problem kommt meistens von außen. Der Hoster kündigt das Ende von PHP 7.4 an, ein Pentest findet Lücken in einer Bibliothek, oder der Entwickler, der die Anwendung kannte, ist weg. Dann stellt sich heraus, dass "mal eben auf die aktuelle CakePHP-Version heben" kein Update ist. Bevor Sie dafür ein Angebot einholen, sollten Sie die folgenden Fragen beantworten können.
Läuft Ihre Anwendung überhaupt auf CakePHP 2?
Das lässt sich in zwei Minuten prüfen. Typisch für CakePHP 2 sind ein Ordner app/ mit Unterordnern wie Controller, Model und View, Templates mit der Endung .ctp und ein Ordner lib/Cake/, in dem das Framework selbst liegt. Die genaue Version steht in lib/Cake/VERSION.txt. Wenn es keine composer.json gibt oder das Framework als Kopie im Projekt liegt, ist das bei Anwendungen aus dieser Zeit eher die Regel als die Ausnahme.
Liegt die Version unter 2.10, ist die Lage noch etwas enger, weil schon die letzten Korrekturen der 2er-Reihe fehlen.
Warum ist der Schritt auf eine neue CakePHP-Version kein normales Update?
Weil CakePHP 3 das Framework von Grund auf neu gebaut hat, und die Versionen 4 und 5 darauf aufsetzen. Namespaces, Composer, eine neue Verzeichnisstruktur, das lässt sich mit dem offiziellen Upgrade-Werkzeug zu großen Teilen automatisch umstellen. Für das Herzstück gilt das leider nicht, die Datenschicht.
In CakePHP 2 liefert ein find('all', ...) verschachtelte Arrays zurück, und jede Stelle im Code, die damit arbeitet, kennt genau diese Struktur. $order['Order']['total'], $order['Customer']['name'], quer durch Controller, Views und Hilfsfunktionen. Ab CakePHP 3 gibt es stattdessen Table-Klassen, Entity-Objekte und Query-Objekte. Jede dieser Stellen muss angefasst werden, und ein Werkzeug, das das zuverlässig übersetzt, gibt es nicht.
Dazu kommt die Fachlogik, die in Model-Callbacks steckt. beforeSave, afterSave, beforeFind und Behaviors, die beim Speichern still Preise neu berechnen, Statusfelder setzen oder Mails verschicken. Diese Regeln sind nirgends dokumentiert, und sie verschwinden beim Umbau, wenn niemand sie vorher festhält.
Ein Upgrade von 2 auf 5 ist deshalb in Aufwand und Risiko näher an einem Neubau der Datenschicht als an einem Versionssprung. Wer Ihnen dafür ein paar Wochen veranschlagt, hat die Model-Schicht wahrscheinlich noch nicht gesehen.
Reicht es, die Anwendung auf PHP 8 lauffähig zu machen?
Offiziell ist bei CakePHP 2 mit PHP 7.4 Schluss, und PHP 7.4 bekommt seit Ende 2022 keine Sicherheitskorrekturen mehr. Es gibt Community-Forks, die die 2er-Reihe auf PHP 8 lauffähig machen, und manchmal ist das genau der richtige Schritt: wenn die Anwendung in zwei Jahren ohnehin abgelöst wird und bis dahin nur stabil laufen muss.
Sie sollten dabei aber wissen, worauf Sie sich einlassen. Sie hängen dann an einem Projekt von Freiwilligen, ohne Zusage, dass es die nächste PHP-Version auch noch mitmacht. Die eingebundenen Plugins und Bibliotheken müssen zusätzlich mitziehen, und das tun sie oft nicht. Und an der Lage für neue Entwickler ändert sich nichts, denn CakePHP 2 lernt heute niemand mehr freiwillig.
Als Überbrückung für ein, zwei Jahre ist das legitim, als Plan für die nächsten fünf eher nicht. Was es generell bedeutet, auf einer PHP-Version ohne Sicherheitskorrekturen zu laufen, steht in PHP End of Life: das Sicherheitsrisiko.
Wohin also: CakePHP 5, Laravel oder Symfony?
Drei Wege sind realistisch. Jeder davon funktioniert, und jeder hat eine Stelle, an der er typischerweise kippt.
Upgrade im bestehenden Projekt, Version für Version. Ein Branch, in dem die Anwendung über 3 und 4 auf 5 gehoben wird, und am Ende wird umgeschaltet.
Woran es scheitert: Der Branch läuft Monate neben dem Produktivsystem her, in das weiter Änderungen einfließen. Jede Woche wächst der Abstand. Am Ende ist es ein Big Bang mit Stichtag, nur unter anderem Namen, und die Gründe, warum so etwas scheitert, stehen in Warum Big-Bang-Neuentwicklungen so oft scheitern.
Schrittweiser Umzug in eine neue CakePHP-5-Anwendung. Die alte und die neue Anwendung laufen nebeneinander, der Webserver verteilt die Anfragen, und Controller für Controller wandert hinüber. Der Vorteil: Konventionen, Namensgebung und Denkweise bleiben vertraut, wer CakePHP 2 kannte, findet sich in CakePHP 5 schneller zurecht als in einem fremden Framework.
Woran es scheitert: Der Markt an Entwicklern, die CakePHP 5 beherrschen, ist in Deutschland deutlich kleiner als bei Laravel oder Symfony. Wenn Sie in drei Jahren jemanden für die Weiterentwicklung suchen, merken Sie das.
Schrittweiser Umzug zu Laravel oder Symfony. Dasselbe Vorgehen, nur mit einem anderen Ziel. Ablauf und Fragen dazu habe ich in Alte PHP-Anwendung auf Laravel umstellen und Legacy-Code in eine Symfony-Anwendung integrieren beschrieben.
Woran es scheitert: Der Übersetzungsaufwand ist größer, weil die CakePHP-Konventionen (automatische Tabellennamen, Assoziationen, Behaviors) nicht eins zu eins existieren. Wer das unterschätzt, hat nach der ersten Etappe eine Mischung aus beiden Welten, die keiner der beiden Konventionen folgt.
Zwischen den letzten beiden würde ich danach entscheiden, wer die Anwendung in fünf Jahren pflegen soll. Hat Ihr Team Laravel- oder Symfony-Erfahrung, ist der Wechsel meist die bessere Investition. Hängt viel Wissen an den CakePHP-Konventionen, kann CakePHP 5 der ruhigere Weg sein.
Was wird beim Nebeneinanderbetrieb konkret schwierig?
Das Prinzip ist bei beiden schrittweisen Wegen dasselbe und in Das Strangler-Fig-Pattern ausführlich erklärt. CakePHP 2 hat dabei einen echten Vorteil: Die Adressen folgen fast immer dem Schema /controller/action/parameter. Der Webserver kann also sauber nach Controller trennen, /rechnungen/... geht an die neue Anwendung, alles andere bleibt beim alten Code. Bei Anwendungen ohne Framework ist genau das oft der mühsamste Teil.
Schwierig sind drei andere Stellen.
Die Anmeldung. CakePHP 2 legt den angemeldeten Benutzer in der Session unter Auth.User ab, und das Session-Cookie heißt standardmäßig CAKEPHP statt wie üblich PHPSESSID. Die neue Anwendung muss diese Session lesen können, sonst wird jeder Nutzer beim ersten Klick in einen umgezogenen Bereich abgemeldet. Lösbar, aber das gehört vor die erste Etappe.
Die Passwörter. Der Standard in CakePHP 2 war lange ein SHA1-Hash mit einem anwendungsweiten Salt aus der Konfiguration. Ab Version 2.4 gab es bcrypt als Option, aber viele Anwendungen sind beim alten Verfahren geblieben. SHA1 ist für Passwörter seit Jahren nicht mehr vertretbar, und wurde der Beispielwert für den Salt aus dem Projektgerüst nie geändert, schützt er praktisch nichts. Die übliche Lösung: Beim nächsten erfolgreichen Login wird das Passwort nach dem alten Verfahren geprüft und sofort mit password_hash() neu gespeichert. So wandern die Konten nach und nach um, ohne dass jemand sein Passwort zurücksetzen muss. Mehr dazu in Passwort-Hashing in PHP.
Die Datenbank. Beide Anwendungen arbeiten zunächst auf denselben Tabellen, und das funktioniert gut, solange für jede Tabelle feststeht, wer schreibt. Gefährlich sind die Model-Callbacks von oben. Speichert die neue Anwendung eine Bestellung, läuft das afterSave aus CakePHP 2 nicht mehr, und was es bisher nebenbei erledigt hat, passiert einfach nicht. Deshalb wird vor jedem Umzug in Tests festgehalten, was beim Speichern tatsächlich alles geschieht. Wie das bei Code ohne Tests geht, steht in Tests in Legacy-Code einführen, wo keine sind.
Wer kümmert sich bei Ihnen darum?
Das ist die Frage, an der die meisten CakePHP-2-Projekte tatsächlich hängen. Die Technik ist lösbar. Oft gibt es aber niemanden mehr im Haus, der die Anwendung wirklich kennt, und der externe Dienstleister von damals hat sich längst anderen Frameworks zugewandt. Dann wird so lange nichts entschieden, bis der Hoster das Datum vorgibt.
Wenn Sie in dieser Lage sind, kommt vor jeder Umsetzung eine ehrliche Bestandsaufnahme: welche Version, wie viele Controller und Models, wie viel Logik in Callbacks und Behaviors steckt, wie die Anmeldung funktioniert und welche Plugins mitgeschleppt werden. Danach lässt sich ziemlich genau sagen, welcher der drei Wege zu Ihnen passt und wie viele Etappen er braucht. Vorher ist jede Zahl geraten.