Skip to content

Commit da44063

Browse files
committed
phase 6
1 parent c6c5330 commit da44063

5 files changed

Lines changed: 133 additions & 221 deletions

File tree

.docs/refactor/REFACTOR.md

Lines changed: 84 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -355,48 +355,101 @@ enthalten mit `hasVoted` und dem CSRF-Token mitgliedsbezogene Daten.
355355
Die TTL wird deshalb bewusst unter dem Polling-Intervall gehalten und in
356356
`DECISIONS.md` begründet.
357357

358-
### B15 — Keine Erweiterungspunkte für ein verteilbares Bundle
358+
### B15 — Keine Erweiterungspunkte für ein verteilbares Bundle**ZURÜCKGESTELLT**
359359

360360
Alle Klassen sind `final readonly` ohne Interfaces; die einzige dokumentierte
361361
Naht ist `QnaSessionControlVoter`. Ein Host-Projekt, das bei einer neuen Frage
362362
benachrichtigen, eine Session automatisch schließen oder Statuswechsel
363363
protokollieren will, hat keinen Ansatzpunkt.
364364

365-
**Zielbild:** `QuestionCreatedEvent`, `SessionStartedEvent`,
366-
`SessionClosedEvent` — dispatcht **nach** dem Commit, nie innerhalb der
367-
Transaktion. Das ist der Unterschied zwischen einem Bundle und einer Anwendung.
365+
**Lösungsweg, falls der Bedarf entsteht:** `QuestionCreatedEvent`,
366+
`SessionStartedEvent`, `SessionClosedEvent` — dispatcht **nach** dem Commit, nie
367+
innerhalb der Transaktion. `transactional()` gibt das Ergebnis bereits zurück
368+
(`QuestionService::create()`, `SessionService::start()`/`stop()`), dispatcht wird
369+
im äußeren Methodenrumpf. Ein Listener innerhalb der Transaktion sähe
370+
ungeschriebenen Zustand, verlängerte die Session-Sperre, und eine Exception in
371+
ihm rollte die fachliche Operation zurück.
368372

369-
Das verletzt `SPEC.md` §1.1 nicht: Dort sind *Benachrichtigungen* als
370-
Funktionsumfang ausgeschlossen, nicht die Naht, an der ein Host sie selbst
371-
anbringt.
373+
**Zurückgestellt am 15.09.2026.** Begründung: Es gibt keinen benannten Abnehmer,
374+
und `SPEC.md` §1.1 schließt Benachrichtigungen, Moderation und Export
375+
ausdrücklich als Funktionsumfang aus. Entscheidend ist, dass Events **rein
376+
additiv** sind — anders als die Schichtungsbefunde werden sie durch Warten nicht
377+
teurer. Sobald ein Host-Projekt konkret einen Haken braucht, lassen sie sich
378+
ohne Bruch an bestehendem Code nachrüsten. Bis dahin wären es drei Klassen,
379+
eine neue Dispatcher-Abhängigkeit und zwei Tests für einen hypothetischen
380+
Konsumenten.
372381

373-
### B16 — Der Legacy-Layout-Pfad verletzt die eigenen Regeln
382+
Wieder aufgreifen, wenn ein Host-Projekt einen konkreten Anwendungsfall nennt.
374383

375-
`src/Controller/Page/QnaStageController.php:104-137` verbiegt zur Laufzeit
384+
### B16 — Der Legacy-Layout-Pfad verletzt die eigenen Regeln — **ZURÜCKGESTELLT**
385+
386+
`src/Controller/Page/QnaStageController.php:113-144` verbiegt zur Laufzeit
376387
`$GLOBALS['TL_HOOKS']['generatePage']` und legt die Renderargumente in
377388
`private ?array $legacyArguments` ab — **veränderlicher Zustand auf einem
378-
geteilten Service** (Zeile 32).
389+
geteilten Service** (Zeile 34).
379390

380-
Drei Probleme:
391+
Zwei Probleme:
381392

382393
1. `AGENTS.md` verbietet wörtlich das Eintragen von Hooks in
383394
`$GLOBALS['TL_HOOKS']`.
384395
2. Ein Subrequest, der eine zweite Bühnenseite rendert, überschreibt das Feld.
385396
Der Pfad ist nicht reentrant.
386-
3. Das veränderliche Feld ist der Grund, warum die Klasse als einzige nicht
387-
`final readonly` sein kann.
388397

