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

Skip to content

Commit 288d72a

Browse files
committed
[Form] Refactored choice lists to support dynamic label, value, index and attribute generation
1 parent a7d52fd commit 288d72a

File tree

54 files changed

+5663
-433
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+5663
-433
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
* A choice list presenting a list of Doctrine entities as choices
2222
*
2323
* @author Bernhard Schussek <[email protected]>
24+
*
25+
* @deprecated Deprecated since Symfony 2.7, to be removed in Symfony 3.0.
26+
* Use {@link EntityChoiceLoader} instead.
2427
*/
2528
class EntityChoiceList extends ObjectChoiceList
2629
{
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\Doctrine\Form\ChoiceList;
13+
14+
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
15+
use Doctrine\Common\Persistence\ObjectManager;
16+
use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
17+
use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface;
18+
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
19+
use Symfony\Component\Form\Exception\RuntimeException;
20+
21+
/**
22+
* Loads choices using a Doctrine object manager.
23+
*
24+
* @author Bernhard Schussek <[email protected]>
25+
*/
26+
class EntityChoiceLoader implements ChoiceLoaderInterface
27+
{
28+
/**
29+
* @var ChoiceListFactoryInterface
30+
*/
31+
private $factory;
32+
33+
/**
34+
* @var ObjectManager
35+
*/
36+
private $manager;
37+
38+
/**
39+
* @var string
40+
*/
41+
private $class;
42+
43+
/**
44+
* @var ClassMetadata
45+
*/
46+
private $classMetadata;
47+
48+
/**
49+
* @var null|EntityLoaderInterface
50+
*/
51+
private $entityLoader;
52+
53+
/**
54+
* The identifier field, unless the identifier is composite
55+
*
56+
* @var null|string
57+
*/
58+
private $idField = null;
59+
60+
/**
61+
* Whether to use the identifier for value generation
62+
*
63+
* @var bool
64+
*/
65+
private $compositeId = true;
66+
67+
/**
68+
* @var ChoiceListInterface
69+
*/
70+
private $choiceList;
71+
72+
/**
73+
* Returns the value of the identifier field of an entity.
74+
*
75+
* Doctrine must know about this entity, that is, the entity must already
76+
* be persisted or added to the identity map before. Otherwise an
77+
* exception is thrown.
78+
*
79+
* This method assumes that the entity has a single-column identifier and
80+
* will return a single value instead of an array.
81+
*
82+
* @param object $object The entity for which to get the identifier
83+
*
84+
* @return int|string The identifier value
85+
*
86+
* @throws RuntimeException If the entity does not exist in Doctrine's identity map
87+
*
88+
* @internal Should not be accessed by user-land code. This method is public
89+
* only to be usable as callback.
90+
*/
91+
public static function getIdValue(ObjectManager $om, ClassMetadata $classMetadata, $object)
92+
{
93+
if (!$om->contains($object)) {
94+
throw new RuntimeException(
95+
'Entities passed to the choice field must be managed. Maybe '.
96+
'persist them in the entity manager?'
97+
);
98+
}
99+
100+
$om->initializeObject($object);
101+
102+
return current($classMetadata->getIdentifierValues($object));
103+
}
104+
105+
/**
106+
* Creates a new choice loader.
107+
*
108+
* Optionally, an implementation of {@link EntityLoaderInterface} can be
109+
* passed which optimizes the entity loading for one of the Doctrine
110+
* mapper implementations.
111+
*
112+
* @param ChoiceListFactoryInterface $factory The factory for creating
113+
* the loaded choice list
114+
* @param ObjectManager $manager The object manager
115+
* @param string $class The entity class name
116+
* @param null|EntityLoaderInterface $entityLoader The entity loader
117+
*/
118+
public function __construct(ChoiceListFactoryInterface $factory, ObjectManager $manager, $class, EntityLoaderInterface $entityLoader = null)
119+
{
120+
$this->factory = $factory;
121+
$this->manager = $manager;
122+
$this->classMetadata = $manager->getClassMetadata($class);
123+
$this->class = $this->classMetadata->getName();
124+
$this->entityLoader = $entityLoader;
125+
126+
$identifier = $this->classMetadata->getIdentifierFieldNames();
127+
128+
if (1 === count($identifier)) {
129+
$this->idField = $identifier[0];
130+
$this->compositeId = false;
131+
}
132+
}
133+
134+
/**
135+
* {@inheritdoc}
136+
*/
137+
public function loadChoiceList($value = null)
138+
{
139+
if ($this->choiceList) {
140+
return $this->choiceList;
141+
}
142+
143+
$entities = $this->entityLoader
144+
? $this->entityLoader->getEntities()
145+
: $this->manager->getRepository($this->class)->findAll();
146+
147+
// If the class has a multi-column identifier, we cannot index the
148+
// entities by their IDs
149+
if ($this->compositeId) {
150+
$this->choiceList = $this->factory->createListFromChoices($entities, $value);
151+
152+
return $this->choiceList;
153+
}
154+
155+
// Index the entities by ID
156+
$entitiesById = array();
157+
158+
foreach ($entities as $entity) {
159+
$id = self::getIdValue($this->manager, $this->classMetadata, $entity);
160+
$entitiesById[$id] = $entity;
161+
}
162+
163+
$this->choiceList = $this->factory->createListFromChoices($entitiesById, $value);
164+
165+
return $this->choiceList;
166+
}
167+
168+
/**
169+
* Loads the values corresponding to the given entities.
170+
*
171+
* The values are returned with the same keys and in the same order as the
172+
* corresponding entities in the given array.
173+
*
174+
* 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+
* argument.
177+
*
178+
* @param array $entities An array of entities. Non-existing entities
179+
* in this array are ignored
180+
* @param null|callable $value The callable generating the choice values
181+
*
182+
* @return string[] An array of choice values
183+
*/
184+
public function loadValuesForChoices(array $entities, $value = null)
185+
{
186+
// Performance optimization
187+
if (empty($entities)) {
188+
return array();
189+
}
190+
191+
// Optimize performance for single-field identifiers. We already
192+
// know that the IDs are used as values
193+
194+
// Attention: This optimization does not check choices for existence
195+
if (!$this->choiceList && !$this->compositeId) {
196+
$values = array();
197+
198+
// Maintain order and indices of the given entities
199+
foreach ($entities as $i => $entity) {
200+
if ($entity instanceof $this->class) {
201+
// Make sure to convert to the right format
202+
$values[$i] = (string) self::getIdValue($this->manager, $this->classMetadata, $entity);
203+
}
204+
}
205+
206+
return $values;
207+
}
208+
209+
return $this->loadChoiceList($value)->getValuesForChoices($entities);
210+
}
211+
212+
/**
213+
* Loads the entities corresponding to the given values.
214+
*
215+
* The entities are returned with the same keys and in the same order as the
216+
* corresponding values in the given array.
217+
*
218+
* 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+
* argument.
221+
*
222+
* @param string[] $values An array of choice values. Non-existing
223+
* values in this array are ignored
224+
* @param null|callable $value The callable generating the choice values
225+
*
226+
* @return array An array of entities
227+
*/
228+
public function loadChoicesForValues(array $values, $value = null)
229+
{
230+
// Performance optimization
231+
// Also prevents the generation of "WHERE id IN ()" queries through the
232+
// entity loader. At least with MySQL and on the development machine
233+
// this was tested on, no exception was thrown for such invalid
234+
// statements, consequently no test fails when this code is removed.
235+
// https://github.com/symfony/symfony/pull/8981#issuecomment-24230557
236+
if (empty($values)) {
237+
return array();
238+
}
239+
240+
// Optimize performance in case we have an entity loader and
241+
// 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();
246+
247+
// Maintain order and indices from the given $values
248+
// An alternative approach to the following loop is to add the
249+
// "INDEX BY" clause to the Doctrine query in the loader,
250+
// 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;
254+
}
255+
256+
foreach ($values as $i => $id) {
257+
if (isset($entitiesById[$id])) {
258+
$entities[$i] = $entitiesById[$id];
259+
}
260+
}
261+
262+
return $entities;
263+
}
264+
265+
return $this->loadChoiceList($value)->getChoicesForValues($values);
266+
}
267+
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@
1717
use Doctrine\ORM\EntityManager;
1818

