Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 3846b37

Browse files
committed
[DoctrineBridge] Fixed: don't cache choice lists if query builders are constructed dynamically
1 parent 03efce1 commit 3846b37

File tree

4 files changed

+96
-133
lines changed

4 files changed

+96
-133
lines changed

src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceLoader.php renamed to src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php

Lines changed: 51 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
*
2424
* @author Bernhard Schussek <[email protected]>
2525
*/
26-
class EntityChoiceLoader implements ChoiceLoaderInterface
26+
class DoctrineChoiceLoader implements ChoiceLoaderInterface
2727
{
2828
/**
2929
* @var ChoiceListFactoryInterface
@@ -48,7 +48,7 @@ class EntityChoiceLoader implements ChoiceLoaderInterface
4848
/**
4949
* @var null|EntityLoaderInterface
5050
*/
51-
private $entityLoader;
51+
private $objectLoader;
5252

5353
/**
5454
* The identifier field, unless the identifier is composite
@@ -70,20 +70,20 @@ class EntityChoiceLoader implements ChoiceLoaderInterface
7070
private $choiceList;
7171

7272
/**
73-
* Returns the value of the identifier field of an entity.
73+
* Returns the value of the identifier field of an object.
7474
*
75-
* Doctrine must know about this entity, that is, the entity must already
75+
* Doctrine must know about this object, that is, the object must already
7676
* be persisted or added to the identity map before. Otherwise an
7777
* exception is thrown.
7878
*
79-
* This method assumes that the entity has a single-column identifier and
79+
* This method assumes that the object has a single-column identifier and
8080
* will return a single value instead of an array.
8181
*
82-
* @param object $object The entity for which to get the identifier
82+
* @param object $object The object for which to get the identifier
8383
*
8484
* @return int|string The identifier value
8585
*
86-
* @throws RuntimeException If the entity does not exist in Doctrine's identity map
86+
* @throws RuntimeException If the object does not exist in Doctrine's identity map
8787
*
8888
* @internal Should not be accessed by user-land code. This method is public
8989
* only to be usable as callback.
@@ -106,22 +106,23 @@ public static function getIdValue(ObjectManager $om, ClassMetadata $classMetadat
106106
* Creates a new choice loader.
107107
*
108108
* Optionally, an implementation of {@link EntityLoaderInterface} can be
109-
* passed which optimizes the entity loading for one of the Doctrine
109+
* passed which optimizes the object loading for one of the Doctrine
110110
* mapper implementations.
111111
*
112112
* @param ChoiceListFactoryInterface $factory The factory for creating
113113
* the loaded choice list
114114
* @param ObjectManager $manager The object manager
115-
* @param string $class The entity class name
116-
* @param null|EntityLoaderInterface $entityLoader The entity loader
115+
* @param string $class The class name of the
116+
* loaded objects
117+
* @param null|EntityLoaderInterface $objectLoader The objects loader
117118
*/
118-
public function __construct(ChoiceListFactoryInterface $factory, ObjectManager $manager, $class, EntityLoaderInterface $entityLoader = null)
119+
public function __construct(ChoiceListFactoryInterface $factory, ObjectManager $manager, $class, EntityLoaderInterface $objectLoader = null)
119120
{
120121
$this->factory = $factory;
121122
$this->manager = $manager;
122123
$this->classMetadata = $manager->getClassMetadata($class);
123124
$this->class = $this->classMetadata->getName();
124-
$this->entityLoader = $entityLoader;
125+
$this->objectLoader = $objectLoader;
125126

126127
$identifier = $this->classMetadata->getIdentifierFieldNames();
127128

@@ -140,51 +141,51 @@ public function loadChoiceList($value = null)
140141
return $this->choiceList;
141142
}
142143

143-
$entities = $this->entityLoader
144-
? $this->entityLoader->getEntities()
144+
$objects = $this->objectLoader
145+
? $this->objectLoader->getEntities()
145146
: $this->manager->getRepository($this->class)->findAll();
146147

147148
// If the class has a multi-column identifier, we cannot index the
148-
// entities by their IDs
149+
// objects by their IDs
149150
if ($this->compositeId) {
150-
$this->choiceList = $this->factory->createListFromChoices($entities, $value);
151+
$this->choiceList = $this->factory->createListFromChoices($objects, $value);
151152

152153
return $this->choiceList;
153154
}
154155

155-
// Index the entities by ID
156-
$entitiesById = array();
156+
// Index the objects by ID
157+
$objectsById = array();
157158

158-
foreach ($entities as $entity) {
159-
$id = self::getIdValue($this->manager, $this->classMetadata, $entity);
160-
$entitiesById[$id] = $entity;
159+
foreach ($objects as $object) {
160+
$id = self::getIdValue($this->manager, $this->classMetadata, $object);
161+
$objectsById[$id] = $object;
161162
}
162163

163-
$this->choiceList = $this->factory->createListFromChoices($entitiesById, $value);
164+
$this->choiceList = $this->factory->createListFromChoices($objectsById, $value);
164165

165166
return $this->choiceList;
166167
}
167168

168169
/**
169-
* Loads the values corresponding to the given entities.
170+
* Loads the values corresponding to the given objects.
170171
*
171172
* The values are returned with the same keys and in the same order as the
172-
* corresponding entities in the given array.
173+
* corresponding objects in the given array.
173174
*
174175
* Optionally, a callable can be passed for generating the choice values.
175-
* The callable receives the entity as first and the array key as the second
176+
* The callable receives the object as first and the array key as the second
176177
* argument.
177178
*
178-
* @param array $entities An array of entities. Non-existing entities
179-
* in this array are ignored
179+
* @param array $objects An array of objects. Non-existing objects in
180+
* this array are ignored
180181
* @param null|callable $value The callable generating the choice values
181182
*
182183
* @return string[] An array of choice values
183184
*/
184-
public function loadValuesForChoices(array $entities, $value = null)
185+
public function loadValuesForChoices(array $objects, $value = null)
185186
{
186187
// Performance optimization
187-
if (empty($entities)) {
188+
if (empty($objects)) {
188189
return array();
189190
}
190191

@@ -195,71 +196,71 @@ public function loadValuesForChoices(array $entities, $value = null)
195196
if (!$this->choiceList && !$this->compositeId) {
196197
$values = array();
197198

198-
// Maintain order and indices of the given entities
199-
foreach ($entities as $i => $entity) {
200-
if ($entity instanceof $this->class) {
199+
// Maintain order and indices of the given objects
200+
foreach ($objects as $i => $object) {
201+
if ($object instanceof $this->class) {
201202
// Make sure to convert to the right format
202-
$values[$i] = (string) self::getIdValue($this->manager, $this->classMetadata, $entity);
203+
$values[$i] = (string) self::getIdValue($this->manager, $this->classMetadata, $object);
203204
}
204205
}
205206

206207
return $values;
207208
}
208209

209-
return $this->loadChoiceList($value)->getValuesForChoices($entities);
210+
return $this->loadChoiceList($value)->getValuesForChoices($objects);
210211
}
211212

212213
/**
213-
* Loads the entities corresponding to the given values.
214+
* Loads the objects corresponding to the given values.
214215
*
215-
* The entities are returned with the same keys and in the same order as the
216+
* The objects are returned with the same keys and in the same order as the
216217
* corresponding values in the given array.
217218
*
218219
* Optionally, a callable can be passed for generating the choice values.
219-
* The callable receives the entity as first and the array key as the second
220+
* The callable receives the object as first and the array key as the second
220221
* argument.
221222
*
222223
* @param string[] $values An array of choice values. Non-existing
223224
* values in this array are ignored
224225
* @param null|callable $value The callable generating the choice values
225226
*
226-
* @return array An array of entities
227+
* @return array An array of objects
227228
*/
228229
public function loadChoicesForValues(array $values, $value = null)
229230
{
230231
// Performance optimization
231232
// Also prevents the generation of "WHERE id IN ()" queries through the
232-
// entity loader. At least with MySQL and on the development machine
233+
// object loader. At least with MySQL and on the development machine
233234
// this was tested on, no exception was thrown for such invalid
234235
// statements, consequently no test fails when this code is removed.
235236
// https://github.com/symfony/symfony/pull/8981#issuecomment-24230557
236237
if (empty($values)) {
237238
return array();
238239
}
239240

240-
// Optimize performance in case we have an entity loader and
241+
// Optimize performance in case we have an object loader and
241242
// a single-field identifier
242-
if (!$this->choiceList && !$this->compositeId && $this->entityLoader) {
243-
$unorderedEntities = $this->entityLoader->getEntitiesByIds($this->idField, $values);
244-
$entitiesById = array();
245-
$entities = array();
243+
if (!$this->choiceList && !$this->compositeId && $this->objectLoader) {
244+
$unorderedObjects = $this->objectLoader->getEntitiesByIds($this->idField, $values);
245+
$objectsById = array();
246+
$objects = array();
246247

247248
// Maintain order and indices from the given $values
248249
// An alternative approach to the following loop is to add the
249250
// "INDEX BY" clause to the Doctrine query in the loader,
250251
// but I'm not sure whether that's doable in a generic fashion.
251-
foreach ($unorderedEntities as $entity) {
252-
$id = self::getIdValue($this->manager, $this->classMetadata, $entity);
253-
$entitiesById[$id] = $entity;
252+
foreach ($unorderedObjects as $object) {
253+
$id = self::getIdValue($this->manager, $this->classMetadata, $object);
254+
$objectsById[$id] = $object;
254255
}
255256

256257
foreach ($values as $i => $id) {
257-
if (isset($entitiesById[$id])) {
258-
$entities[$i] = $entitiesById[$id];
258+
if (isset($objectsById[$id])) {
259+
$objects[$i] = $objectsById[$id];
259260
}
260261
}
261262

262-
return $entities;
263+
return $objects;
263264
}
264265

265266
return $this->loadChoiceList($value)->getChoicesForValues($values);

src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
* @author Bernhard Schussek <[email protected]>
2525
*
2626
* @deprecated Deprecated since Symfony 2.7, to be removed in Symfony 3.0.
27-
* Use {@link EntityChoiceLoader} instead.
27+
* Use {@link DoctrineChoiceLoader} instead.
2828
*/
2929
class EntityChoiceList extends ObjectChoiceList
3030
{

src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
use Doctrine\Common\Persistence\ManagerRegistry;
1515
use Doctrine\Common\Persistence\ObjectManager;
16-
use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceLoader;
16+
use Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader;
1717
use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityLoaderInterface;
1818
use Symfony\Bridge\Doctrine\Form\DataTransformer\CollectionToArrayTransformer;
1919
use Symfony\Bridge\Doctrine\Form\EventListener\MergeDoctrineCollectionListener;
@@ -41,7 +41,7 @@ abstract class DoctrineType extends AbstractType
4141
private $choiceListFactory;
4242

4343
/**
44-
* @var EntityChoiceLoader[]
44+
* @var DoctrineChoiceLoader[]
4545
*/
4646
private $choiceLoaders = array();
4747

@@ -71,32 +71,43 @@ public function configureOptions(OptionsResolver $resolver)
7171
$choiceLoader = function (Options $options) use ($choiceListFactory, &$choiceLoaders, $type) {
7272
// Unless the choices are given explicitly, load them on demand
7373
if (null === $options['choices']) {
74-
$hash = CachingFactoryDecorator::generateHash(array(
75-
$options['em'],
76-
$options['class'],
77-
$options['query_builder'],
78-
$options['loader'],
79-
));
74+
// Don't cache if the query builder is constructed dynamically
75+
if ($options['query_builder'] instanceof \Closure) {
76+
$hash = null;
77+
} else {
78+
$hash = CachingFactoryDecorator::generateHash(array(
79+
$options['em'],
80+
$options['class'],
81+
$options['query_builder'],
82+
$options['loader'],
83+
));
8084

81-
if (!isset($choiceLoaders[$hash])) {
82-
if ($options['loader']) {
83-
$loader = $options['loader'];
84-
} elseif (null !== $options['query_builder']) {
85-
$loader = $type->getLoader($options['em'], $options['query_builder'], $options['class']);
86-
} else {
87-
$queryBuilder = $options['em']->getRepository($options['class'])->createQueryBuilder('e');
88-
$loader = $type->getLoader($options['em'], $queryBuilder, $options['class']);
85+
if (isset($choiceLoaders[$hash])) {
86+
return $choiceLoaders[$hash];
8987
}
88+
}
9089

91-
$choiceLoaders[$hash] = new EntityChoiceLoader(
92-
$choiceListFactory,
93-
$options['em'],
94-
$options['class'],
95-
$loader
96-
);
90+
if ($options['loader']) {
91+
$entityLoader = $options['loader'];
92+
} elseif (null !== $options['query_builder']) {
93+
$entityLoader = $type->getLoader($options['em'], $options['query_builder'], $options['class']);
94+
} else {
95+
$queryBuilder = $options['em']->getRepository($options['class'])->createQueryBuilder('e');
96+
$entityLoader = $type->getLoader($options['em'], $queryBuilder, $options['class']);
97+
}
98+
99+
$choiceLoader = new DoctrineChoiceLoader(
100+
$choiceListFactory,
101+
$options['em'],
102+
$options['class'],
103+
$entityLoader
104+
);
105+
106+
if (null !== $hash) {
107+
$choiceLoaders[$hash] = $choiceLoader;
97108
}
98109

99-
return $choiceLoaders[$hash];
110+
return $choiceLoader;
100111
}
101112
};
102113

@@ -131,7 +142,7 @@ public function configureOptions(OptionsResolver $resolver)
131142
};
132143

133144
// The choices are always indexed by ID (see "choices" normalizer
134-
// and EntityChoiceLoader), unless the ID is composite. Then they
145+
// and DoctrineChoiceLoader), unless the ID is composite. Then they
135146
// are indexed by an incrementing integer.
136147
// Use the ID/incrementing integer as choice value.
137148
$choiceValue = function ($entity, $key) {
@@ -181,7 +192,7 @@ public function configureOptions(OptionsResolver $resolver)
181192
$entitiesById = array();
182193

183194
foreach ($entities as $entity) {
184-
$id = EntityChoiceLoader::getIdValue($om, $classMetadata, $entity);
195+
$id = DoctrineChoiceLoader::getIdValue($om, $classMetadata, $entity);
185196
$entitiesById[$id] = $entity;
186197
}
187198

0 commit comments

Comments
 (0)