25
25
* @author Charles Sarrazin <[email protected] >
26
26
* @author Kévin Dunglas <[email protected] >
27
27
* @author Antoine Bluchet <[email protected] >
28
+ * @author Baptiste Meyer <[email protected] >
28
29
*/
29
30
final class EagerLoadingExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface
30
31
{
31
32
private $ propertyNameCollectionFactory ;
32
33
private $ propertyMetadataFactory ;
33
34
private $ resourceMetadataFactory ;
34
35
private $ maxJoins ;
35
- private $ eagerOnly ;
36
+ private $ forceEager ;
36
37
37
- public function __construct (PropertyNameCollectionFactoryInterface $ propertyNameCollectionFactory , PropertyMetadataFactoryInterface $ propertyMetadataFactory , ResourceMetadataFactoryInterface $ resourceMetadataFactory , int $ maxJoins = 30 , bool $ eagerOnly = true )
38
+ public function __construct (PropertyNameCollectionFactoryInterface $ propertyNameCollectionFactory , PropertyMetadataFactoryInterface $ propertyMetadataFactory , ResourceMetadataFactoryInterface $ resourceMetadataFactory , int $ maxJoins = 30 , bool $ forceEager = true )
38
39
{
39
40
$ this ->propertyMetadataFactory = $ propertyMetadataFactory ;
40
41
$ this ->propertyNameCollectionFactory = $ propertyNameCollectionFactory ;
41
42
$ this ->resourceMetadataFactory = $ resourceMetadataFactory ;
42
43
$ this ->maxJoins = $ maxJoins ;
43
- $ this ->eagerOnly = $ eagerOnly ;
44
+ $ this ->forceEager = $ forceEager ;
44
45
}
45
46
46
47
/**
@@ -82,9 +83,10 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator
82
83
$ options = ['collection_operation_name ' => $ operationName ];
83
84
}
84
85
86
+ $ forceEager = $ this ->isForceEager ($ resourceClass , $ options );
85
87
$ groups = $ this ->getSerializerGroups ($ resourceClass , $ options , 'normalization_context ' );
86
88
87
- $ this ->joinRelations ($ queryBuilder , $ resourceClass , $ groups );
89
+ $ this ->joinRelations ($ queryBuilder , $ resourceClass , $ forceEager , $ groups );
88
90
}
89
91
90
92
/**
@@ -99,6 +101,8 @@ public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterf
99
101
$ options = ['item_operation_name ' => $ operationName ];
100
102
}
101
103
104
+ $ forceEager = $ this ->isForceEager ($ resourceClass , $ options );
105
+
102
106
if (isset ($ context ['groups ' ])) {
103
107
$ groups = ['serializer_groups ' => $ context ['groups ' ]];
104
108
} elseif (isset ($ context ['resource_class ' ])) {
@@ -107,14 +111,15 @@ public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterf
107
111
$ groups = $ this ->getSerializerGroups ($ resourceClass , $ options , 'normalization_context ' );
108
112
}
109
113
110
- $ this ->joinRelations ($ queryBuilder , $ resourceClass , $ groups );
114
+ $ this ->joinRelations ($ queryBuilder , $ resourceClass , $ forceEager , $ groups );
111
115
}
112
116
113
117
/**
114
118
* Joins relations to eager load.
115
119
*
116
120
* @param QueryBuilder $queryBuilder
117
121
* @param string $resourceClass
122
+ * @param bool $forceEager
118
123
* @param array $propertyMetadataOptions
119
124
* @param string $originAlias the current entity alias (first o, then a1, a2 etc.)
120
125
* @param string $relationAlias the previous relation alias to keep it unique
@@ -123,7 +128,7 @@ public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterf
123
128
*
124
129
* @throws RuntimeException when the max number of joins has been reached
125
130
*/
126
- private function joinRelations (QueryBuilder $ queryBuilder , string $ resourceClass , array $ propertyMetadataOptions = [], string $ originAlias = 'o ' , string &$ relationAlias = 'a ' , bool $ wasLeftJoin = false , int &$ joinCount = 0 )
131
+ private function joinRelations (QueryBuilder $ queryBuilder , string $ resourceClass , bool $ forceEager , array $ propertyMetadataOptions = [], string $ originAlias = 'o ' , string &$ relationAlias = 'a ' , bool $ wasLeftJoin = false , int &$ joinCount = 0 )
127
132
{
128
133
if ($ joinCount > $ this ->maxJoins ) {
129
134
throw new RuntimeException ('The total number of joined relations has exceeded the specified maximum. Raise the limit if necessary. ' );
@@ -136,7 +141,7 @@ private function joinRelations(QueryBuilder $queryBuilder, string $resourceClass
136
141
foreach ($ classMetadata ->associationMappings as $ association => $ mapping ) {
137
142
$ propertyMetadata = $ this ->propertyMetadataFactory ->create ($ resourceClass , $ association , $ propertyMetadataOptions );
138
143
139
- if (true === $ this -> eagerOnly && ClassMetadataInfo::FETCH_EAGER !== $ mapping ['fetch ' ]) {
144
+ if (false === $ forceEager && ClassMetadataInfo::FETCH_EAGER !== $ mapping ['fetch ' ]) {
140
145
continue ;
141
146
}
142
147
@@ -154,28 +159,33 @@ private function joinRelations(QueryBuilder $queryBuilder, string $resourceClass
154
159
$ associationAlias = $ relationAlias .$ i ++;
155
160
$ queryBuilder ->{$ method }($ originAlias .'. ' .$ association , $ associationAlias );
156
161
++$ joinCount ;
157
- $ select = [];
158
- $ targetClassMetadata = $ entityManager ->getClassMetadata ($ mapping ['targetEntity ' ]);
159
-
160
- foreach ($ this ->propertyNameCollectionFactory ->create ($ mapping ['targetEntity ' ]) as $ property ) {
161
- $ propertyMetadata = $ this ->propertyMetadataFactory ->create ($ mapping ['targetEntity ' ], $ property , $ propertyMetadataOptions );
162
-
163
- if (true === $ propertyMetadata ->isIdentifier ()) {
164
- $ select [] = $ property ;
165
- continue ;
166
- }
167
162
168
- //the field test allows to add methods to a Resource which do not reflect real database fields
169
- if (true === $ targetClassMetadata ->hasField ($ property ) && true === $ propertyMetadata ->isReadable ()) {
170
- $ select [] = $ property ;
171
- }
172
- }
163
+ $ relationAlias .= ++$ j ;
173
164
174
- $ queryBuilder ->addSelect (sprintf ('partial %s.{%s} ' , $ associationAlias , implode (', ' , $ select )));
165
+ $ this ->joinRelations ($ queryBuilder , $ mapping ['targetEntity ' ], $ forceEager , $ propertyMetadataOptions , $ associationAlias , $ relationAlias , $ method === 'leftJoin ' , $ joinCount );
166
+ }
167
+ }
175
168
176
- $ relationAlias .= ++$ j ;
169
+ /**
170
+ * Does an operation force eager?
171
+ *
172
+ * @param string $resourceClass
173
+ * @param array $options
174
+ *
175
+ * @return bool
176
+ */
177
+ private function isForceEager (string $ resourceClass , array $ options ): bool
178
+ {
179
+ $ resourceMetadata = $ this ->resourceMetadataFactory ->create ($ resourceClass );
177
180
178
- $ this ->joinRelations ($ queryBuilder , $ mapping ['targetEntity ' ], $ propertyMetadataOptions , $ associationAlias , $ relationAlias , $ method === 'leftJoin ' , $ joinCount );
181
+ if (isset ($ options ['collection_operation_name ' ])) {
182
+ $ forceEager = $ resourceMetadata ->getCollectionOperationAttribute ($ options ['collection_operation_name ' ], 'force_eager ' , null , true );
183
+ } elseif (isset ($ options ['item_operation_name ' ])) {
184
+ $ forceEager = $ resourceMetadata ->getItemOperationAttribute ($ options ['item_operation_name ' ], 'force_eager ' , null , true );
185
+ } else {
186
+ $ forceEager = $ resourceMetadata ->getAttribute ('force_eager ' );
179
187
}
188
+
189
+ return is_bool ($ forceEager ) ? $ forceEager : $ this ->forceEager ;
180
190
}
181
191
}
0 commit comments