1919
/**
20-
* Getting Entities through the ORM QueryBuilder
20+
* Loads entities using a {@link QueryBuilder} instance.
21+
*
22+
* @author Benjamin Eberlei <[email protected]>
23+
* @author Bernhard Schussek <[email protected]>
2124
*/
2225
class ORMQueryBuilderLoader implements EntityLoaderInterface
2326
{

src/Symfony/Bridge/Doctrine/Form/DoctrineOrmExtension.php

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,38 @@
1313

1414
use Doctrine\Common\Persistence\ManagerRegistry;
1515
use Symfony\Component\Form\AbstractExtension;
16+
use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator;
17+
use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface;
18+
use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
19+
use Symfony\Component\Form\ChoiceList\Factory\PropertyAccessDecorator;
1620
use Symfony\Component\PropertyAccess\PropertyAccess;
21+
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
1722

1823
class DoctrineOrmExtension extends AbstractExtension
1924
{
2025
protected $registry;
2126

22-
public function __construct(ManagerRegistry $registry)
27+
/**
28+
* @var PropertyAccessorInterface
29+
*/
30+
private $propertyAccessor;
31+
32+
/**
33+
* @var ChoiceListFactoryInterface
34+
*/
35+
private $choiceListFactory;
36+
37+
public function __construct(ManagerRegistry $registry, PropertyAccessorInterface $propertyAccessor = null, ChoiceListFactoryInterface $choiceListFactory = null)
2338
{
2439
$this->registry = $registry;
40+
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
41+
$this->choiceListFactory = $choiceListFactory ?: new CachingFactoryDecorator(new PropertyAccessDecorator(new DefaultChoiceListFactory(), $this->propertyAccessor));
2542
}
2643

2744
protected function loadTypes()
2845
{
2946
return array(
30-
new Type\EntityType($this->registry, PropertyAccess::createPropertyAccessor()),
47+
new Type\EntityType($this->registry, $this->propertyAccessor, $this->choiceListFactory),
3148
);
3249
}
3350

0 commit comments

Comments
 (0)