2020use Symfony \Bridge \Doctrine \Form \DataTransformer \CollectionToArrayTransformer ;
2121use Symfony \Bridge \Doctrine \Form \EventListener \MergeDoctrineCollectionListener ;
2222use Symfony \Component \Form \AbstractType ;
23+ use Symfony \Component \Form \ChoiceList \ChoiceList ;
2324use Symfony \Component \Form \ChoiceList \Factory \CachingFactoryDecorator ;
2425use Symfony \Component \Form \Exception \RuntimeException ;
2526use Symfony \Component \Form \FormBuilderInterface ;
@@ -40,9 +41,9 @@ abstract class DoctrineType extends AbstractType implements ResetInterface
4041 private $ idReaders = [];
4142
4243 /**
43- * @var DoctrineChoiceLoader []
44+ * @var EntityLoaderInterface []
4445 */
45- private $ choiceLoaders = [];
46+ private $ entityLoaders = [];
4647
4748 /**
4849 * Creates the label for a choice.
@@ -115,43 +116,26 @@ public function configureOptions(OptionsResolver $resolver)
115116 $ choiceLoader = function (Options $ options ) {
116117 // Unless the choices are given explicitly, load them on demand
117118 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 ' ]] ;
120121
121- // If there is no QueryBuilder we can safely cache DoctrineChoiceLoader,
122122 // 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 ' ];
134126 }
135127
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 (
144129 $ options ['em ' ],
145130 $ options ['class ' ],
146131 $ 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 );
155139 }
156140
157141 return null ;
@@ -162,7 +146,7 @@ public function configureOptions(OptionsResolver $resolver)
162146 // field name. We can only use numeric IDs as names, as we cannot
163147 // guarantee that a non-numeric ID contains a valid form name
164148 if ($ options ['id_reader ' ] instanceof IdReader && $ options ['id_reader ' ]->isIntId ()) {
165- return [__CLASS__ , 'createChoiceName ' ];
149+ return ChoiceList:: fieldName ( $ this , [__CLASS__ , 'createChoiceName ' ]) ;
166150 }
167151
168152 // Otherwise, an incrementing integer is used as name automatically
@@ -176,7 +160,7 @@ public function configureOptions(OptionsResolver $resolver)
176160 $ choiceValue = function (Options $ options ) {
177161 // If the entity has a single-column ID, use that ID as value
178162 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 ' ]) ;
180164 }
181165
182166 // Otherwise, an incrementing integer is used as value automatically
@@ -214,35 +198,21 @@ public function configureOptions(OptionsResolver $resolver)
214198 // Set the "id_reader" option via the normalizer. This option is not
215199 // supposed to be set by the user.
216200 $ idReaderNormalizer = function (Options $ options ) {
217- $ hash = CachingFactoryDecorator::generateHash ([
218- $ options ['em ' ],
219- $ options ['class ' ],
220- ]);
221-
222201 // The ID reader is a utility that is needed to read the object IDs
223202 // when generating the field values. The callback generating the
224203 // field values has no access to the object manager or the class
225204 // of the field, so we store that information in the reader.
226205 // The reader is cached so that two choice lists for the same class
227206 // (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 ' ]);
238208 };
239209
240210 $ resolver ->setDefaults ([
241211 'em ' => null ,
242212 'query_builder ' => null ,
243213 'choices ' => null ,
244214 'choice_loader ' => $ choiceLoader ,
245- 'choice_label ' => [__CLASS__ , 'createChoiceLabel ' ],
215+ 'choice_label ' => ChoiceList:: label ( $ this , [__CLASS__ , 'createChoiceLabel ' ]) ,
246216 'choice_name ' => $ choiceName ,
247217 'choice_value ' => $ choiceValue ,
248218 'id_reader ' => null , // internal
@@ -274,6 +244,27 @@ public function getParent()
274244
275245 public function reset ()
276246 {
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 ));
278269 }
279270}
0 commit comments