perf(rating): fetch-join comment tree to kill N+1 in public list (H7)
findApprovedRootsByDoctor used a plain findBy, so Comment::toArray() lazy-loaded likes, replies and the author per comment (and recursively per reply). Hydrate in two fetch-join passes (roots + author + likes; then replies + their author + likes + one further reply level) — no per-comment lazy loads for a two-level thread. Regression: tests/Rating/CommentListNPlusOneTest (functional correctness — like counts, approved-only replies, author preserved). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -22,10 +22,40 @@ class CommentRepository extends ServiceEntityRepository
|
||||
/** @return Comment[] approved root comments (no parent) for a doctor */
|
||||
public function findApprovedRootsByDoctor(Doctor $doctor): array
|
||||
{
|
||||
return $this->findBy(
|
||||
['doctor' => $doctor, 'status' => Comment::STATUS_APPROVED, 'parent' => null],
|
||||
['createdAt' => 'DESC']
|
||||
);
|
||||
// Two fetch-join passes (not the product, to avoid a likes×replies
|
||||
// cartesian) hydrate everything toArray() touches, so serialization
|
||||
// triggers no per-comment lazy loads (N+1): pass 1 = roots + author +
|
||||
// likes, pass 2 = replies + their author + likes, merged into the same
|
||||
// managed root entities.
|
||||
$roots = $this->createQueryBuilder('c')
|
||||
->addSelect('u', 'l')
|
||||
->leftJoin('c.user', 'u')
|
||||
->leftJoin('c.likes', 'l')
|
||||
->where('c.doctor = :doctor')
|
||||
->andWhere('c.status = :status')
|
||||
->andWhere('c.parent IS NULL')
|
||||
->setParameter('doctor', $doctor)
|
||||
->setParameter('status', Comment::STATUS_APPROVED)
|
||||
->orderBy('c.createdAt', 'DESC')
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
if ($roots !== []) {
|
||||
// Also initialise each reply's own replies collection (empty in a
|
||||
// two-level thread) so reply->toArray() doesn't lazy-load it per row.
|
||||
$this->createQueryBuilder('c')
|
||||
->addSelect('r', 'ru', 'rl', 'rr')
|
||||
->leftJoin('c.replies', 'r')
|
||||
->leftJoin('r.user', 'ru')
|
||||
->leftJoin('r.likes', 'rl')
|
||||
->leftJoin('r.replies', 'rr')
|
||||
->where('c IN (:roots)')
|
||||
->setParameter('roots', $roots)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
|
||||
return $roots;
|
||||
}
|
||||
|
||||
/** @return Comment[] */
|
||||
|
||||
Reference in New Issue
Block a user