20
20
use Symfony \Bridge \Doctrine \Form \DataTransformer \CollectionToArrayTransformer ;
21
21
use Symfony \Bridge \Doctrine \Form \EventListener \MergeDoctrineCollectionListener ;
22
22
use Symfony \Component \Form \AbstractType ;
23
+ use Symfony \Component \Form \ChoiceList \ChoiceList ;
23
24
use Symfony \Component \Form \ChoiceList \Factory \CachingFactoryDecorator ;
24
25
use Symfony \Component \Form \Exception \RuntimeException ;
25
26
use Symfony \Component \Form \FormBuilderInterface ;
@@ -40,9 +41,9 @@ abstract class DoctrineType extends AbstractType implements ResetInterface
40
41
private $ idReaders = [];
41
42
42
43
/**
43
- * @var DoctrineChoiceLoader []
44
+ * @var EntityLoaderInterface []
44
45
*/
45
- private $ choiceLoaders = [];
46
+ private $ entityLoaders = [];
46
47
47
48
/**
48
49
* Creates the label for a choice.
@@ -115,43 +116,26 @@ public function configureOptions(OptionsResolver $resolver)
115
116
$ choiceLoader = function (Options $ options ) {
116
117
// Unless the choices are given explicitly, load them on demand
117
118
if (null === $ options ['choices ' ]) {
118
- $ hash = null ;
119
- $ qbParts = null ;
119
+ // If there is no QueryBuilder we can safely cache
120
+ $ vary = [ $ options [ ' em ' ], $ options [ ' class ' ]] ;
120
121
121
- // If there is no QueryBuilder we can safely cache DoctrineChoiceLoader,
122
122
// also if concrete Type can return important QueryBuilder parts to generate
123
- // hash key we go for it as well
124
- if (!$ options ['query_builder ' ] || null !== $ qbParts = $ this ->getQueryBuilderPartsForCachingHash ($ options ['query_builder ' ])) {
125
- $ hash = CachingFactoryDecorator::generateHash ([
126
- $ options ['em ' ],
127
- $ options ['class ' ],
128
- $ qbParts ,
129
- ]);
130
-
131
- if (isset ($ this ->choiceLoaders [$ hash ])) {
132
- return $ this ->choiceLoaders [$ hash ];
133
- }
123
+ // hash key we go for it as well, otherwise fallback on the instance
124
+ if ($ options ['query_builder ' ]) {
125
+ $ vary [] = $ this ->getQueryBuilderPartsForCachingHash ($ options ['query_builder ' ]) ?? $ options ['query_builder ' ];
134
126
}
135
127
136
- if (null !== $ options ['query_builder ' ]) {
137
- $ entityLoader = $ this ->getLoader ($ options ['em ' ], $ options ['query_builder ' ], $ options ['class ' ]);
138
- } else {
139
- $ queryBuilder = $ options ['em ' ]->getRepository ($ options ['class ' ])->createQueryBuilder ('e ' );
140
- $ entityLoader = $ this ->getLoader ($ options ['em ' ], $ queryBuilder , $ options ['class ' ]);
141
- }
142
-
143
- $ doctrineChoiceLoader = new DoctrineChoiceLoader (
128
+ return ChoiceList::loader ($ this , new DoctrineChoiceLoader (
144
129
$ options ['em ' ],
145
130
$ options ['class ' ],
146
131
$ options ['id_reader ' ],
147
- $ entityLoader
148
- );
149
-
150
- if (null !== $ hash ) {
151
- $ this ->choiceLoaders [$ hash ] = $ doctrineChoiceLoader ;
152
- }
153
-
154
- return $ doctrineChoiceLoader ;
132
+ $ this ->getCachedEntityLoader (
133
+ $ options ['em ' ],
134
+ $ options ['query_builder ' ] ?? $ options ['em ' ]->getRepository ($ options ['class ' ])->createQueryBuilder ('e ' ),
135
+ $ options ['class ' ],
136
+ $ vary
137
+ )
138
+ ), $ vary );
155
139
}
156
140
157
141
return null ;
@@ -162,7 +146,7 @@ public function configureOptions(OptionsResolver $resolver)
162
146
// field name. We can only use numeric IDs as names, as we cannot
163
147
// guarantee that a non-numeric ID contains a valid form name
164
148
if ($ options ['id_reader ' ] instanceof IdReader && $ options ['id_reader ' ]->isIntId ()) {
165
- return [__CLASS__ , 'createChoiceName ' ];
149
+ return ChoiceList:: fieldName ( $ this , [__CLASS__ , 'createChoiceName ' ]) ;
166
150
}
167
151
168
152
// Otherwise, an incrementing integer is used as name automatically
@@ -176,7 +160,7 @@ public function configureOptions(OptionsResolver $resolver)
176
160
$ choiceValue = function (Options $ options ) {
177
161
// If the entity has a single-column ID, use that ID as value
178
162
if ($ options ['id_reader ' ] instanceof IdReader && $ options ['id_reader ' ]->isSingleId ()) {
179
- return [$ options ['id_reader ' ], 'getIdValue ' ];
163
+ return ChoiceList:: value ( $ this , [$ options ['id_reader ' ], 'getIdValue ' ], $ options [ ' id_reader ' ]) ;
180
164
}
181
165
182
166
// Otherwise, an incrementing integer is used as value automatically
@@ -214,35 +198,21 @@ public function configureOptions(OptionsResolver $resolver)
214
198
// Set the "id_reader" option via the normalizer. This option is not
215
199
// supposed to be set by the user.
216
200
$ idReaderNormalizer = function (Options $ options ) {
217
- $ hash = CachingFactoryDecorator::generateHash ([
218
- $ options ['em ' ],
219
- $ options ['class ' ],
220
- ]);
221
-
222
201
// The ID reader is a utility that is needed to read the object IDs
223
202
// when generating the field values. The callback generating the
224
203
// field values has no access to the object manager or the class
225
204
// of the field, so we store that information in the reader.
226
205
// The reader is cached so that two choice lists for the same class
227
206
// (and hence with the same reader) can successfully be cached.
228
- if (!isset ($ this ->idReaders [$ hash ])) {
229
- $ classMetadata = $ options ['em ' ]->getClassMetadata ($ options ['class ' ]);
230
- $ this ->idReaders [$ hash ] = new IdReader ($ options ['em ' ], $ classMetadata );
231
- }
232
-
233
- if ($ this ->idReaders [$ hash ]->isSingleId ()) {
234
- return $ this ->idReaders [$ hash ];
235
- }
236
-
237
- return null ;
207
+ return $ this ->getCachedIdReader ($ options ['em ' ], $ options ['class ' ]);
238
208
};
239
209
240
210
$ resolver ->setDefaults ([
241
211
'em ' => null ,
242
212
'query_builder ' => null ,
243
213
'choices ' => null ,
244
214
'choice_loader ' => $ choiceLoader ,
245
- 'choice_label ' => [__CLASS__ , 'createChoiceLabel ' ],
215
+ 'choice_label ' => ChoiceList:: label ( $ this , [__CLASS__ , 'createChoiceLabel ' ]) ,
246
216
'choice_name ' => $ choiceName ,
247
217
'choice_value ' => $ choiceValue ,
248
218
'id_reader ' => null , // internal
@@ -274,6 +244,27 @@ public function getParent()
274
244
275
245
public function reset ()
276
246
{
277
- $ this ->choiceLoaders = [];
247
+ $ this ->entityLoaders = [];
248
+ }
249
+
250
+ private function getCachedIdReader (ObjectManager $ manager , string $ class ): ?IdReader
251
+ {
252
+ $ hash = CachingFactoryDecorator::generateHash ([$ manager , $ class ]);
253
+
254
+ if (isset ($ this ->idReaders [$ hash ])) {
255
+ return $ this ->idReaders [$ hash ];
256
+ }
257
+
258
+ $ idReader = new IdReader ($ manager , $ manager ->getClassMetadata ($ class ));
259
+
260
+ // don't cache the instance for composite ids that cannot be optimized
261
+ return $ this ->idReaders [$ hash ] = $ idReader ->isSingleId () ? $ idReader : null ;
262
+ }
263
+
264
+ private function getCachedEntityLoader (ObjectManager $ manager , $ queryBuilder , string $ class , array $ vary ): EntityLoaderInterface
265
+ {
266
+ $ hash = CachingFactoryDecorator::generateHash ($ vary );
267
+
268
+ return $ this ->entityLoaders [$ hash ] ?? ($ this ->entityLoaders [$ hash ] = $ this ->getLoader ($ manager , $ queryBuilder , $ class ));
278
269
}
279
270
}
0 commit comments