You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
`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.
368
372
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.
372
381
373
-
### B16 — Der Legacy-Layout-Pfad verletzt die eigenen Regeln
382
+
Wieder aufgreifen, wenn ein Host-Projekt einen konkreten Anwendungsfall nennt.
374
383
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
376
387
`$GLOBALS['TL_HOOKS']['generatePage']` und legt die Renderargumente in
377
388
`private ?array $legacyArguments` ab — **veränderlicher Zustand auf einem
378
-
geteilten Service** (Zeile 32).
389
+
geteilten Service** (Zeile 34).
379
390
380
-
Drei Probleme:
391
+
Zwei Probleme:
381
392
382
393
1.`AGENTS.md` verbietet wörtlich das Eintragen von Hooks in
383
394
`$GLOBALS['TL_HOOKS']`.
384
395
2. Ein Subrequest, der eine zweite Bühnenseite rendert, überschreibt das Feld.
385
396
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.
388
397
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)
391
403
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:
396
405
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
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.
400
453
401
454
### B17 — Kleinere Befunde
402
455
@@ -408,6 +461,7 @@ Unabhängig davon baut `QnaStageController::getContent()` (Zeilen 147-180) die
408
461
| 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. |
409
462
| B17.5 |`QuestionService::create()` überschreibt seinen eigenen `string`-Parameter `$question` (`src/Service/QuestionService.php:39`) und gibt am Ende ein `QnaQuestion` zurück. |
410
463
| 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. |
0 commit comments