Vierzehn YAML-Dateien in config/packages, und jede fängt mit demselben Wort an. Ich habe sie an einem Donnerstagabend gezählt, als ein Staging-Mailer sich weigerte zu senden. Bevor ich mir überhaupt die DSN ansehen konnte, musste ich mich durch framework: scrollen, dann durch mailer:, dann erst kam der eigentliche Wert, und das in einer Datei, die ohnehin schon mailer.yaml heißt. Symfony 8.2 macht diese erste Zeile optional. Ich behaupte: Optional ist hier nur die höfliche Formulierung für „auf dem Weg nach draußen“. Wenn du auf 8.2 hochziehst, lösch das Präfix im selben Pull Request. Warte nicht, bis dich eine Deprecation-Meldung dazu zwingt.

config/packages/mailer.yaml
# old form, still accepted in Symfony 8.2 via aliases
framework:
  mailer:
    dsn: '%env(MAILER_DSN)%'

# new form, owned by MailerBundle
mailer:
  dsn: '%env(MAILER_DSN)%'

Die Fakten sind schnell erzählt. In 8.2 bringen 29 Komponenten, die bisher innerhalb von FrameworkBundle konfiguriert wurden, ihr eigenes Bundle mit, und das registriert sich selbst, sobald die Komponente installiert ist. config/bundles.php bleibt also unangetastet. Jedes Bundle leitet seinen Root-Key aus dem Klassennamen ab: MailerBundle hört auf mailer, PropertyInfoBundle auf property_info. FrameworkBundle behält die Teile, die wirklich zur HTTP-Schicht gehören, etwa Sessions, CSRF, den Profiler und Fragments. Für Maintainer ist das die eigentliche Schlagzeile: Eine neue Mailer-Option ist jetzt eine Änderung an symfony/mailer und an sonst nichts. Für alle, die nur Config schreiben, ändert sich sichtbar vor allem das Vokabular.

Und genau beim Vokabular verlangt das Release meiner Meinung nach ganz leise etwas von uns. Die alten Schlüssel funktionieren weiter, weil FrameworkBundle sie mit dem neuen NodeDefinition::aliasOf() deklariert und an die Extension der Komponente weiterreicht, bevor irgendetwas geladen wird. Das ist elegant. Aber das Tooling ist längst auf die neuen Namen umgestiegen. Wer seine effektiven HTTP-Client-Einstellungen sehen will, ruft jetzt debug:config http_client auf. Den framework-Pfad kennt dieser Befehl nicht mehr. Drei Roots haben unterwegs außerdem ihre Schreibweise geändert: Aus assets wurde asset, aus translator wurde translation, aus workflows wurde workflow. Eine Codebasis, die noch die alte Form schreibt, sagt in ihren Dateien jetzt das eine, während die Konsole etwas anderes sagt.

Hier das stärkste Gegenargument, und es ist ein gutes. Das Symfony-Team hat sich bewusst für Aliase statt Deprecation entschieden, damit niemand funktionierende Config anfassen muss. Ein Diff, der aus jeder Package-Datei eine Einrückungsebene entfernt, bringt keinem einzigen Nutzer etwas. Er kollidiert mit jedem offenen Branch, der Config anfasst, und er legt einem Reviewer vierzig Zeilen Whitespace-Rauschen vor, die er blind abnicken soll. Wenn dein Team eine Handvoll Apps betreibt und nach Plan upgradet, ist es eine vertretbare Entscheidung über deinen Nachmittag, framework: einfach stehen zu lassen. Ich habe so eine Entscheidung selbst schon getroffen, bei weniger wichtigen Dingen.

Trotzdem lande ich auf der anderen Seite, weil die eine echte Verhaltensänderung in diesem Release zeigt, warum eine ehrliche Struktur zählt. Unter der alten, einzelnen Extension hat das Einschalten von Forms auch Validation eingeschaltet, einfach weil der Code, der beides registrierte, zufällig an derselben Stelle saß. In 8.2 entscheidet jede Komponente für sich, und Validation ist da, weil symfony/validator installiert ist, nicht weil Forms es mitgebracht hat. Die meisten Apps mit Formularen haben den Validator sowieso, also bricht nichts. Die Lehre betrifft aber die Lesbarkeit. Das Framework hat eine Kopplung entfernt, die du nur beim Lesen des Codes finden konntest. Deine Config sollte nicht länger so tun, als gäbe es diese Kopplung noch.

Dazu kommt ein praktischer Grund, und der hat mit Timing zu tun. Der Upgrade-PR ist der eine Moment, in dem alle erwarten, dass sich config/packages ändert. Die Reviewer schauen ohnehin dort hin, die CI lässt ohnehin die ganze Suite laufen, und git blame führt die Umbenennung auf einen Commit namens "Symfony 8.2" zurück, der sich auch in einem Jahr noch von selbst erklärt. Machst du es sechs Monate später als eigenständiges Aufräumen, ist es ein rätselhafter Diff von jemandem, der offenbar eine ruhige Woche hatte. Machst du es nie, lernt die nächste Neueinstellung aus deinem Repo zwei Schreibweisen für dieselbe Einstellung. So oder so zahlst du, und später zahlen kostet mehr.

Ich gebe zu, dass mich die Aufteilung auf der Worker-Seite mehr begeistert als auf der Web-Seite. Ein Messenger-Consumer kann mit Sessions oder einem Profiler nichts anfangen, und jetzt muss er sie auch nicht mehr mitschleppen. Du registrierst ConsoleBundle und die Komponenten-Bundles, die der Worker tatsächlich anfasst. Bundles ohne Laufzeitarbeit bleiben außerdem bloße Klassennamen, bis jemand nach ihnen fragt, und MimeBundle ist die bewusste Ausnahme, weil seine Standard-MIME-Types bei jedem Request gesetzt werden müssen. Meine Freunde, die Go schreiben, erwähnen gern, wie klein ihre Binaries sind. Ein Symfony-Worker, der nur bootet, was er braucht, ist darauf eine faire Antwort, und deswegen nicht weniger PHP.

Meine Regel lautet also: Upgrade und Umbenennung gehen zusammen raus, mit je einem eigenen Commit, damit der Reviewer den zweiten überfliegen kann. Ich weiß, dass das eine Ermessensfrage ist, und manche von euch betreiben ganze Flotten von Apps, bei denen Config-Churn echte Kosten verursacht. Wenn du framework: absichtlich behältst, würde ich gern wissen, was dich umstimmen würde. Bräuchte es eine echte Deprecation in einem späteren Release, oder gibt es einen Grund, warum die Aliase für immer bleiben sollten, den ich nicht sehe?