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

api-platform / core / 14008635868

22 Mar 2025 12:39PM UTC coverage: 8.52% (+0.005%) from 8.515%
14008635868

Pull #7042

github

web-flow
Merge fdd88ef56 into 47a6dffbb
Pull Request #7042: Purge parent collections in inheritance cases

4 of 9 new or added lines in 1 file covered. (44.44%)

540 existing lines in 57 files now uncovered.

13394 of 157210 relevant lines covered (8.52%)

22.93 hits per line

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

78.48
/src/Doctrine/Orm/Util/QueryBuilderHelper.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\Util;
15

16
use Doctrine\ORM\Query\Expr\Join;
17
use Doctrine\ORM\QueryBuilder;
18
use Doctrine\Persistence\ManagerRegistry;
19

20
/**
21
 * @author Vincent Chalamon <vincentchalamon@gmail.com>
22
 *
23
 * @internal
24
 */
25
final class QueryBuilderHelper
26
{
27
    private function __construct()
28
    {
29
    }
×
30

31
    /**
32
     * Adds a join to the QueryBuilder if none exists.
33
     */
34
    public static function addJoinOnce(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $association, ?string $joinType = null, ?string $conditionType = null, ?string $condition = null, ?string $originAlias = null, ?string $newAlias = null): string
35
    {
UNCOV
36
        $join = self::getExistingJoin($queryBuilder, $alias, $association, $originAlias);
55✔
37

UNCOV
38
        if (null !== $join) {
55✔
39
            return $join->getAlias();
×
40
        }
41

UNCOV
42
        $associationAlias = $newAlias ?? $queryNameGenerator->generateJoinAlias($association);
55✔
UNCOV
43
        $query = "$alias.$association";
55✔
44

UNCOV
45
        if (Join::LEFT_JOIN === $joinType || QueryChecker::hasLeftJoin($queryBuilder)) {
55✔
UNCOV
46
            $queryBuilder->leftJoin($query, $associationAlias, $conditionType, $condition);
4✔
47
        } else {
UNCOV
48
            $queryBuilder->innerJoin($query, $associationAlias, $conditionType, $condition);
53✔
49
        }
50

UNCOV
51
        return $associationAlias;
55✔
52
    }
53

54
    /**
55
     * Gets the entity class name by an alias used in the QueryBuilder.
56
     */
57
    public static function getEntityClassByAlias(string $alias, QueryBuilder $queryBuilder, ManagerRegistry $managerRegistry): string
58
    {
59
        if (!\in_array($alias, $queryBuilder->getAllAliases(), true)) {
×
60
            throw new \LogicException(\sprintf('The alias "%s" does not exist in the QueryBuilder.', $alias));
×
61
        }
62

63
        $rootAliasMap = self::mapRootAliases($queryBuilder->getRootAliases(), $queryBuilder->getRootEntities());
×
64

65
        if (isset($rootAliasMap[$alias])) {
×
66
            return $rootAliasMap[$alias];
×
67
        }
68

69
        $metadata = null;
×
70

71
        foreach (self::traverseJoins($alias, $queryBuilder, $managerRegistry) as [$currentMetadata]) {
×
72
            $metadata = $currentMetadata;
×
73
        }
74

75
        if (null === $metadata) {
×
76
            throw new \LogicException(\sprintf('The alias "%s" does not exist in the QueryBuilder.', $alias));
×
77
        }
78

79
        return $metadata->getName();
×
80
    }
81

82
    /**
83
     * Finds the root alias for an alias used in the QueryBuilder.
84
     */
85
    public static function findRootAlias(string $alias, QueryBuilder $queryBuilder): string
86
    {
UNCOV
87
        if (\in_array($alias, $queryBuilder->getRootAliases(), true)) {
79✔
88
            return $alias;
×
89
        }
90

UNCOV
91
        foreach ($queryBuilder->getDQLPart('join') as $rootAlias => $joins) {
79✔
UNCOV
92
            foreach ($joins as $join) {
79✔
UNCOV
93
                if ($alias === $join->getAlias()) {
79✔
UNCOV
94
                    return $rootAlias;
79✔
95
                }
96
            }
97
        }
98

99
        throw new \LogicException(\sprintf('The alias "%s" does not exist in the QueryBuilder.', $alias));
×
100
    }
101

102
    /**
103
     * Traverses through the joins for an alias used in the QueryBuilder.
104
     *
105
     * @return \Generator<string, array>
106
     */
107
    public static function traverseJoins(string $alias, QueryBuilder $queryBuilder, ManagerRegistry $managerRegistry): \Generator
108
    {
UNCOV
109
        $rootAliasMap = self::mapRootAliases($queryBuilder->getRootAliases(), $queryBuilder->getRootEntities());
79✔
110

UNCOV
111
        $joinParts = $queryBuilder->getDQLPart('join');
79✔
UNCOV
112
        $rootAlias = self::findRootAlias($alias, $queryBuilder);
79✔
113

UNCOV
114
        $joinAliasMap = self::mapJoinAliases($joinParts[$rootAlias]);
79✔
115

UNCOV
116
        $aliasMap = [...$rootAliasMap, ...$joinAliasMap];
79✔
117

UNCOV
118
        $apexEntityClass = null;
79✔
UNCOV
119
        $associationStack = [];
79✔
UNCOV
120
        $aliasStack = [];
79✔
UNCOV
121
        $currentAlias = $alias;
79✔
122

UNCOV
123
        while (null === $apexEntityClass) {
79✔
UNCOV
124
            if (!isset($aliasMap[$currentAlias])) {
79✔
125
                throw new \LogicException(\sprintf('Unknown alias "%s".', $currentAlias));
×
126
            }
127

UNCOV
128
            if (\is_string($aliasMap[$currentAlias])) {
79✔
UNCOV
129
                $aliasStack[] = $currentAlias;
79✔
UNCOV
130
                $apexEntityClass = $aliasMap[$currentAlias];
79✔
131
            } else {
UNCOV
132
                [$parentAlias, $association] = $aliasMap[$currentAlias];
76✔
133

UNCOV
134
                $associationStack[] = $association;
76✔
UNCOV
135
                $aliasStack[] = $currentAlias;
76✔
UNCOV
136
                $currentAlias = $parentAlias;
76✔
137
            }
138
        }
139

UNCOV
140
        $entityClass = $apexEntityClass;
79✔
141

UNCOV
142
        while (null !== ($alias = array_pop($aliasStack))) {
79✔
UNCOV
143
            $metadata = $managerRegistry
79✔
UNCOV
144
                ->getManagerForClass($entityClass)
79✔
UNCOV
145
                ->getClassMetadata($entityClass);
79✔
146

UNCOV
147
            $association = array_pop($associationStack);
79✔
148

UNCOV
149
            yield $alias => [
79✔
UNCOV
150
                $metadata,
79✔
UNCOV
151
                $association,
79✔
UNCOV
152
            ];
79✔
153

UNCOV
154
            if (null !== $association) {
61✔
UNCOV
155
                $entityClass = $metadata->getAssociationTargetClass($association);
58✔
156
            }
157
        }
158
    }
159

160
    /**
161
     * Gets the existing join from QueryBuilder DQL parts.
162
     */
163
    public static function getExistingJoin(QueryBuilder $queryBuilder, string $alias, string $association, ?string $originAlias = null): ?Join
164
    {
165
        $parts = $queryBuilder->getDQLPart('join');
132✔
166
        $rootAlias = $originAlias ?? $queryBuilder->getRootAliases()[0];
132✔
167

168
        if (!isset($parts[$rootAlias])) {
132✔
169
            return null;
132✔
170
        }
171

UNCOV
172
        foreach ($parts[$rootAlias] as $join) {
56✔
173
            /** @var Join $join */
UNCOV
174
            if (\sprintf('%s.%s', $alias, $association) === $join->getJoin()) {
56✔
UNCOV
175
                return $join;
7✔
176
            }
177
        }
178

UNCOV
179
        return null;
52✔
180
    }
181

182
    /**
183
     * Maps the root aliases to root entity classes.
184
     *
185
     * @return array<string, string>
186
     */
187
    private static function mapRootAliases(array $rootAliases, array $rootEntities): array
188
    {
189
        /** @var false|array $aliasMap */
UNCOV
190
        $aliasMap = array_combine($rootAliases, $rootEntities);
79✔
UNCOV
191
        if (false === $aliasMap) {
79✔
192
            throw new \LogicException('Number of root aliases and root entities do not match.');
×
193
        }
194

UNCOV
195
        return $aliasMap;
79✔
196
    }
197

198
    /**
199
     * Maps the join aliases to the parent alias and association, or the entity class.
200
     *
201
     * @return array<string, string[]|string>
202
     */
203
    private static function mapJoinAliases(iterable $joins): array
204
    {
UNCOV
205
        $aliasMap = [];
79✔
206

UNCOV
207
        foreach ($joins as $join) {
79✔
UNCOV
208
            $alias = $join->getAlias();
79✔
UNCOV
209
            $relationship = $join->getJoin();
79✔
210

UNCOV
211
            if (str_contains((string) $relationship, '.')) {
79✔
UNCOV
212
                $aliasMap[$alias] = explode('.', (string) $relationship);
76✔
213
            } else {
UNCOV
214
                $aliasMap[$alias] = $relationship;
3✔
215
            }
216
        }
217

UNCOV
218
        return $aliasMap;
79✔
219
    }
220
}
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