From df0fec0272501e3fe73dba880de406c3ebe4c7a8 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Mon, 15 Jun 2026 11:21:05 +0200 Subject: [PATCH] =?UTF-8?q?fix(notifications)=20:=20pagination=20r=C3=A9el?= =?UTF-8?q?le=20c=C3=B4t=C3=A9=20provider=20pour=20ne=20plus=20tronquer=20?= =?UTF-8?q?=C3=A0=2030=20(LST-52)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NotificationProvider retournait findBy(..., 30) : limite codée en dur, paramètre page ignoré et tableau brut (pas un Paginator). hydra:totalItems valait donc 30 → fetchAllHydra s'arrêtait à la 1re page et les notifications restaient tronquées à 30 malgré le correctif front. - NotificationProvider : vraie pagination Doctrine (Pagination + DoctrinePaginator + TraversablePaginator), totalItems réel et hydra:view.next exposés - NotificationRepository : createUserNotificationsQueryBuilder (filtre user + tri) - fetchAllHydra : ne retronque plus silencieusement quand hydra:totalItems est absent, pagine jusqu'à une page non pleine --- frontend/utils/api.ts | 18 +++++++++++++-- src/Repository/NotificationRepository.php | 11 +++++++++ src/State/NotificationProvider.php | 28 +++++++++++++++++++---- 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/frontend/utils/api.ts b/frontend/utils/api.ts index 6e673ec..4384a23 100644 --- a/frontend/utils/api.ts +++ b/frontend/utils/api.ts @@ -33,13 +33,27 @@ export async function fetchAllHydra( const first = await fetchPage(1) const all = extractHydraMembers(first) const total = extractHydraTotal(first) + const pageSize = all.length - if (total === undefined) { + // 1ʳᵉ page vide → collection vide, rien de plus à récupérer. + if (pageSize === 0) { return all } let page = 2 - while (all.length < total && page <= maxPages) { + while (page <= maxPages) { + if (total !== undefined) { + // Total connu : on s'arrête dès qu'on a tout récupéré. + if (all.length >= total) { + break + } + } else if (all.length % pageSize !== 0) { + // Total inconnu (provider custom sans `hydra:totalItems`) : la dernière + // page récupérée n'était pas pleine → fin de collection. On ne s'arrête + // pas en silence sur la 1ʳᵉ page, contrairement à `extractHydraMembers`. + break + } + const next = await fetchPage(page) const members = extractHydraMembers(next) if (members.length === 0) { diff --git a/src/Repository/NotificationRepository.php b/src/Repository/NotificationRepository.php index 76d642d..7dba9a8 100644 --- a/src/Repository/NotificationRepository.php +++ b/src/Repository/NotificationRepository.php @@ -7,7 +7,9 @@ namespace App\Repository; use App\Entity\Notification; use App\Entity\User; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ManagerRegistry; +use Symfony\Component\Security\Core\User\UserInterface; /** * @extends ServiceEntityRepository @@ -19,6 +21,15 @@ class NotificationRepository extends ServiceEntityRepository parent::__construct($registry, Notification::class); } + public function createUserNotificationsQueryBuilder(UserInterface $user): QueryBuilder + { + return $this->createQueryBuilder('n') + ->where('n.user = :user') + ->setParameter('user', $user) + ->orderBy('n.createdAt', 'DESC') + ; + } + public function countUnreadByUser(User $user): int { return (int) $this->createQueryBuilder('n') diff --git a/src/State/NotificationProvider.php b/src/State/NotificationProvider.php index 0a1b8b9..ff872c9 100644 --- a/src/State/NotificationProvider.php +++ b/src/State/NotificationProvider.php @@ -5,11 +5,17 @@ declare(strict_types=1); namespace App\State; use ApiPlatform\Metadata\Operation; +use ApiPlatform\State\Pagination\Pagination; +use ApiPlatform\State\Pagination\TraversablePaginator; use ApiPlatform\State\ProviderInterface; use App\Entity\Notification; use App\Repository\NotificationRepository; +use ArrayIterator; +use Doctrine\ORM\Tools\Pagination\Paginator as DoctrinePaginator; use Symfony\Bundle\SecurityBundle\Security; +use function count; + /** * @implements ProviderInterface */ @@ -18,16 +24,28 @@ final readonly class NotificationProvider implements ProviderInterface public function __construct( private Security $security, private NotificationRepository $notificationRepository, + private Pagination $pagination, ) {} - public function provide(Operation $operation, array $uriVariables = [], array $context = []): array|object + public function provide(Operation $operation, array $uriVariables = [], array $context = []): object { $user = $this->security->getUser(); - return $this->notificationRepository->findBy( - ['user' => $user], - ['createdAt' => 'DESC'], - 30, + [$page, $offset, $limit] = $this->pagination->getPagination($operation, $context); + + $queryBuilder = $this->notificationRepository + ->createUserNotificationsQueryBuilder($user) + ->setFirstResult($offset) + ->setMaxResults($limit) + ; + + $doctrinePaginator = new DoctrinePaginator($queryBuilder); + + return new TraversablePaginator( + new ArrayIterator(iterator_to_array($doctrinePaginator, false)), + $page, + $limit, + (float) count($doctrinePaginator), ); } }