Skip to content

Commit 787889a

Browse files
committed
PaginatedResult
1 parent 97ad3ba commit 787889a

24 files changed

Lines changed: 275 additions & 81 deletions

src/Domain/Analytics/Service/AnalyticsService.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public function __construct(
5555
*/
5656
public function getCampaignStatistics(int $limit = 50, int $lastId = 0): array
5757
{
58-
$messages = $this->messageRepository->getFilteredAfterId($lastId, $limit);
58+
$messages = $this->messageRepository->getFilteredAfterId($lastId, $limit)->items;
5959

6060
$campaignStats = [];
6161

@@ -113,11 +113,11 @@ public function getCampaignStatistics(int $limit = 50, int $lastId = 0): array
113113
*/
114114
public function getViewOpensStatistics(int $limit = 50, int $lastId = 0): array
115115
{
116-
$messages = $this->messageRepository->getFilteredAfterId($lastId, $limit);
116+
$messagesResult = $this->messageRepository->getFilteredAfterId($lastId, $limit);
117117

118118
$viewStats = [];
119119

120-
foreach ($messages as $message) {
120+
foreach ($messagesResult->items as $message) {
121121
$views = $this->userMessageViewManager->countViewsByMessageId($message->getId());
122122
$sentCount = $message->getMetadata()->getBounceCount() + $views;
123123

@@ -134,9 +134,9 @@ public function getViewOpensStatistics(int $limit = 50, int $lastId = 0): array
134134

135135
return [
136136
'campaigns' => $viewStats,
137-
'total' => count($viewStats),
138-
'hasMore' => count($messages) === $limit,
139-
'lastId' => count($messages) > 0 ? $messages[count($messages) - 1]->getId() : $lastId,
137+
'total' => $messagesResult->total,
138+
'hasMore' => count($messagesResult->items) === $limit,
139+
'lastId' => $messagesResult->lastId,
140140
];
141141
}
142142

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpList\Core\Domain\Common\Model;
6+
7+
use PhpList\Core\Domain\Common\Model\Interfaces\DomainModel;
8+
9+
/**
10+
* @template T of DomainModel
11+
*/
12+
class PaginatedResult
13+
{
14+
/**
15+
* @param list<T> $items
16+
*/
17+
public function __construct(
18+
public readonly array $items,
19+
public readonly int $total,
20+
public readonly int $limit,
21+
public readonly int $lastId,
22+
) {}
23+
}

src/Domain/Common/Repository/CursorPaginationTrait.php

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,50 @@
66

77
use BadMethodCallException;
88
use PhpList\Core\Domain\Common\Model\Filter\FilterRequestInterface;
9-
use PhpList\Core\Domain\Common\Model\Interfaces\DomainModel;
9+
use PhpList\Core\Domain\Common\Model\PaginatedResult;
1010

1111
trait CursorPaginationTrait
1212
{
1313
/**
1414
* @param int $lastId Last seen ID
1515
* @param int $limit Max results
16-
* @return array
1716
*/
18-
public function getAfterId(int $lastId, int $limit): array
17+
public function getAfterId(int $lastId, int $limit): PaginatedResult
1918
{
20-
return $this->createQueryBuilder('e')
19+
$queryBuilder = $this->createQueryBuilder('e');
20+
21+
$countQb = clone $queryBuilder;
22+
$total = (int) $countQb
23+
->select('COUNT(DISTINCT e.id)')
24+
->getQuery()
25+
->getSingleScalarResult();
26+
27+
$items = $queryBuilder
2128
->andWhere('e.id > :lastId')
2229
->setParameter('lastId', $lastId)
2330
->orderBy('e.id', 'ASC')
2431
->setMaxResults($limit)
2532
->getQuery()
2633
->getResult();
34+
35+
return new PaginatedResult(
36+
items: $items,
37+
total: $total,
38+
limit: $limit,
39+
lastId: $lastId,
40+
);
2741
}
2842

2943
/**
3044
* Get filtered + paginated messages for a given owner and status.
3145
*
32-
* @return DomainModel[]
3346
* @throws BadMethodCallException
3447
* */
35-
public function getFilteredAfterId(int $lastId, int $limit, ?FilterRequestInterface $filter = null): array
36-
{
48+
public function getFilteredAfterId(
49+
int $lastId,
50+
int $limit,
51+
?FilterRequestInterface $filter = null
52+
): PaginatedResult {
3753
if ($filter === null) {
3854
return $this->getAfterId($lastId, $limit);
3955
}

src/Domain/Common/Repository/Interfaces/PaginatableRepositoryInterface.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@
55
namespace PhpList\Core\Domain\Common\Repository\Interfaces;
66

77
use PhpList\Core\Domain\Common\Model\Filter\FilterRequestInterface;
8+
use PhpList\Core\Domain\Common\Model\PaginatedResult;
89

910
interface PaginatableRepositoryInterface
1011
{
11-
public function getFilteredAfterId(int $lastId, int $limit, ?FilterRequestInterface $filter = null): array;
12-
public function count(): int;
12+
public function getFilteredAfterId(
13+
int $lastId,
14+
int $limit,
15+
?FilterRequestInterface $filter = null
16+
): PaginatedResult;
1317
}

src/Domain/Configuration/Repository/EventLogRepository.php

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use InvalidArgumentException;
88
use PhpList\Core\Domain\Common\Model\Filter\FilterRequestInterface;
9+
use PhpList\Core\Domain\Common\Model\PaginatedResult;
910
use PhpList\Core\Domain\Common\Repository\AbstractRepository;
1011
use PhpList\Core\Domain\Common\Repository\CursorPaginationTrait;
1112
use PhpList\Core\Domain\Common\Repository\Interfaces\PaginatableRepositoryInterface;
@@ -17,16 +18,15 @@ class EventLogRepository extends AbstractRepository implements PaginatableReposi
1718
use CursorPaginationTrait;
1819

1920
/**
20-
* @return EventLog[]
21+
* @return PaginatedResult<EventLog>
2122
* @throws InvalidArgumentException
2223
*/
23-
public function getFilteredAfterId(int $lastId, int $limit, ?FilterRequestInterface $filter = null): array
24-
{
25-
$queryBuilder = $this->createQueryBuilder('e')
26-
->andWhere('e.id > :lastId')
27-
->setParameter('lastId', $lastId)
28-
->orderBy('e.id', 'ASC')
29-
->setMaxResults($limit);
24+
public function getFilteredAfterId(
25+
int $lastId,
26+
int $limit,
27+
?FilterRequestInterface $filter = null
28+
): PaginatedResult {
29+
$queryBuilder = $this->createQueryBuilder('e');
3030

3131
if ($filter === null) {
3232
return $queryBuilder->getQuery()->getResult();
@@ -46,6 +46,26 @@ public function getFilteredAfterId(int $lastId, int $limit, ?FilterRequestInterf
4646
$queryBuilder->andWhere('e.entered <= :dateTo')->setParameter('dateTo', $filter->getDateTo());
4747
}
4848

49-
return $queryBuilder->getQuery()->getResult();
49+
$countQb = clone $queryBuilder;
50+
$total = (int) $countQb
51+
->select('COUNT(DISTINCT e.id)')
52+
->getQuery()
53+
->getSingleScalarResult();
54+
55+
/** @var list<EventLog> $items */
56+
$items = $queryBuilder
57+
->andWhere('e.id > :lastId')
58+
->setParameter('lastId', $lastId)
59+
->orderBy('e.id', 'ASC')
60+
->setMaxResults($limit)
61+
->getQuery()
62+
->getResult();
63+
64+
return new PaginatedResult(
65+
items: $items,
66+
total: $total,
67+
limit: $limit,
68+
lastId: $lastId,
69+
);
5070
}
5171
}

src/Domain/Configuration/Service/Manager/EventLogManager.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public function get(
4545
): array {
4646
$filter = new EventLogFilter($page, $dateFrom, $dateTo);
4747

48-
return $this->repository->getFilteredAfterId($lastId, $limit, $filter);
48+
return $this->repository->getFilteredAfterId($lastId, $limit, $filter)->items;
4949
}
5050

5151
public function delete(EventLog $log): void

src/Domain/Identity/Repository/AdminAttributeValueRepository.php

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66

77
use InvalidArgumentException;
88
use PhpList\Core\Domain\Common\Model\Filter\FilterRequestInterface;
9+
use PhpList\Core\Domain\Common\Model\PaginatedResult;
910
use PhpList\Core\Domain\Common\Repository\AbstractRepository;
1011
use PhpList\Core\Domain\Common\Repository\Interfaces\PaginatableRepositoryInterface;
1112
use PhpList\Core\Domain\Identity\Model\AdminAttributeValue;
1213
use PhpList\Core\Domain\Identity\Model\Filter\AdminAttributeValueFilter;
14+
use PhpList\Core\Domain\Subscription\Model\SubscriberAttributeValue;
1315

1416
class AdminAttributeValueRepository extends AbstractRepository implements PaginatableRepositoryInterface
1517
{
@@ -26,29 +28,47 @@ public function findOneByAdminIdAndAttributeId(int $adminId, int $definitionId):
2628
->getOneOrNullResult();
2729
}
2830

29-
3031
/**
31-
* @return AdminAttributeValue[]
32+
* @return PaginatedResult<SubscriberAttributeValue>
3233
* @throws InvalidArgumentException
3334
*/
34-
public function getFilteredAfterId(int $lastId, int $limit, ?FilterRequestInterface $filter = null): array
35-
{
35+
public function getFilteredAfterId(
36+
int $lastId,
37+
int $limit,
38+
?FilterRequestInterface $filter = null
39+
): PaginatedResult {
3640
if (!$filter instanceof AdminAttributeValueFilter) {
3741
throw new InvalidArgumentException('Expected AdminAttributeValueFilter.');
3842
}
39-
$query = $this->createQueryBuilder('aav')
43+
$queryBuilder = $this->createQueryBuilder('aav')
4044
->join('aav.administrator', 'a')
41-
->join('aav.attributeDefinition', 'ad')
42-
->where('ad.id > :lastId')
43-
->setParameter('lastId', $lastId);
45+
->join('aav.attributeDefinition', 'ad');
4446

4547
if ($filter->getAdminId() !== null) {
46-
$query->andWhere('a.id = :adminId')
48+
$queryBuilder->andWhere('a.id = :adminId')
4749
->setParameter('adminId', $filter->getAdminId());
4850
}
49-
return $query->orderBy('ad.id', 'ASC')
51+
52+
$countQb = clone $queryBuilder;
53+
$total = (int) $countQb
54+
->select('COUNT(DISTINCT aav.id)')
55+
->getQuery()
56+
->getSingleScalarResult();
57+
58+
/** @var list<SubscriberAttributeValue> $items */
59+
$items = $queryBuilder
60+
->andWhere('aav.id > :lastId')
61+
->setParameter('lastId', $lastId)
62+
->orderBy('aav.id', 'ASC')
5063
->setMaxResults($limit)
5164
->getQuery()
5265
->getResult();
66+
67+
return new PaginatedResult(
68+
items: $items,
69+
total: $total,
70+
limit: $limit,
71+
lastId: $lastId,
72+
);
5373
}
5474
}

src/Domain/Identity/Service/Manager/AdminAttributeDefinitionManager.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace PhpList\Core\Domain\Identity\Service\Manager;
66

7+
use PhpList\Core\Domain\Common\Model\PaginatedResult;
78
use PhpList\Core\Domain\Identity\Exception\AttributeDefinitionCreationException;
89
use PhpList\Core\Domain\Identity\Model\AdminAttributeDefinition;
910
use PhpList\Core\Domain\Identity\Model\Dto\AdminAttributeDefinitionDto;
@@ -79,7 +80,7 @@ public function getTotalCount(): int
7980
return $this->definitionRepository->count();
8081
}
8182

82-
public function getAttributesAfterId(int $afterId, int $limit): array
83+
public function getAttributesAfterId(int $afterId, int $limit): PaginatedResult
8384
{
8485
return $this->definitionRepository->getAfterId($afterId, $limit);
8586
}

src/Domain/Messaging/Repository/MessageRepository.php

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use DateTimeImmutable;
88
use Doctrine\ORM\AbstractQuery;
99
use PhpList\Core\Domain\Common\Model\Filter\FilterRequestInterface;
10+
use PhpList\Core\Domain\Common\Model\PaginatedResult;
1011
use PhpList\Core\Domain\Common\Repository\AbstractRepository;
1112
use PhpList\Core\Domain\Common\Repository\Interfaces\PaginatableRepositoryInterface;
1213
use PhpList\Core\Domain\Messaging\Model\Filter\MessageFilter;
@@ -46,22 +47,40 @@ public function findById(int $id): ?Message
4647
->getOneOrNullResult();
4748
}
4849

49-
/** @return Message[] */
50-
public function getFilteredAfterId(int $lastId, int $limit, ?FilterRequestInterface $filter = null): array
51-
{
50+
/** @return PaginatedResult<Message> */
51+
public function getFilteredAfterId(
52+
int $lastId,
53+
int $limit,
54+
?FilterRequestInterface $filter = null
55+
): PaginatedResult {
5256
$queryBuilder = $this->createQueryBuilder('m');
5357

5458
if ($filter instanceof MessageFilter && $filter->getOwner() !== null) {
5559
$queryBuilder->andWhere('IDENTITY(m.owner) = :ownerId')
5660
->setParameter('ownerId', $filter->getOwner()->getId());
5761
}
5862

59-
return $queryBuilder->andWhere('m.id > :lastId')
63+
$countQb = clone $queryBuilder;
64+
$total = (int) $countQb
65+
->select('COUNT(DISTINCT m.id)')
66+
->getQuery()
67+
->getSingleScalarResult();
68+
69+
/** @var list<Message> $items */
70+
$items = $queryBuilder
71+
->andWhere('m.id > :lastId')
6072
->setParameter('lastId', $lastId)
6173
->orderBy('m.id', 'ASC')
6274
->setMaxResults($limit)
6375
->getQuery()
6476
->getResult();
77+
78+
return new PaginatedResult(
79+
items: $items,
80+
total: $total,
81+
limit: $limit,
82+
lastId: $lastId,
83+
);
6584
}
6685

6786
/** @return Message[] */

src/Domain/Subscription/Repository/SubscriberAttributeDefinitionRepository.php

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace PhpList\Core\Domain\Subscription\Repository;
66

7+
use PhpList\Core\Domain\Common\Model\PaginatedResult;
78
use PhpList\Core\Domain\Common\Repository\AbstractRepository;
89
use PhpList\Core\Domain\Common\Repository\CursorPaginationTrait;
910
use PhpList\Core\Domain\Common\Repository\Interfaces\PaginatableRepositoryInterface;
@@ -47,17 +48,31 @@ private function hydrateOptions(SubscriberAttributeDefinition $def): void
4748
}
4849
}
4950

50-
public function getAfterId(int $lastId, int $limit): array
51+
/** @return PaginatedResult<SubscriberAttributeDefinition>*/
52+
public function getAfterId(int $lastId, int $limit): PaginatedResult
5153
{
52-
$result = $this->createQueryBuilder('e')
54+
$queryBuilder = $this->createQueryBuilder('e');
55+
56+
$countQb = clone $queryBuilder;
57+
$total = (int) $countQb
58+
->select('COUNT(DISTINCT e.id)')
59+
->getQuery()
60+
->getSingleScalarResult();
61+
62+
$items = $queryBuilder
5363
->andWhere('e.id > :lastId')
5464
->setParameter('lastId', $lastId)
5565
->orderBy('e.id', 'ASC')
5666
->setMaxResults($limit)
5767
->getQuery()
5868
->getResult();
5969

60-
return $this->hydrateOptionsForAll($result);
70+
return new PaginatedResult(
71+
items: $this->hydrateOptionsForAll($items),
72+
total: $total,
73+
limit: $limit,
74+
lastId: $lastId,
75+
);
6176
}
6277

6378
public function findOneByName(string $name): ?SubscriberAttributeDefinition

0 commit comments

Comments
 (0)