Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
92.31% covered (success)
92.31%
60 / 65
66.67% covered (warning)
66.67%
4 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
DoctrineEntityList
92.31% covered (success)
92.31%
60 / 65
66.67% covered (warning)
66.67%
4 / 6
9.04
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getIterator
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 createNativeQuery
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
2.00
 toPaginatedResult
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
2
 getTotalCount
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
1
 getFilteredCount
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2namespace Apie\DoctrineEntityDatalayer\Lists;
3
4use Apie\Core\BoundedContext\BoundedContextId;
5use Apie\Core\Context\ApieContext;
6use Apie\Core\Datalayers\Lists\EntityListInterface;
7use Apie\Core\Datalayers\Lists\PaginatedResult;
8use Apie\Core\Datalayers\Search\QuerySearch;
9use Apie\Core\Datalayers\ValueObjects\LazyLoadedListIdentifier;
10use Apie\Core\Entities\EntityInterface;
11use Apie\Core\Lists\ItemList;
12use Apie\DoctrineEntityDatalayer\DoctrineUtils;
13use Apie\DoctrineEntityDatalayer\Factories\EntityQueryFactory;
14use Apie\DoctrineEntityDatalayer\OrmBuilder;
15use Apie\StorageMetadata\DomainToStorageConverter;
16use Apie\StorageMetadata\Interfaces\StorageDtoInterface;
17use Doctrine\ORM\AbstractQuery;
18use Doctrine\ORM\NativeQuery;
19use Doctrine\ORM\Query\ResultSetMapping;
20use Doctrine\ORM\Query\ResultSetMappingBuilder;
21use Iterator;
22use ReflectionClass;
23
24/**
25 * @template T of EntityInterface
26 * @implements EntityListInterface<T>
27 */
28final class DoctrineEntityList implements EntityListInterface
29{
30    /**
31     * @param ReflectionClass<T> $entityClass
32     */
33    public function __construct(
34        private readonly OrmBuilder $ormBuilder,
35        private readonly DomainToStorageConverter $domainToStorageConverter,
36        private readonly EntityQueryFactory $entityQueryFactory,
37        private readonly ReflectionClass $entityClass,
38        private readonly BoundedContextId $boundedContextId
39    ) {
40    }
41
42    public function getIterator(): Iterator
43    {
44        $query = $this->createNativeQuery(new QuerySearch(0), noPagination: true);
45        /** @var StorageDtoInterface $rowResult */
46        foreach ($query->toIterable() as $rowResult) {
47            DoctrineUtils::loadAllProxies($rowResult);
48            yield $this->domainToStorageConverter->createDomainObject($rowResult);
49        }
50    }
51
52    private function createNativeQuery(QuerySearch $querySearch, bool $noPagination): NativeQuery
53    {
54        $entityQuery = $this->entityQueryFactory->createQueryFor($querySearch);
55        $entityManager = $this->ormBuilder->createEntityManager();
56        $resultSetMapping = new ResultSetMappingBuilder($entityManager);
57        $resultSetMapping->addRootEntityFromClassMetadata(
58            $this->entityQueryFactory->getDoctrineClass()->name,
59            'entity'
60        );
61        
62        if ($noPagination) {
63            return $entityManager->createNativeQuery($entityQuery->getWithoutPagination(), $resultSetMapping);
64        }
65        return $entityManager->createNativeQuery((string) $entityQuery, $resultSetMapping);
66    }
67
68    /**
69     * @return PaginatedResult<T>
70     */
71    public function toPaginatedResult(QuerySearch $search): PaginatedResult
72    {
73        $query = $this->createNativeQuery($search, noPagination: false);
74        $list = [];
75        foreach ($query->toIterable() as $rowResult) {
76            DoctrineUtils::loadAllProxies($rowResult);
77            $list[] = $this->domainToStorageConverter->createDomainObject($rowResult);
78        }
79        return new PaginatedResult(
80            LazyLoadedListIdentifier::createFrom($this->boundedContextId, $this->entityClass),
81            $this->getTotalCount($search->getApieContext()),
82            $this->getFilteredCount($search),
83            new ItemList($list),
84            $search->getPageIndex(),
85            $search->getItemsPerPage(),
86            $search
87        );
88    }
89
90    public function getTotalCount(ApieContext $apieContext = new ApieContext()): int
91    {
92        $entityQuery = $this->entityQueryFactory->createQueryFor(new QuerySearch(0, apieContext: $apieContext));
93        $entityManager = $this->ormBuilder->createEntityManager();
94
95        $rsm = new ResultSetMapping();
96        $rsm->addScalarResult('entityCount', 'entityCount', 'integer');
97
98        $query = $entityManager->createNativeQuery(
99            preg_replace(
100                '/order\s+by\s+.+$/i',
101                '',
102                str_replace(
103                    ['SELECT DISTINCT entity.*', 'GROUP BY entity.id'],
104                    ['SELECT COUNT(entity.id) AS entityCount', ''],
105                    $entityQuery->getWithoutPagination()
106                ),
107            ),
108            $rsm
109        );
110        $result = $query->execute(hydrationMode: AbstractQuery::HYDRATE_SINGLE_SCALAR);
111        return $result ?? 0;
112    }
113
114    public function getFilteredCount(QuerySearch $search): int
115    {
116        $entityQuery = $this->entityQueryFactory->createQueryFor($search);
117        $entityManager = $this->ormBuilder->createEntityManager();
118
119        $rsm = new ResultSetMapping();
120        $rsm->addScalarResult('entityCount', 'entityCount', 'integer');
121
122        $query = $entityManager->createNativeQuery(
123            preg_replace(
124                '/order\s+by\s+.+$/i',
125                '',
126                str_replace(
127                    ['SELECT DISTINCT entity.*', 'GROUP BY entity.id'],
128                    ['SELECT COUNT(entity.id) AS entityCount', ''],
129                    $entityQuery->getWithoutPagination()
130                ),
131            ),
132            $rsm
133        );
134        $result = $query->execute(hydrationMode: AbstractQuery::HYDRATE_SINGLE_SCALAR);
135        return $result ?? 0;
136    }
137}