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:
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Rating;
|
||||
|
||||
use App\Doctor\Entity\Doctor;
|
||||
use App\Rating\Entity\Comment;
|
||||
use App\Rating\Entity\Like;
|
||||
use App\Tests\ApiTestCase;
|
||||
|
||||
/**
|
||||
* The public comment list fetch-joins likes/replies/authors (two passes) to
|
||||
* avoid lazy loading per comment (N+1). This guards the refactor's correctness:
|
||||
* the serialized tree — like counts, approved replies, author names — must be
|
||||
* unchanged.
|
||||
*/
|
||||
class CommentListNPlusOneTest extends ApiTestCase
|
||||
{
|
||||
public function testTreeIsSerializedCorrectly(): void
|
||||
{
|
||||
$doctor = new Doctor($this->createUser(['ROLE_DOCTOR']), 'دکتر تست');
|
||||
$this->em->persist($doctor);
|
||||
|
||||
$root = (new Comment($this->createUser(), $doctor, 'نظر ریشه'))->approve();
|
||||
$this->em->persist($root);
|
||||
// two likes + one dislike → like_count 2, dislike_count 1
|
||||
$this->em->persist(new Like($this->createUser(), $root, 1));
|
||||
$this->em->persist(new Like($this->createUser(), $root, 1));
|
||||
$this->em->persist(new Like($this->createUser(), $root, -1));
|
||||
|
||||
$reply = (new Comment($this->createUser(), $doctor, 'پاسخ تأییدشده', $root))->approve();
|
||||
$this->em->persist($reply);
|
||||
// a pending reply must NOT appear
|
||||
$this->em->persist(new Comment($this->createUser(), $doctor, 'پاسخ در انتظار', $root));
|
||||
$this->em->flush();
|
||||
|
||||
$this->em->clear();
|
||||
|
||||
$body = $this->jsonGet('/api/v1/comments/' . $doctor->getUuid());
|
||||
|
||||
$roots = $body['data']['data'];
|
||||
$this->assertCount(1, $roots);
|
||||
|
||||
$r = $roots[0];
|
||||
$this->assertSame('نظر ریشه', $r['comment']);
|
||||
$this->assertSame(2, $r['like_status']['like_count']);
|
||||
$this->assertSame(1, $r['like_status']['dislike_count']);
|
||||
$this->assertCount(1, $r['replies']); // only the approved reply
|
||||
$this->assertSame('پاسخ تأییدشده', $r['replies'][0]['comment']);
|
||||
}
|
||||
|
||||
private function jsonGet(string $uri): array
|
||||
{
|
||||
$this->client->request('GET', $uri);
|
||||
|
||||
return json_decode($this->client->getResponse()->getContent(), true) ?? [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user