389-
`FrontendIndex::renderPage()` ist laut `.docs/build/DECISIONS.md` in Contao 6
390-
ohnehin zur Entfernung vorgesehen.
398+
Ein drittes Problem stand hier ursprünglich — das Feld verhindere `final`. Das
399+
war falsch: Es verhindert nur `readonly`. `final` ist unabhängig davon möglich
400+
und wird in B17.4 erledigt.
401+
402+
#### Lösungsweg über `$GLOBALS['TL_PTY']` (recherchiert, nicht umgesetzt)
391403

392-
**Zielbild:** Unterstützung für `default`-Layouts entfällt, oder sie wird in
393-
einen `LegacyStageRenderer` ausgelagert, der seine Argumente explizit
394-
entgegennimmt, `@deprecated` markiert ist und zum nächsten Major verschwindet.
395-
Die Entscheidung wird in `DECISIONS.md` festgehalten.
404+
Der Legacy-Pfad lässt sich **ohne** Laufzeit-Hook bauen. Die Kette im Core:
396405

397-
Unabhängig davon baut `QnaStageController::getContent()` (Zeilen 147-180) die
398-
Übersichts-Arrays inline zusammen und dupliziert damit, wofür
399-
`QnaSessionListViewFactory` existiert.
406+
| Schritt | Beleg |
407+
| --- | --- |
408+
| `AbstractPageController::renderPage()` verzweigt per `match` auf `layout->type` | `vendor/contao/core-bundle/src/Controller/Page/AbstractPageController.php:50-54` |
409+
| `handleDefaultLayout()` (protected) delegiert an den Legacy-Renderer | ebenda, `:65-68` |
410+
| `FrontendIndex::renderLegacy()` löst den Handler auf | `vendor/contao/core-bundle/contao/controllers/FrontendIndex.php:45` |
411+
| `$GLOBALS['TL_PTY'][$objPage->type] ?? PageRegular::class`, dann `new $pageType()` | ebenda, `:64-67` |
412+
| Einsprungpunkt `PageRegular::createTemplate()` ist `protected`; `main` wird dort auf `''` gesetzt | `vendor/contao/core-bundle/contao/pages/PageRegular.php:383` bzw. `:515` |
413+
414+
`$GLOBALS['TL_PTY']` ist eine statische Registrierung zur Konfigurationszeit
415+
(Core: `contao/config/config.php:354`); dieses Bundle nutzt dieselbe Datei
416+
bereits für `$GLOBALS['BE_MOD']`. Eine `PageRegular`-Unterklasse, dort
417+
registriert, setzt den Inhalt am Ende eines überschriebenen `createTemplate()`
418+
weil die Bühnenseite mit `contentComposition: false` registriert ist, gibt es
419+
keine Artikelschleife, die `main` danach überschreibt. Damit entfallen beide
420+
Probleme: statische statt dynamischer Registrierung, und `new $pageType()`
421+
erzeugt eine Instanz pro Request.
422+
423+
Nebenbei würde `QnaStageController::executeRender()` überflüssig — die
424+
Verzweigung modern/default macht `AbstractPageController::renderPage()` selbst.
425+
426+
Einschränkung: Der Handler wird per `new` ohne DI erzeugt und müsste seine
427+
Abhängigkeiten über `System::getContainer()` ziehen.
428+
429+
#### Zurückgestellt am 15.09.2026
430+
431+
Drei Gründe:
432+
433+
1. **Der Reentranz-Fehler ist latent, nicht aktiv.** Er braucht einen Subrequest,
434+
der eine zweite Bühnenseite rendert. Die Bühne ist ein Seitentyp mit
435+
`contentComposition: false` — keine Artikel, keine Inhaltselemente —, und das
436+
Bundle bringt keinen Mechanismus mit, der eine Seite in eine Seite rendert.
437+
Innerhalb eines Requests stellt der `finally`-Block Hook und Feld korrekt
438+
wieder her.
439+
2. **Contao 6 erzwingt den Umbau ohnehin.** `FrontendIndex::renderPage()` ist
440+
seit 5.7 deprecated, `renderLegacy()` ist `@internal`, `PageRegular`
441+
verschwindet. Auch die `TL_PTY`-Variante wäre nur bis dahin haltbar. Der
442+
Aufwand fiele beim Contao-6-Wechsel erneut an, dann unter Randbedingungen,
443+
die heute niemand kennt.
444+
3. **Der Pfad wird gebraucht.** Ein Entfernen von `default`-Layouts (die
445+
ursprünglich erwogene Variante A) steht nicht zur Debatte.
446+
447+
Solange der Hook bleibt, trägt die Codestelle einen Kommentar, der auf diesen
448+
Abschnitt verweist — damit die Abweichung von `AGENTS.md` nicht beiläufig
449+
„repariert" oder als Präzedenzfall gelesen wird.
450+
451+
Wieder aufgreifen beim Contao-6-Wechsel, spätestens wenn der Legacy-Pfad
452+
tatsächlich bricht.
400453

