From 32874f142b35f58bc286f9c555664163c123ef9d Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Sat, 5 Oct 2024 22:54:47 +0800 Subject: [PATCH] fix(questions): Pagination --- src/Repository/QuestionRepository.php | 52 +++++++++---------- .../Questions/FilterableSection.php | 32 ++++-------- 2 files changed, 34 insertions(+), 50 deletions(-) diff --git a/src/Repository/QuestionRepository.php b/src/Repository/QuestionRepository.php index c06671a..491c56b 100644 --- a/src/Repository/QuestionRepository.php +++ b/src/Repository/QuestionRepository.php @@ -16,10 +16,12 @@ */ class QuestionRepository extends ServiceEntityRepository { - public static int $pageSize = 10; + public static int $pageSize = 12; - public function __construct(ManagerRegistry $registry) - { + public function __construct( + ManagerRegistry $registry, + private readonly SearchService $searchService, + ) { parent::__construct($registry, Question::class); } @@ -75,7 +77,7 @@ public function getPreviousPage(int $page): ?int * * @return Question[] The list of questions for the given page */ - public function search(SearchService $searchService, ?string $query, ?string $type, int $page, ?int $pageSize): array + public function search(?string $query, ?string $type, int $page, ?int $pageSize = null): array { $filters = []; if (null !== $type && '' !== $type) { @@ -83,9 +85,9 @@ public function search(SearchService $searchService, ?string $query, ?string $ty $filters[] = "type = \"$escapedType\""; } - return $searchService->search($this->getEntityManager(), Question::class, $query ?? '', [ + return $this->searchService->search($this->getEntityManager(), Question::class, $query ?? '', [ 'limit' => $pageSize ?? self::$pageSize, - 'page' => $page, + 'offset' => ($page - 1) * ($pageSize ?? self::$pageSize), 'filter' => $filters, 'sort' => ['id:asc'], ]); @@ -96,40 +98,34 @@ public function search(SearchService $searchService, ?string $query, ?string $ty */ public function reindex(SearchService $searchService): void { + $searchService->clear(Question::class); $searchService->index($this->getEntityManager(), $this->findAll()); } /** - * Calculate the total number of pages for a given query and page size. + * Count the total search results based on a query and type. * - * @param string|null $query The search query - * @param string|null $type The question type - * @param int|null $pageSize The number of items per page + * @param string|null $query The search query + * @param string|null $type The question type * - * @return int The total number of pages + * @return int The total result count */ - public function calculateTotalPages(?string $query, ?string $type, ?int $pageSize): int + public function countSearchResults(?string $query, ?string $type): int { - // FIXME: This is a naive implementation, it should be optimized - $pageSize ??= self::$pageSize; - - $qb = $this->createQueryBuilder('q'); - $qb = $qb->select('COUNT(q.id)'); - - if (null !== $query) { - $qb = $qb->andWhere('q.title LIKE :query') - ->setParameter('query', "%$query%"); + $filters = []; + if (null !== $type && '' !== $type) { + $escapedType = addslashes($type); + $filters[] = "type = \"$escapedType\""; } - if (null !== $type) { - $qb = $qb->andWhere('q.type = :type') - ->setParameter('type', $type); - } + $result = $this->searchService->rawSearch(Question::class, $query ?? '', [ + 'filter' => $filters, + 'attributesToRetrieve' => ['estimatedTotalHits'], + ]); - $questionsCount = $qb->getQuery()->getSingleScalarResult(); - \assert(\is_int($questionsCount), 'The questions count should be an integer.'); + \assert(isset($result['estimatedTotalHits']) && \is_int($result['estimatedTotalHits']), 'estimatedTotalHits should be set and must be a integer'); - return (int) ceil($questionsCount / $pageSize); + return $result['estimatedTotalHits']; } /** diff --git a/src/Twig/Components/Questions/FilterableSection.php b/src/Twig/Components/Questions/FilterableSection.php index 1c6b555..810b9f2 100644 --- a/src/Twig/Components/Questions/FilterableSection.php +++ b/src/Twig/Components/Questions/FilterableSection.php @@ -7,7 +7,6 @@ use App\Entity\Question; use App\Entity\User; use App\Repository\QuestionRepository; -use Meilisearch\Bundle\SearchService; use Symfony\UX\LiveComponent\Attribute\AsLiveComponent; use Symfony\UX\LiveComponent\Attribute\LiveAction; use Symfony\UX\LiveComponent\Attribute\LiveArg; @@ -20,8 +19,6 @@ final class FilterableSection { use DefaultActionTrait; - public readonly int $pageSize; - public string $title = '題庫一覽'; #[LiveProp] @@ -38,9 +35,7 @@ final class FilterableSection public function __construct( private readonly QuestionRepository $questionRepository, - private readonly SearchService $searchService, ) { - $this->pageSize = QuestionRepository::$pageSize; } /** @@ -51,11 +46,9 @@ public function __construct( public function getQuestions(): array { return $this->questionRepository->search( - $this->searchService, query: $this->query, type: $this->type, page: $this->getCurrentPage(), - pageSize: $this->pageSize, ); } @@ -69,18 +62,6 @@ public function getTypesList(): array return $this->questionRepository->listTypes(); } - /** - * Get the total number of pages. - */ - public function getTotalPages(): int - { - return $this->questionRepository->calculateTotalPages( - query: $this->query, - type: $this->type, - pageSize: $this->pageSize, - ); - } - /** * Get the current page number, clamped to the valid range. * @@ -88,7 +69,7 @@ public function getTotalPages(): int */ public function getCurrentPage(): int { - return max(min($this->currentPage, $this->getTotalPages()), 1); + return $this->currentPage; } /** @@ -98,7 +79,7 @@ public function getCurrentPage(): int */ public function setCurrentPage(int $page): void { - $this->currentPage = max(min($page, $this->getTotalPages()), 1); + $this->currentPage = max(1, $page); } /** @@ -106,7 +87,14 @@ public function setCurrentPage(int $page): void */ public function hasNext(): bool { - return $this->getCurrentPage() < $this->getTotalPages(); + $total = $this->questionRepository->countSearchResults( + query: $this->query, + type: $this->type, + ); + + $totalPage = ceil($total / QuestionRepository::$pageSize); + + return $this->getCurrentPage() < $totalPage; } /**