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

api-platform / core / 16050929464

03 Jul 2025 12:51PM UTC coverage: 22.065% (+0.2%) from 21.821%
16050929464

push

github

soyuka
chore: todo improvement

11516 of 52192 relevant lines covered (22.06%)

22.08 hits per line

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

75.0
/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
    {
36
        $join = self::getExistingJoin($queryBuilder, $alias, $association, $originAlias);
2✔
37

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

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

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

51
        return $associationAlias;
2✔
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
    {
87
        if (\in_array($alias, $queryBuilder->getRootAliases(), true)) {
6✔
88
            return $alias;
×
89
        }
90

91
        foreach ($queryBuilder->getDQLPart('join') as $rootAlias => $joins) {
6✔
92
            foreach ($joins as $join) {
6✔
93
                if ($alias === $join->getAlias()) {
6✔
94
                    return $rootAlias;
6✔
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
    {
109
        $rootAliasMap = self::mapRootAliases($queryBuilder->getRootAliases(), $queryBuilder->getRootEntities());
6✔
110

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

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

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

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

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

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

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

140
        $entityClass = $apexEntityClass;
6✔
141

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

147
            $association = array_pop($associationStack);
6✔
148

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

154
            if (null !== $association) {
4✔
155
                $entityClass = $metadata->getAssociationTargetClass($association);
4✔
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');
6✔
166
        $rootAlias = $originAlias ?? $queryBuilder->getRootAliases()[0];
6✔
167

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

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

179
        return null;
×
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
        return array_combine($rootAliases, $rootEntities);
6✔
190
    }
191

192
    /**
193
     * Maps the join aliases to the parent alias and association, or the entity class.
194
     *
195
     * @return array<string, string[]|string>
196
     */
197
    private static function mapJoinAliases(iterable $joins): array
198
    {
199
        $aliasMap = [];
6✔
200

201
        foreach ($joins as $join) {
6✔
202
            $alias = $join->getAlias();
6✔
203
            $relationship = $join->getJoin();
6✔
204

205
            if (str_contains((string) $relationship, '.')) {
6✔
206
                $aliasMap[$alias] = explode('.', (string) $relationship);
6✔
207
            } else {
208
                $aliasMap[$alias] = $relationship;
×
209
            }
210
        }
211

212
        return $aliasMap;
6✔
213
    }
214
}
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

© 2026 Coveralls, Inc