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

api-platform / core / 18089937549

29 Sep 2025 07:56AM UTC coverage: 21.764% (-0.3%) from 22.093%
18089937549

Pull #7416

github

web-flow
Merge 061bcc790 into abe0438be
Pull Request #7416: fix(laravel): serializer attributes on Eloquent methods

0 of 151 new or added lines in 11 files covered. (0.0%)

5028 existing lines in 173 files now uncovered.

11889 of 54626 relevant lines covered (21.76%)

25.32 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);
6✔
37

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

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

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

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

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

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

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

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

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

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

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

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

140
        $entityClass = $apexEntityClass;
10✔
141

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

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

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

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

168
        if (!isset($parts[$rootAlias])) {
12✔
169
            return null;
12✔
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);
10✔
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 = [];
10✔
200

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

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

212
        return $aliasMap;
10✔
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