@@ -94,7 +94,61 @@ private function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $
9494 $ options ['denormalization_groups ' ] = $ denormalizationGroups ;
9595 }
9696
97- $ this ->joinRelations ($ queryBuilder , $ queryNameGenerator , $ resourceClass , $ forceEager , $ fetchPartial , $ queryBuilder ->getRootAliases ()[0 ], $ options , $ context );
97+ $ selects = $ this ->joinRelations ($ queryBuilder , $ queryNameGenerator , $ resourceClass , $ forceEager , $ fetchPartial , $ queryBuilder ->getRootAliases ()[0 ], $ options , $ context );
98+ $ selectsByClass = [];
99+ foreach ($ selects as [$ entity , $ alias , $ fields ]) {
100+ if ($ entity === $ resourceClass ) {
101+ // We don't perform partial select the root entity
102+ $ fields = null ;
103+ }
104+
105+ if (!isset ($ selectsByClass [$ entity ])) {
106+ $ selectsByClass [$ entity ] = [
107+ 'aliases ' => [$ alias => true ],
108+ 'fields ' => null === $ fields ? null : array_flip ($ fields ),
109+ ];
110+ } else {
111+ $ selectsByClass [$ entity ]['aliases ' ][$ alias ] = true ;
112+ if (null === $ selectsByClass [$ entity ]['fields ' ]) {
113+ continue ;
114+ }
115+
116+ if (null === $ fields ) {
117+ $ selectsByClass [$ entity ]['fields ' ] = null ;
118+ continue ;
119+ }
120+
121+ // Merge fields
122+ foreach ($ fields as $ field ) {
123+ $ selectsByClass [$ entity ]['fields ' ][$ field ] = true ;
124+ }
125+ }
126+ }
127+
128+ $ existingSelects = [];
129+ foreach ($ queryBuilder ->getDQLPart ('select ' ) ?? [] as $ dqlSelect ) {
130+ if (!$ dqlSelect instanceof Select) {
131+ continue ;
132+ }
133+ foreach ($ dqlSelect ->getParts () as $ part ) {
134+ $ existingSelects [(string ) $ part ] = true ;
135+ }
136+ }
137+
138+ foreach ($ selectsByClass as $ data ) {
139+ $ fields = $ data ['fields ' ] === null ? null : array_keys ($ data ['fields ' ]);
140+ foreach (array_keys ($ data ['aliases ' ]) as $ alias ) {
141+ if (isset ($ existingSelects [$ alias ])) {
142+ continue ;
143+ }
144+
145+ if (null === $ fields ) {
146+ $ queryBuilder ->addSelect ($ alias );
147+ } else {
148+ $ queryBuilder ->addSelect (\sprintf ('partial %s.{%s} ' , $ alias , implode (', ' , $ fields )));
149+ }
150+ }
151+ }
98152 }
99153
100154 /**
@@ -106,7 +160,7 @@ private function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $
106160 *
107161 * @throws RuntimeException when the max number of joins has been reached
108162 */
109- private function joinRelations (QueryBuilder $ queryBuilder , QueryNameGeneratorInterface $ queryNameGenerator , string $ resourceClass , bool $ forceEager , bool $ fetchPartial , string $ parentAlias , array $ options = [], array $ normalizationContext = [], bool $ wasLeftJoin = false , int &$ joinCount = 0 , ?int $ currentDepth = null , ?string $ parentAssociation = null ): void
163+ private function joinRelations (QueryBuilder $ queryBuilder , QueryNameGeneratorInterface $ queryNameGenerator , string $ resourceClass , bool $ forceEager , bool $ fetchPartial , string $ parentAlias , array $ options = [], array $ normalizationContext = [], bool $ wasLeftJoin = false , int &$ joinCount = 0 , ?int $ currentDepth = null , ?string $ parentAssociation = null ): iterable
110164 {
111165 if ($ joinCount > $ this ->maxJoins ) {
112166 throw new RuntimeException ('The total number of joined relations has exceeded the specified maximum. Raise the limit if necessary with the "api_platform.eager_loading.max_joins" configuration key (https://api-platform.com/docs/core/performance/#eager-loading), or limit the maximum serialization depth using the "enable_max_depth" option of the Symfony serializer (https://symfony.com/doc/current/components/serializer.html#handling-serialization-depth). ' );
@@ -198,12 +252,12 @@ private function joinRelations(QueryBuilder $queryBuilder, QueryNameGeneratorInt
198252
199253 if (true === $ fetchPartial ) {
200254 try {
201- $ this ->addSelect ($ queryBuilder , $ mapping ['targetEntity ' ], $ associationAlias , $ options );
255+ yield from $ this ->addSelect ($ queryBuilder , $ mapping ['targetEntity ' ], $ associationAlias , $ options );
202256 } catch (ResourceClassNotFoundException ) {
203257 continue ;
204258 }
205259 } else {
206- $ this -> addSelectOnce ( $ queryBuilder , $ associationAlias) ;
260+ yield [ $ resourceClass , $ associationAlias, null ] ;
207261 }
208262
209263 // Avoid recursive joins for self-referencing relations
@@ -225,17 +279,17 @@ private function joinRelations(QueryBuilder $queryBuilder, QueryNameGeneratorInt
225279 }
226280 }
227281
228- $ this ->joinRelations ($ queryBuilder , $ queryNameGenerator , $ mapping ['targetEntity ' ], $ forceEager , $ fetchPartial , $ associationAlias , $ options , $ childNormalizationContext , $ isLeftJoin , $ joinCount , $ currentDepth , $ association );
282+ yield from $ this ->joinRelations ($ queryBuilder , $ queryNameGenerator , $ mapping ['targetEntity ' ], $ forceEager , $ fetchPartial , $ associationAlias , $ options , $ childNormalizationContext , $ isLeftJoin , $ joinCount , $ currentDepth , $ association );
229283 }
230284 }
231285
232- private function addSelect (QueryBuilder $ queryBuilder , string $ entity , string $ associationAlias , array $ propertyMetadataOptions ): void
286+ private function addSelect (QueryBuilder $ queryBuilder , string $ entity , string $ associationAlias , array $ propertyMetadataOptions ): iterable
233287 {
234288 $ select = [];
235289 $ entityManager = $ queryBuilder ->getEntityManager ();
236290 $ targetClassMetadata = $ entityManager ->getClassMetadata ($ entity );
237291 if (!empty ($ targetClassMetadata ->subClasses )) {
238- $ this -> addSelectOnce ( $ queryBuilder , $ associationAlias) ;
292+ yield [ $ entity , $ associationAlias, null ] ;
239293
240294 return ;
241295 }
@@ -270,15 +324,6 @@ private function addSelect(QueryBuilder $queryBuilder, string $entity, string $a
270324 }
271325 }
272326
273- $ queryBuilder ->addSelect (\sprintf ('partial %s.{%s} ' , $ associationAlias , implode (', ' , $ select )));
274- }
275-
276- private function addSelectOnce (QueryBuilder $ queryBuilder , string $ alias ): void
277- {
278- $ existingSelects = array_reduce ($ queryBuilder ->getDQLPart ('select ' ) ?? [], fn ($ existing , $ dqlSelect ) => ($ dqlSelect instanceof Select) ? array_merge ($ existing , $ dqlSelect ->getParts ()) : $ existing , []);
279-
280- if (!\in_array ($ alias , $ existingSelects , true )) {
281- $ queryBuilder ->addSelect ($ alias );
282- }
327+ yield [$ entity , $ associationAlias , $ select ];
283328 }
284329}
0 commit comments