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

api-platform / core / 15133993414

20 May 2025 09:30AM UTC coverage: 26.313% (-1.2%) from 27.493%
15133993414

Pull #7161

github

web-flow
Merge e2c03d45f into 5459ba375
Pull Request #7161: fix(metadata): infer parameter string type from schema

0 of 2 new or added lines in 1 file covered. (0.0%)

11019 existing lines in 363 files now uncovered.

12898 of 49018 relevant lines covered (26.31%)

34.33 hits per line

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

74.55
/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);
1✔
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
    {
UNCOV
41
    }
104✔
42

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

UNCOV
122
        return $context;
2✔
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
    {
UNCOV
130
        $fetchJoinCollection = $operation?->getPaginationFetchJoinCollection();
104✔
131

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

UNCOV
136
        if (isset($context['graphql_operation_name']) && isset($fetchJoinCollection)) {
104✔
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
         */
UNCOV
146
        if (QueryChecker::hasRootEntityWithCompositeIdentifier($queryBuilder, $this->managerRegistry)) {
104✔
147
            return false;
×
148
        }
149

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

154
        // disable $fetchJoinCollection by default (performance)
UNCOV
155
        return false;
103✔
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
    {
UNCOV
163
        $useOutputWalkers = $operation?->getPaginationUseOutputWalkers();
104✔
164

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

UNCOV
169
        if (isset($context['graphql_operation_name']) && isset($useOutputWalkers)) {
104✔
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
         */
UNCOV
178
        if (QueryChecker::hasHavingClause($queryBuilder)) {
104✔
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
         */
UNCOV
187
        if (QueryChecker::hasRootEntityWithCompositeIdentifier($queryBuilder, $this->managerRegistry)) {
104✔
188
            return true;
×
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
         */
UNCOV
196
        if (QueryChecker::hasRootEntityWithForeignKeyIdentifier($queryBuilder, $this->managerRegistry)) {
104✔
197
            return true;
×
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
         */
UNCOV
205
        if (QueryChecker::hasMaxResults($queryBuilder) && QueryChecker::hasOrderByOnFetchJoinedToManyAssociation($queryBuilder, $this->managerRegistry)) {
104✔
206
            return true;
×
207
        }
208

209
        // Disable output walkers by default (performance)
UNCOV
210
        return false;
104✔
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