• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

api-platform / core / 15040977736

15 May 2025 09:02AM UTC coverage: 21.754% (+13.3%) from 8.423%
15040977736

Pull #6960

github

web-flow
Merge 7a7a13526 into 1862d03b7
Pull Request #6960: feat(json-schema): mutualize json schema between formats

320 of 460 new or added lines in 24 files covered. (69.57%)

1863 existing lines in 109 files now uncovered.

11069 of 50882 relevant lines covered (21.75%)

29.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.27
/src/Doctrine/Orm/Extension/PaginationExtension.php
1
<?php
2

3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <dunglas@gmail.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11

12
declare(strict_types=1);
13

14
namespace ApiPlatform\Doctrine\Orm\Extension;
15

16
use ApiPlatform\Doctrine\Orm\AbstractPaginator;
17
use ApiPlatform\Doctrine\Orm\Paginator;
18
use ApiPlatform\Doctrine\Orm\Util\QueryChecker;
19
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
20
use ApiPlatform\Metadata\Exception\InvalidArgumentException;
21
use ApiPlatform\Metadata\Operation;
22
use ApiPlatform\State\Pagination\Pagination;
23
use Doctrine\ORM\QueryBuilder;
24
use Doctrine\ORM\Tools\Pagination\CountWalker;
25
use Doctrine\ORM\Tools\Pagination\Paginator as DoctrineOrmPaginator;
26
use Doctrine\Persistence\ManagerRegistry;
27

28
// Help opcache.preload discover always-needed symbols
UNCOV
29
class_exists(AbstractPaginator::class);
×
30

31
/**
32
 * Applies pagination on the Doctrine query for resource collection when enabled.
33
 *
34
 * @author Kévin Dunglas <dunglas@gmail.com>
35
 * @author Samuel ROZE <samuel.roze@gmail.com>
36
 */
