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));
public function softDelete(int $id): void{ $this->executor->execute( "UPDATE comments SET status = 'deleted', body = '[deleted]' WHERE id = ?", [$id], );}
スレッド化コメント
深さ制限とソフトデリートを持つ自己参照コメントスレッドを実装します。
概要
スレッド化コメントシステムは自分自身を参照する 1 つのテーブルを持ちます。各コメントは
parent_id(トップレベルの場合は null)、depth(0 ベース)、statusを知っています。返信はレスポンスツリーで親の中にネストされます。データベーススキーマ
2
3
4
5
6
7
8
9
10
11
12
depthは各挿入時の再帰的な祖先クエリを避けるために行に非正規化されます。最大深さ
書き込み時に深さ制限を強制してください:
2
3
4
5
6
ルートハンドラーで挿入前にチェックしてください:
2
3
4
5
6
7
ソフトデリート
ソフトデリートはボディを
[deleted]に置換し、status = 'deleted'を設定します。子コメントは保持されます:2
3
4
5
6
7
ツリーの取得は
[deleted]ボディを持つ削除済みコメントを返すため、スレッド構造は一貫性を保ちます — 読者は削除されたコメントがあった場所にプレースホルダーを見て、その子コメントは引き続き表示されます。削除済みコメントへの返信を試みると 409 が返されます:
2
3
N+1 なしでコメントツリーを構築する
単一クエリで投稿のすべてのコメントを ID でソートして読み込みます(親は常に子より低い ID を持ちます)。その後、2 回のパスで PHP でツリーを組み立てます:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
生の行と
int[]子 ID リストをComment値オブジェクトとは分離して保持することで、readonlyクラスを扱う際の PHPStan 型の混乱を避けられます。行データと値オブジェクトの分離
再帰的に readonly 値オブジェクトのツリーを組み立てる際、PHPStan はクリーンな型境界を必要とします。機能するパターン:
array<int, array<string, mixed>> $rowMapとarray<int, int[]> $childIdsを構築します。まだ値オブジェクトはありません。buildTree()はint[]ID と 2 つのマップのみを受け取り、再帰して完全に組み立てられた子配列を持つCommentオブジェクトをハイドレートします。これにより、同じ配列に
CommentオブジェクトとintID が混在するのを避けられ、PHPStan が絞り込めないユニオン型を防ぎます。ルートサマリー
POST/postsGET/posts/{id}POST/posts/{id}/commentsGET/posts/{id}/commentsPOST/comments/{id}/repliesDELETE/comments/{id}設計上の注意
depthは各挿入時の再帰的な祖先クエリを避けるために行に保存されます(非正規化)。ORDER BY id ASCはフラットリストを読み込む際に親が子より前に現れることを保証します。