401454
### B17 — Kleinere Befunde
402455

@@ -408,6 +461,7 @@ Unabhängig davon baut `QnaStageController::getContent()` (Zeilen 147-180) die
408461
| B17.4 | Die drei Contao-Controller sind nicht `final`, während alles andere `final readonly` ist. Nur beim Voter gibt es dafür einen dokumentierten Grund. |
409462
| B17.5 | `QuestionService::create()` überschreibt seinen eigenen `string`-Parameter `$question` (`src/Service/QuestionService.php:39`) und gibt am Ende ein `QnaQuestion` zurück. |
410463
| B17.6 | Alle Klassen tragen das Präfix `Qna`, obwohl der Namespace es bereits sagt (`HeimrichHannot\QnaBundle\Gateway\QnaSessionGateway`). Kosmetisch, wird nur im Zuge ohnehin verschobener Klassen bereinigt. |
464+
| B17.7 | `QnaStageController::getContent()` (Zeilen 149-182) baut die Übersichts-Arrays inline zusammen und dupliziert damit, wofür `QnaSessionListViewFactory` existiert. Stammt aus dem gestrichenen B16-Umfang und ist von der dortigen Zurückstellung unabhängig. |
411465

412466
### B18 — Template-Tests prüfen Quelltext statt Verhalten
413467

@@ -436,8 +490,13 @@ wörtliche Palettenstrings prüft.
436490
| 3 | `prompts/phase-3-gateways.md` | B4, B5, B6, B7 | ja |
437491
| 4 | `prompts/phase-4-view.md` | B8, B9, B10, B11 | ja |
438492
| 5 | `prompts/phase-5-tests-performance.md` | B12, B13, B14, B18 | nein (B13, B14) |
439-
| 6 | `prompts/phase-6-extensibility.md` | B15, B16 | nein |
440-
| 7 | `prompts/phase-7-cleanup.md` | B17 | ja |
493+
| ~~6~~ | ~~`prompts/phase-6-extensibility.md`~~ | ~~B15, B16~~ | **entfällt** |
494+
| 7 | `prompts/phase-7-cleanup.md` | B17.1 – B17.7 | ja |
495+
496+
**Phase 6 ist am 15.09.2026 gestrichen.** Beide Befunde sind zurückgestellt
497+
(Begründungen dort); der Prompt wurde gelöscht. Der einzige verbliebene Punkt
498+
aus ihrem Umfang — die Duplizierung in `getContent()` — ist als B17.7 nach
499+
Phase 7 gewandert. **Phase 7 ist damit die letzte Phase.**
441500

442501
Begründung der Reihenfolge:
443502

@@ -449,5 +508,4 @@ Begründung der Reihenfolge:
449508
* **4** — der große Umbau, bewusst nach 1-3, damit er kleiner ausfällt.
450509
* **5 nach 4** — die Integrationstests (B12) sind das Sicherheitsnetz für die
451510
Denormalisierung (B13) und die Cache-Änderung (B14).
452-
* **6 und 7** zuletzt, weil sie fachliche Entscheidungen bzw. reine Kosmetik
453-
sind.
511+
* **7 zuletzt**, weil es reine Kosmetik ist.

.docs/refactor/prompts/phase-4-view.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ Damit verschwinden die Konstruktor-Parameter `int $pollingInterval` aus
143143
* Keine Änderung an den Templates außer Umbenennungen, die aus Schritt 1
144144
zwingend folgen. Insbesondere keine Umstrukturierung der Includes.
145145
* Keine Cache-Header-Änderung. Das ist Phase 5 (B14).
146-
* Kein Legacy-Layout-Umbau. Das ist Phase 6 (B16). `QnaStageController` wird
146+
* Kein Legacy-Layout-Umbau (B16 — inzwischen zurückgestellt). `QnaStageController` wird
147147
hier nur insoweit angefasst, wie B10 und B11 es erzwingen.
148148

149149
## Akzeptanzkriterien

.docs/refactor/prompts/phase-6-extensibility.md

Lines changed: 0 additions & 187 deletions
This file was deleted.

0 commit comments

Comments
 (0)