37
final class PaginationExtension implements QueryResultCollectionExtensionInterface
38
{
39
    public function __construct(private readonly ManagerRegistry $managerRegistry, private readonly ?Pagination $pagination)
40
    {
41
    }
303✔
42

43
    /**
44
     * {@inheritdoc}
45
     */
46
    public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void
47
    {
48
        if (null === $pagination = $this->getPagination($queryBuilder, $operation, $context)) {
303✔
49
            return;
3✔
50
        }
51

52
        [$offset, $limit] = $pagination;
298✔
53

54
        $queryBuilder
298✔
55
            ->setFirstResult($offset)
298✔
56
            ->setMaxResults($limit);
298✔
57
    }
58

59
    /**
60
     * {@inheritdoc}
61
     */
62
    public function supportsResult(string $resourceClass, ?Operation $operation = null, array $context = []): bool
63
    {
64
        if ($context['graphql_operation_name'] ?? false) {
301✔
65
            return $this->pagination->isGraphQlEnabled($operation, $context);
50✔
66
        }
67

68
        return $this->pagination->isEnabled($operation, $context);
252✔
69
    }
70

71
    /**
72
     * {@inheritdoc}
73
     */
74
    public function getResult(QueryBuilder $queryBuilder, ?string $resourceClass = null, ?Operation $operation = null, array $context = []): iterable
75
    {
76
        $query = $queryBuilder->getQuery();
298✔
77

78
        // Only one alias, without joins, disable the DISTINCT on the COUNT
79
        if (1 === \count($queryBuilder->getAllAliases())) {
298✔
80
            $query->setHint(CountWalker::HINT_DISTINCT, false);
228✔
81
        }
82

83
        $doctrineOrmPaginator = new DoctrineOrmPaginator($query, $this->shouldDoctrinePaginatorFetchJoinCollection($queryBuilder, $operation, $context));
298✔
84
        $doctrineOrmPaginator->setUseOutputWalkers($this->shouldDoctrinePaginatorUseOutputWalkers($queryBuilder, $operation, $context));
298✔
85

86
        $isPartialEnabled = $this->pagination->isPartialEnabled($operation, $context);
298✔
87

88
        if ($isPartialEnabled) {
298✔
89
            return new class($doctrineOrmPaginator) extends AbstractPaginator {
8✔
90
            };
8✔
91
        }
92

93
        return new Paginator($doctrineOrmPaginator);
292✔
94
    }
95

96
    /**
97
     * @throws InvalidArgumentException
98
     */
99
    private function getPagination(QueryBuilder $queryBuilder, ?Operation $operation, array $context): ?array
100
    {
101
        $enabled = isset($context['graphql_operation_name']) ? $this->pagination->isGraphQlEnabled($operation, $context) : $this->pagination->isEnabled($operation, $context);
303✔
102

103
        if (!$enabled) {
303✔
104
            return null;
3✔
105
        }
106

107
        $context = $this->addCountToContext($queryBuilder, $context);
300✔
108

109
        return \array_slice($this->pagination->getPagination($operation, $context), 1);
300✔
110
    }
111

112
    private function addCountToContext(QueryBuilder $queryBuilder, array $context): array
113
    {
114
        if (!($context['graphql_operation_name'] ?? false)) {
300✔
115
            return $context;
252✔
116
        }
117

118
        if (isset($context['filters']['last']) && !isset($context['filters']['before'])) {
49✔
119
            $context['count'] = (new DoctrineOrmPaginator($queryBuilder))->count();
1✔
120
        }
121

122
        return $context;
49✔
123
    }
124

125
    /**
126
     * Determines the value of the $fetchJoinCollection argument passed to the Doctrine ORM Paginator.
127
     */
128
    private function shouldDoctrinePaginatorFetchJoinCollection(QueryBuilder $queryBuilder, ?Operation $operation = null, array $context = []): bool
129
    {
130
        $fetchJoinCollection = $operation?->getPaginationFetchJoinCollection();
298✔
131

132
        if (isset($context['operation_name']) && isset($fetchJoinCollection)) {
298✔
133
            return $fetchJoinCollection;
×
134
        }
135

136
        if (isset($context['graphql_operation_name']) && isset($fetchJoinCollection)) {
298✔
137
            return $fetchJoinCollection;
×
138
        }
139

140
        /*
141
         * "Cannot count query which selects two FROM components, cannot make distinction"
142
         *
143
         * @see https://github.com/doctrine/orm/blob/v2.6.3/lib/Doctrine/ORM/Tools/Pagination/WhereInWalker.php#L81
144
         * @see https://github.com/doctrine/doctrine2/issues/2910
145
         */
146
        if (QueryChecker::hasRootEntityWithCompositeIdentifier($queryBuilder, $this->managerRegistry)) {
298✔
147
            return false;
2✔
148
        }
149

150
        if (QueryChecker::hasJoinedToManyAssociation($queryBuilder, $this->managerRegistry)) {
296✔
151
            return true;
28✔
152
        }
153

154
        // disable $fetchJoinCollection by default (performance)
155
        return false;
277✔
156
    }
157

158
    /**
159
     * Determines whether the Doctrine ORM Paginator should use output walkers.
160
     */
161
    private function shouldDoctrinePaginatorUseOutputWalkers(QueryBuilder $queryBuilder, ?Operation $operation = null, array $context = []): bool
162
    {
163
        $useOutputWalkers = $operation?->getPaginationUseOutputWalkers();
298✔
164

165
        if (isset($context['operation_name']) && isset($useOutputWalkers)) {
298✔
166
            return $useOutputWalkers;
×
167
        }
168

169
        if (isset($context['graphql_operation_name']) && isset($useOutputWalkers)) {
298✔
170
            return $useOutputWalkers;
×
171
        }
172

173
        /*
174
         * "Cannot count query that uses a HAVING clause. Use the output walkers for pagination"
175
         *
176
         * @see https://github.com/doctrine/orm/blob/v2.6.3/lib/Doctrine/ORM/Tools/Pagination/CountWalker.php#L56
177
         */
178
        if (QueryChecker::hasHavingClause($queryBuilder)) {
298✔
179
            return true;
×
180
        }
181

182
        /*
183
         * "Cannot count query which selects two FROM components, cannot make distinction"
184
         *
185
         * @see https://github.com/doctrine/orm/blob/v2.6.3/lib/Doctrine/ORM/Tools/Pagination/CountWalker.php#L64
186
         */
187
        if (QueryChecker::hasRootEntityWithCompositeIdentifier($queryBuilder, $this->managerRegistry)) {
298✔
188
            return true;
2✔
189
        }
190

191
        /*
192
         * "Paginating an entity with foreign key as identifier only works when using the Output Walkers. Call Paginator#setUseOutputWalkers(true) before iterating the paginator."
193
         *
194
         * @see https://github.com/doctrine/orm/blob/v2.6.3/lib/Doctrine/ORM/Tools/Pagination/LimitSubqueryWalker.php#L77
195
         */
196
        if (QueryChecker::hasRootEntityWithForeignKeyIdentifier($queryBuilder, $this->managerRegistry)) {
296✔
197
            return true;
2✔
198
        }
199

200
        /*
201
         * "Cannot select distinct identifiers from query with LIMIT and ORDER BY on a column from a fetch joined to-many association. Use output walkers."
202
         *
203
         * @see https://github.com/doctrine/orm/blob/v2.6.3/lib/Doctrine/ORM/Tools/Pagination/LimitSubqueryWalker.php#L150
204
         */
205
        if (QueryChecker::hasMaxResults($queryBuilder) && QueryChecker::hasOrderByOnFetchJoinedToManyAssociation($queryBuilder, $this->managerRegistry)) {
294✔
206
            return true;
×
207
        }
208

209
        // Disable output walkers by default (performance)
210
        return false;
294✔
211
    }
212
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc