Skip to content
This repository was archived by the owner on Oct 15, 2025. It is now read-only.

Commit c043f96

Browse files
committed
refactor(point): Optimize query
Reduce queries from 27 to 9.
1 parent 1d1f45b commit c043f96

File tree

1 file changed

+67
-19
lines changed

1 file changed

+67
-19
lines changed

src/Service/PointCalculationService.php

Lines changed: 67 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@
88
use App\Entity\QuestionDifficulty;
99
use App\Entity\SolutionEvent;
1010
use App\Entity\SolutionEventStatus;
11+
use App\Entity\SolutionVideoEvent;
1112
use App\Entity\User;
1213
use App\Repository\HintOpenEventRepository;
13-
use App\Repository\QuestionRepository;
1414
use App\Repository\SolutionEventRepository;
1515
use App\Repository\SolutionVideoEventRepository;
16+
use Psr\Cache\InvalidArgumentException;
17+
use Symfony\Contracts\Cache\ItemInterface;
18+
use Symfony\Contracts\Cache\TagAwareCacheInterface;
1619

1720
final class PointCalculationService
1821
{
@@ -37,11 +40,14 @@ final class PointCalculationService
3740
public function __construct(
3841
private readonly SolutionEventRepository $solutionEventRepository,
3942
private readonly SolutionVideoEventRepository $solutionVideoEventRepository,
40-
private readonly QuestionRepository $questionRepository,
4143
private readonly HintOpenEventRepository $hintOpenEventRepository,
44+
private readonly TagAwareCacheInterface $cache,
4245
) {
4346
}
4447

48+
/**
49+
* @throws InvalidArgumentException
50+
*/
4551
public function calculate(User $user): int
4652
{
4753
return self::$base
@@ -76,40 +82,82 @@ protected function calculateSolutionQuestionPoints(User $user): int
7682
* Calculate the points if the user is the first solver of a question.
7783
*
7884
* 第一位解題成功者加10點。
85+
*
86+
* @throws InvalidArgumentException
7987
*/
8088
protected function calculateFirstSolutionPoints(User $user): int
8189
{
82-
$questions = $this->questionRepository->findAll();
83-
90+
// select the question this user has ever solved
91+
$qb = $this->solutionEventRepository->createQueryBuilder('e')
92+
->select('q')
93+
->from(Question::class, 'q')
94+
->where('e.question = q.id')
95+
->andWhere('e.status = :status')
96+
->andWhere('e.submitter = :submitter')
97+
->setParameter('status', SolutionEventStatus::Passed)
98+
->setParameter('submitter', $user);
99+
100+
/**
101+
* @var Question[] $questions
102+
*/
103+
$questions = $qb->getQuery()->getResult();
104+
105+
// check if the user is the first solver of each question
84106
$points = 0;
85107

86-
// list the first solver of each question
87108
foreach ($questions as $question) {
88-
$solutionEvent = $question
89-
->getSolutionEvents()
90-
->findFirst(fn ($_, SolutionEvent $event) => SolutionEventStatus::Passed === $event->getStatus());
91-
92-
if (!$solutionEvent || $solutionEvent->getSubmitter() !== $user) {
93-
continue;
109+
$firstSolver = $this->listFirstSolversOfQuestion($question);
110+
if ($firstSolver && $firstSolver === $user->getId()) {
111+
$points += self::$firstSolverPoint;
94112
}
95-
96-
$points += self::$firstSolverPoint;
97113
}
98114

99115
return $points;
100116
}
101117

118+
/**
119+
* List and cache the first solvers of each question.
120+
*
121+
* @param Question $question the question to get the first solver
122+
*
123+
* @returns int|null the first solver ID of the question
124+
*
125+
* @throws InvalidArgumentException
126+
*/
127+
protected function listFirstSolversOfQuestion(Question $question): ?int
128+
{
129+
return $this->cache->get(
130+
"question.q{$question->getId()}.first-solver",
131+
function (ItemInterface $item) use ($question) {
132+
$item->tag(['question', 'first-solver']);
133+
134+
$solutionEvent = $question
135+
->getSolutionEvents()
136+
->findFirst(fn ($_, SolutionEvent $event) => SolutionEventStatus::Passed === $event->getStatus());
137+
138+
return $solutionEvent?->getSubmitter()?->getId();
139+
}
140+
);
141+
}
142+
102143
protected function calculateSolutionVideoPoints(User $user): int
103144
{
104-
$solutionVideoEvents = $this->solutionVideoEventRepository->findByUser($user);
145+
/**
146+
* @var Question[] $questions
147+
*/
148+
$questions = $this->solutionVideoEventRepository->createQueryBuilder('sve')
149+
->from(Question::class, 'q')
150+
->select('q')
151+
->where('sve.opener = :user')
152+
->andWhere('sve.question = q.id')
153+
->groupBy('q.id')
154+
->setParameter('user', $user)
155+
->getQuery()
156+
->getResult();
105157

106158
$questionPointsPair = [];
107-
foreach ($solutionVideoEvents as $event) {
108-
$question = $event->getQuestion();
109-
if (!$question) {
110-
continue;
111-
}
112159

160+
foreach ($questions as $question) {
113161
$questionPointsPair[$question->getId()] = match ($question->getDifficulty()) {
114162
QuestionDifficulty::Easy => self::$solutionVideoEventEasy,
115163
QuestionDifficulty::Medium => self::$solutionVideoEventMedium,

0 commit comments

Comments
 (0)