Erst prüfen, ob gebaut werden muss
Der erste Blick geht in den Core, und er lohnt sich häufiger, als man denkt. TYPO3 bringt im Standard Formulare über das Form Framework mit, eine Verwaltung für Weiterleitungen, Kategorien, Mehrsprachigkeit über die Site-Konfiguration, eine Volltextsuche und den Scheduler für wiederkehrende Aufgaben. Seit v13 bündeln Site Sets TypoScript, Einstellungen und Abhängigkeiten zu Paketen, die sich je Site aktivieren lassen. Vieles, wofür früher eine kleine Extension geschrieben wurde, ist heute ein paar Zeilen Konfiguration oder ein angepasstes Fluid-Template. Bevor wir eine Extension schreiben, prüfen wir deshalb, ob TypoScript, ein Site Set oder der Core die Aufgabe schon lösen. Eine Extension, die nicht installiert wird, kostet nichts — weder beim Bau noch beim nächsten Update.
Der zweite Weg ist eine gepflegte Extension aus dem TER oder von Packagist. Für Nachrichten, Kalender, Karten, Cookie-Hinweise oder Suchanbindungen gibt es etablierte Lösungen, und eine Eigenentwicklung daneben wäre Verschwendung. Gepflegt heißt dabei konkret: ein Release für die aktuelle LTS, ein Maintainer, der auf Fehlermeldungen antwortet, ein öffentlicher Issue-Tracker und Sicherheitskorrekturen, die in der Vergangenheit zeitnah kamen. Wichtig ist auch, wie eine Extension mit neuen Major-Versionen umgeht: Die Extension news etwa unterstützt ab Version 14.0.0 kein TYPO3 v12 mehr — wer sie aktuell halten will, muss mitziehen. Liefert ein Autor bei einer Sicherheitslücke keinen Fix, entfernt das TYPO3 Security Team die betroffenen Versionen aus dem TER. Deckt eine gepflegte Extension Ihre Anforderung, empfehlen wir sie, richten sie ein und nehmen sie in die Wartung auf, statt dieselbe Funktion ein zweites Mal zu bauen.
Der dritte Weg ist die Eigenentwicklung. Sie bringt genau die Funktion, die Ihr Ablauf braucht, ohne Konfigurationsmasken für Fälle, die es bei Ihnen nicht gibt, und ohne fremde Roadmap. Dafür gehört die Pflege von Anfang an dazu. Jede Extension, ob geladen oder gebaut, ist eine Position im Betrieb: Jedes Patch-Update des Cores prüft sie mit, jedes Major-Upgrade verlangt ihre Anpassung, jede Sicherheitsmeldung zu ihr will bewertet werden. Wir rechnen die Wege deshalb über die Jahre gegeneinander, die Sie die Website betreiben wollen, nicht über den Angebotspreis. Und manche Anforderung ist gar keine Extension-Anforderung, sondern eine Redaktionsregel, die noch niemand aufgeschrieben hat. Die schreibt man auf, statt sie zu programmieren.
Wie wir eine Extension bauen
Eine Extension entsteht bei uns als Composer-Paket mit eigenem Vendor-Namespace und einer composer.json, die den Typ typo3-cms-extension, den Extension-Key und die unterstützten TYPO3-Versionen benennt. Ab v14 ist eine gültige composer.json ohnehin Pflicht, auch in Classic-Mode-Installationen. Projektspezifische Extensions liegen im Repository des Projekts, wiederverwendbare in einem eigenen; den Quellcode bekommen Sie in einem Git-Repository, das Ihnen gehört. Die Konfiguration steht in Dateien und damit in der Versionsverwaltung: TCA für Tabellen und Felder, ein Site Set für TypoScript und Einstellungen, Symfony Dependency Injection für die Services. Was ein Redakteur ändern darf, steht im Backend. Was Code ist, steht im Repository — und nirgends sonst.
Extbase und Fluid setzen wir dort ein, wo sie passen: bei Datensätzen mit Listen- und Detailansicht, bei Formularen mit Validierung, bei einem Domänenmodell, das mehr ist als eine Tabelle. Für einen JSON-Endpunkt, einen Teaser aus einer Fremd-API oder eine kleine Ausgabe ist Extbase Überbau; dann genügt ein schlanker Controller oder eine PSR-15-Middleware. Eigene Inhaltselemente bauen wir mit Content Blocks — einer Extension, nicht Teil des Cores — oder mit eigenen TCA-Definitionen und Fluid-Template, damit Redakteure sehen, was sie pflegen. Bei Eschen Nutzfahrzeuge ist der Fahrzeugbestand als strukturierte Datensätze angelegt, redaktionell pflegbar und mobil nutzbar. Strukturierte Datensätze lassen sich filtern, sortieren und an anderer Stelle wiederverwenden; Fließtext nicht. Was in kein Inhaltselement passt — eine Importübersicht, eine Freigabeliste —, bekommt ein eigenes Backend-Modul.
Eingegriffen wird über die Erweiterungspunkte, die TYPO3 dafür vorsieht. PSR-14-Events, um sich an Abläufe des Cores und anderer Extensions zu hängen. PSR-15-Middlewares für alles, was am Request oder an der Response geschieht, etwa ein eigener API-Endpunkt. Symfony Dependency Injection, um Services zu konfigurieren, statt sie per new zu erzeugen. TCA-Overrides, um vorhandene Tabellen um Felder zu erweitern. Und der DataHandler, wenn Datensätze programmatisch geschrieben werden — dann greifen Rechte, Protokoll, Referenzindex und Cache wie bei einer Eingabe im Backend. Nicht dazu gehören Core-Patches, die das nächste Composer-Update überschreibt, und XCLASS: Sie ersetzt eine Core-Klasse vollständig, pro Klasse greift nur eine, und jede Änderung am Original muss von Hand nachgezogen werden. Was über einen vorgesehenen Erweiterungspunkt eingreift, übersteht ein Major-Release in aller Regel. Was daran vorbeigreift, hat ein Ablaufdatum.
Schnittstellen-Extensions: Daten aus Fremdsystemen
Viele Extensions haben eine einzige Aufgabe: Daten aus einem anderen System auf die Website zu bringen. Beim Autohaus Brüggemann — sechs Standorte von Rheine bis Rostock, acht Vertragsmarken, ein Bestand im vierstelligen Bereich — führt die TYPO3-Website alle Fahrzeuge in einer gemeinsamen Suche zusammen. Gespeist wird sie aus dem Händlersystem, nicht aus der Redaktion. Das ist das Muster: Was ein anderes System ohnehin führt, wird nicht ein zweites Mal von Hand gepflegt. Die Website zeigt denselben Stand wie das System dahinter, und niemand tippt Fahrzeugdaten ab. Damit das trägt, muss vorher geklärt sein, welche Daten in welche Richtung fließen, wie oft und was bei einem Fehler passiert.
Technisch läuft ein Import bei uns als Symfony-Console-Befehl, aufrufbar über vendor/bin/typo3 und zeitgesteuert über den Scheduler. Damit lässt er sich von Hand anstoßen, testen und auf der Kommandozeile nachvollziehen, statt in einem Frontend-Request zu laufen, der bei großen Datenmengen in ein Timeout gerät. Jeder Lauf schreibt ein Protokoll: wie viele Datensätze neu, geändert oder gelöscht wurden, welche abgelehnt und warum. Ein abgebrochener Lauf lässt sich ohne Schaden wiederholen, weil jeder Datensatz über einen eindeutigen Schlüssel aus dem Quellsystem zugeordnet wird und nicht über seine Position in einer Datei. Geschrieben wird über den DataHandler oder in eine klar abgegrenzte eigene Tabelle — nie per Direktzugriff auf Core-Tabellen, an dem Rechte und Cache vorbeilaufen.
Die entscheidende Frage ist keine technische: Wer führt welches Feld? Kommen Preis und Verfügbarkeit aus dem Fremdsystem, darf die Redaktion sie nicht überschreiben — sonst stimmt nach dem nächsten Import entweder die Website nicht, oder die Arbeit der Redaktion ist weg. Ergänzende Texte, Bilder oder Hervorhebungen dagegen bleiben redaktionell, in eigenen Feldern, die der Import nicht anfasst. Diese Datenhoheit je Feld legen wir schriftlich fest, bevor die erste Zeile Code entsteht. Eine Grenze ziehen wir dabei bewusst: Ein Shop gehört nicht als Extension ins CMS. Bei M&S Armaturen läuft der Unternehmensauftritt auf TYPO3, der Shopware-Shop ist angebunden. Ein Verkaufsprozess mit Preisregeln, Zahlarten und Retouren gehört in ein Shopsystem, und das bauen wir mit Shopware 6 daneben, statt es in eine CMS-Extension zu zwingen.
Fremde und verwaiste Extensions beim Upgrade
Bei einem Major-Upgrade ist selten der Core die Bremse, sondern die Extension-Liste. Deshalb beginnt jede Upgrade-Arbeit bei uns mit einem Inventar: Welche Extensions sind installiert, welche davon werden tatsächlich genutzt, wer pflegt sie, gibt es ein Release für die Zielversion? Ob eine Extension genutzt wird, zeigt die Datenbank, nicht das Gedächtnis — ein Plugin, das auf keiner Seite mehr eingebunden ist, muss nicht migriert werden, sondern entfernt. Am Ende steht für jede Extension ein Vermerk: aktualisieren, durch den Core ersetzen, durch eine gepflegte Extension ersetzen, portieren, neu bauen oder entfernen. Diese Liste ist meistens kürzer als befürchtet, und sie ist die Grundlage jeder ehrlichen Schätzung.
Für eine Extension ohne Release für die Zielversion gibt es drei Wege. Ersatz durch den Core oder eine gepflegte Alternative, auch wenn dabei Konfiguration und Daten umziehen müssen. Portierung, also die Übernahme des Codes in Ihr eigenes Repository und die Anpassung an die neue Version — die GPL, unter der TYPO3-Extensions stehen, erlaubt das; mit dem Fork kommt aber auch die Pflege dazu. Oder der Neubau der Funktion, oft mit kleinerem Umfang, weil sich zeigt, dass die Hälfte der Optionen nie benutzt wurde. Der Neubau hat einen Vorteil, der oft übersehen wird: Was die Funktion tun soll, lässt sich am laufenden System ablesen. Beide Zahlen, Portierung und Neubau, stehen in der Schätzung nebeneinander, auch wenn eine davon der kleinere Auftrag ist.
Für die Portierung selbst gibt es Werkzeuge, die einen Teil der Arbeit abnehmen. Der Extension Scanner im Install Tool findet im Code Aufrufe von API, die in der Zielversion entfernt oder als veraltet markiert ist. TYPO3 Rector schreibt einen Teil dieser Stellen automatisch um, Fractor erledigt dasselbe für TypoScript, FlexForms, YAML und Fluid. Was bleibt, ist Handarbeit: Geschäftslogik in Templates, Abhängigkeiten, deren Composer-Constraints kollidieren, und in v14 vor allem die Umstellung der Plugin-Registrierung von list_type auf eigene CTypes samt Upgrade-Wizard, damit vorhandene Inhaltselemente nach dem Upgrade weiter ausgegeben werden. Welche Breaking Changes v14 im Einzelnen mitbringt und wie ein Upgrade-Projekt abläuft, steht auf unserer Seite zum TYPO3-Update. Hier geht es um den Code, der dabei mitziehen muss.
Fremde Eigenentwicklungen übernehmen
Eigenentwicklungen einer früheren Agentur übernehmen wir, aber nicht blind. Vor einer Zusage steht ein Code-Review mit demselben Satz Fragen, jedes Mal. Gibt es eine composer.json, und an welche TYPO3-Version ist sie gebunden? Liegt der Code in einem Repository oder nur auf dem Server? Hängt sich die Extension über Events und Dependency Injection ein oder über XCLASS, veraltete Hooks und Direktzugriffe auf Core-Tabellen? Schreibt sie Datensätze über den DataHandler? Steht Geschäftslogik in Fluid-Templates oder in TypoScript? Was findet der Extension Scanner gegen die Zielversion? Existieren Tests, und laufen sie? Das ist Arbeit von Stunden bis Tagen und ein abgegrenztes Erstprodukt mit eigenem Preis, den wir vorher schriftlich nennen.
Am Ende steht ein Befundbericht: was die Extension tut, wo sie an Interna des Cores greift und was für die nächste Version zu tun ist, mit einer Risikoliste nach Priorität und dem Aufwand in Personentagen. Daneben steht die Alternative Neubau mit ihrer eigenen Zahl, damit Sie zwischen beiden Wegen entscheiden können, bevor Geld in den Code fließt. Und wir sagen ab, wenn eine Extension so gebaut ist, dass wir für ihr Verhalten nach dem nächsten Update nicht geradestehen können, oder wenn zentrale Teile ohne Quellcode ausgeliefert wurden — dann ist der Neubau der ehrlichere Auftrag. Wer fremden Code übernimmt, ohne ihn gelesen zu haben, übernimmt vor allem dessen Überraschungen.
Nach der Übernahme kommt zuerst Ordnung, dann Pflege. Der Code zieht in ein Git-Repository, das Ihnen gehört, bekommt eine composer.json, Versionsnummern nach Semantic Versioning und eine Dokumentation, die erklärt, was die Extension tut und wie sie konfiguriert wird. Kritische Abläufe bekommen Tests mit dem TYPO3 Testing Framework, bevor jemand sie ändert — sonst weiß niemand, ob eine Änderung etwas kaputt gemacht hat. Erst danach wird portiert oder erweitert. Die laufende Pflege übernehmen wir in der TYPO3-Wartung mit Service-Level-Vereinbarung, auf Wunsch zusammen mit dem Betrieb auf unseren eigenen Servern im Hetzner-Rechenzentrum Falkenstein. Die Extension steht wie TYPO3 selbst unter der GPL, der Quellcode gehört Ihnen, die Domain läuft auf Ihren Namen.