Ein Threaded-Comment-System hat eine Tabelle, die auf sich selbst verweist. Jeder Kommentar kennt seine parent_id (null für Top-Level), seine depth (0-basiert) und seinen status. Antworten werden in der Antwortstruktur innerhalb ihres Elternkommentars verschachtelt.
CREATE TABLE comments ( id INTEGER PRIMARY KEY AUTOINCREMENT, post_id INTEGER NOT NULL, parent_id INTEGER, author_name TEXT NOT NULL, body TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'published', depth INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL, FOREIGN KEY (post_id) REFERENCES posts(id), FOREIGN KEY (parent_id) REFERENCES comments(id));
1 2 3 4 5 6 7 8 9 10 11 12
depth ist in der Zeile denormalisiert, um rekursive Vorfahren-Abfragen bei jedem Insert zu vermeiden.
Soft Delete ersetzt den Body durch [deleted] und setzt status = 'deleted'. Kindkommentare bleiben erhalten:
php
public function softDelete(int $id): void{ $this->executor->execute( "UPDATE comments SET status = 'deleted', body = '[deleted]' WHERE id = ?", [$id], );}
1 2 3 4 5 6 7
Der Baumlade-Vorgang gibt gelöschte Kommentare mit [deleted]-Bodies zurück, damit die Thread-Struktur kohärent bleibt — Leser sehen einen Platzhalter, wo der gelöschte Kommentar war, und seine Kindkommentare sind noch sichtbar.
Ein Versuch, auf einen gelöschten Kommentar zu antworten, gibt 409 zurück:
php
if ($parent->isDeleted()) { return $this->problems->create($request, 'conflict', 'Cannot reply to a deleted comment.', 409, '');}
Alle Kommentare für einen Beitrag in einer einzelnen Abfrage laden, geordnet nach ID (Eltern haben immer niedrigere IDs als ihre Kinder). Dann den Baum in PHP mit zwei Durchläufen zusammenbauen:
Wenn man einen Baum von readonly Value-Objekten rekursiv zusammenbaut, braucht PHPStan klare Typgrenzen. Das funktionierende Muster:
Durchlauf 1 — array<int, array<string, mixed>> $rowMap und array<int, int[]> $childIds aus Rohzeilen aufbauen. Noch keine Value-Objekte.
Durchlauf 2 — buildTree() nimmt nur int[]-IDs und die zwei Maps, rekursiert und hydratisiert Comment-Objekte mit vollständig zusammengebauten Kinder-Arrays.
Dies vermeidet das Mischen von Comment-Objekten und int-IDs im selben Array, was einen Union-Typ erzeugen würde, den PHPStan nicht einengen kann.
Threaded Comments
Selbstreferenzierende Kommentar-Threads mit Tiefenlimits und Soft Delete implementieren.
Übersicht
Ein Threaded-Comment-System hat eine Tabelle, die auf sich selbst verweist. Jeder Kommentar kennt seine
parent_id(null für Top-Level), seinedepth(0-basiert) und seinenstatus. Antworten werden in der Antwortstruktur innerhalb ihres Elternkommentars verschachtelt.Datenbankschema
2
3
4
5
6
7
8
9
10
11
12
depthist in der Zeile denormalisiert, um rekursive Vorfahren-Abfragen bei jedem Insert zu vermeiden.Maximale Tiefe
Ein Tiefenlimit beim Schreiben durchsetzen:
2
3
4
5
6
Im Routen-Handler vor dem Einfügen prüfen:
2
3
4
5
6
7
Soft Delete
Soft Delete ersetzt den Body durch
[deleted]und setztstatus = 'deleted'. Kindkommentare bleiben erhalten:2
3
4
5
6
7
Der Baumlade-Vorgang gibt gelöschte Kommentare mit
[deleted]-Bodies zurück, damit die Thread-Struktur kohärent bleibt — Leser sehen einen Platzhalter, wo der gelöschte Kommentar war, und seine Kindkommentare sind noch sichtbar.Ein Versuch, auf einen gelöschten Kommentar zu antworten, gibt 409 zurück:
2
3
Kommentarbaum ohne N+1 aufbauen
Alle Kommentare für einen Beitrag in einer einzelnen Abfrage laden, geordnet nach ID (Eltern haben immer niedrigere IDs als ihre Kinder). Dann den Baum in PHP mit zwei Durchläufen zusammenbauen:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Rohzeilen und
int[]-Kinder-ID-Listen vonComment-Value-Objekten zu trennen vermeidet PHPStan-Typverwirrung beim Arbeiten mitreadonly-Klassen.Zeilendaten von Value-Objekten trennen
Wenn man einen Baum von readonly Value-Objekten rekursiv zusammenbaut, braucht PHPStan klare Typgrenzen. Das funktionierende Muster:
array<int, array<string, mixed>> $rowMapundarray<int, int[]> $childIdsaus Rohzeilen aufbauen. Noch keine Value-Objekte.buildTree()nimmt nurint[]-IDs und die zwei Maps, rekursiert und hydratisiertComment-Objekte mit vollständig zusammengebauten Kinder-Arrays.Dies vermeidet das Mischen von
Comment-Objekten undint-IDs im selben Array, was einen Union-Typ erzeugen würde, den PHPStan nicht einengen kann.Routen-Zusammenfassung
POST/postsGET/posts/{id}POST/posts/{id}/commentsGET/posts/{id}/commentsPOST/comments/{id}/repliesDELETE/comments/{id}Design-Hinweise
depthist in der Zeile gespeichert (denormalisiert), um rekursive Vorfahren-Abfragen bei jedem Insert zu vermeiden.ORDER BY id ASCgarantiert, dass Eltern vor ihren Kindern beim Laden der flachen Liste erscheinen.