• 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

62.03
/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);
×
37

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

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

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

51
        return $associationAlias;
×
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)) {
2✔
88
            return $alias;
×
89
        }
90

UNCOV
91
        foreach ($queryBuilder->getDQLPart('join') as $rootAlias => $joins) {
2✔
UNCOV
92
            foreach ($joins as $join) {
2✔
UNCOV
93
                if ($alias === $join->getAlias()) {
2✔
UNCOV
94
                    return $rootAlias;
2✔
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());
2✔
110

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

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

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

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

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

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

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

UNCOV
140
        $entityClass = $apexEntityClass;
2✔
141

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

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

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

UNCOV
154
            if (null !== $association) {
1✔
UNCOV
155
                $entityClass = $metadata->getAssociationTargetClass($association);
1✔
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
    {
UNCOV
165
        $parts = $queryBuilder->getDQLPart('join');
2✔
UNCOV
166
        $rootAlias = $originAlias ?? $queryBuilder->getRootAliases()[0];
2✔
167

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

172
        foreach ($parts[$rootAlias] as $join) {
×
173
            /** @var Join $join */
174
            if (\sprintf('%s.%s', $alias, $association) === $join->getJoin()) {
×
175
                return $join;
×
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
        /** @var false|array $aliasMap */
UNCOV
190
        $aliasMap = array_combine($rootAliases, $rootEntities);
2✔
UNCOV
191
        if (false === $aliasMap) {
2✔
192
            throw new \LogicException('Number of root aliases and root entities do not match.');
×
193
        }
194

UNCOV
195
        return $aliasMap;
2✔
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 = [];
2✔
206

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

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

UNCOV
218
        return $aliasMap;
2✔
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