8000 [DoctrineBridge] Refactor entity choice list to be ORM independant us… · symfony/symfony@517eebc · GitHub
[go: up one dir, main page]

Skip to content

Commit 517eebc

Browse files
beberleistof
authored andcommitted
[DoctrineBridge] Refactor entity choice list to be ORM independant using an EntityLoader interface.
1 parent e6e78f6 commit 517eebc

File tree

4 files changed

+156
-60
lines changed

4 files changed

+156
-60
lines changed

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

Lines changed: 21 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
use Symfony\Component\Form\Exception\FormException;
1616
use Symfony\Component\Form\Exception\UnexpectedTypeException;
1717
use Symfony\Component\Form\Extension\Core\ChoiceList\ArrayChoiceList;
18-
use Doctrine\ORM\EntityManager;
18+
use Doctrine\Common\Persistence\ObjectManager;
1919
use Doctrine\ORM\QueryBuilder;
2020
use Doctrine\ORM\NoResultException;
2121

@@ -52,7 +52,7 @@ class EntityChoiceList extends ArrayChoiceList
5252
*
5353
* @var Doctrine\ORM\QueryBuilder
5454
*/
55-
private $queryBuilder;
55+
private $entityLoader;
5656

5757
/**
5858
* The fields of which the identifier of the underlying class consists
@@ -63,22 +63,18 @@ class EntityChoiceList extends ArrayChoiceList
6363
*/
6464
private $identifier = array();
6565

66-
/**
67-
* A cache for \ReflectionProperty instances for the underlying class
68-
*
69-
* This property should only be accessed through getReflProperty().
70-
*
71-
* @var array
72-
*/
73-
private $reflProperties = array();
74-
7566
/**
7667
* A cache for the UnitOfWork instance of Doctrine
7768
*
7869
* @var Doctrine\ORM\UnitOfWork
7970
*/
8071
private $unitOfWork;
8172

73+
/**
74+
* Property path to access the key value of this choice-list.
75+
*
76+
* @var PropertyPath
77+
*/
8278
private $propertyPath;
8379

8480
/**
@@ -91,33 +87,20 @@ class EntityChoiceList extends ArrayChoiceList
9187
/**
9288
* Constructor.
9389
*
94-
* @param EntityManager $em An EntityManager instance
90+
* @param ObjectManager $manager An EntityManager instance
9591
* @param string $class The class name
9692
* @param string $property The property name
97-
* @param QueryBuilder|\Closure $queryBuilder An optional query builder
93+
* @param EntityLoaderInterface $entityLoader An optional query builder
9894
* @param array|\Closure $choices An array of choices or a function returning an array
95+
* @param string $groupBy
9996
*/
100-
public function __construct(EntityManager $em, $class, $property = null, $queryBuilder = null, $choices = null, $groupBy = null)
97+
public function __construct(ObjectManager $manager, $class, $property = null, EntityLoaderInterface $entityLoader = null, $choices = null, $groupBy = null)
10198
{
102-
// If a query builder was passed, it must be a closure or QueryBuilder
103-
// instance
104-
if (!(null === $queryBuilder || $queryBuilder instanceof QueryBuilder || $queryBuilder instanceof \Closure)) {
105-
throw new UnexpectedTypeException($queryBuilder, 'Doctrine\ORM\QueryBuilder or \Closure');
106-
}
107-
108-
if ($queryBuilder instanceof \Closure) {
109-
$queryBuilder = $queryBuilder($em->getRepository($class));
110-
111-
if (!$queryBuilder instanceof QueryBuilder) {
112-
throw new UnexpectedTypeException($queryBuilder, 'Doctrine\ORM\QueryBuilder');
113-
}
114-
}
115-
116-
$this->em = $em;
99+
$this->em = $manager;
117100
$this->class = $class;
118-
$this->queryBuilder = $queryBuilder;
119-
$this->unitOfWork = $em->getUnitOfWork();
120-
$this->identifier = $em->getClassMetadata($class)->getIdentifierFieldNames();
101+
$this->entityLoader = $entityLoader;
102+
$this->unitOfWork = $manager->getUnitOfWork();
103+
$this->identifier = $manager->getClassMetadata($class)->getIdentifierFieldNames();
121104
$this->groupBy = $groupBy;
122105

123106
// The property option defines, which property (path) is used for
@@ -150,8 +133,8 @@ protected function load()
150133

151134
if (is_array($this->choices)) {
152135
$entities = $this->choices;
153-
} elseif ($qb = $this->queryBuilder) {
154-
$entities = $qb->getQuery()->execute();
136+
} else if ($entityLoader = $this->entityLoader) {
137+
$entities = $entityLoader->getEntities();
155138
} else {
156139
$entities = $this->em->getRepository($this->class)->findAll();
157140
}
@@ -171,11 +154,11 @@ protected function load()
171154
private function groupEntities($entities, $groupBy)
172155
{
173156
$grouped = array();
157+
$path = new PropertyPath($groupBy);
174158

175159
foreach ($entities as $entity) {
176160
// Get group name from property path
177161
try {
178-
$path = new PropertyPath($groupBy);
179162
$group = (string) $path->getValue($entity);
180163
} catch (UnexpectedTypeException $e) {
181164
// PropertyPath cannot traverse entity
@@ -307,12 +290,9 @@ public function getEntity($key)
307290
return isset($entities[$key]) ? $entities[$key] : null;
308291
} elseif ($this->entities) {
309292
return isset($this->entities[$key]) ? $this->entities[$key] : null;
310-
} elseif ($qb = $this->queryBuilder) {
311-
// should we clone the builder?
312-
$alias = $qb->getRootAlias();
313-
$where = $qb->expr()->eq($alias.'.'.current($this->identifier), $key);
314-
315-
return $qb->andWhere($where)->getQuery()->getSingleResult();
293+
} else if ($entityLoader = $this->entityLoader) {
294+
$entities = $entityLoader->getEntitiesByKeys($this->identifier, array($key));
295+
return (isset($entities[0])) ? $entities[0] : false;
316296
}
317297

318298
return $this->em->find($this->class, $key);
@@ -321,23 +301,6 @@ public function getEntity($key)
321301
}
322302
}
323303

324-
/**
325-
* Returns the \ReflectionProperty instance for a property of the underlying class.
326-
*
327-
* @param string $property The name of the property
328-
*
329-
* @return \ReflectionProperty The reflection instance
330-
*/
331-
private function getReflProperty($property)
332-
{
333-
if (!isset($this->reflProperties[$property])) {
334-
$this->reflProperties[$property] = new \ReflectionProperty($this->class, $property);
335-
$this->reflProperties[$property]->setAccessible(true);
336-
}
337-
338-
return $this->reflProperties[$property];
339-
}
340-
341304
/**
342305
* Returns the values of the identifier fields of an entity.
343306
*
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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+
/**
15+
* Custom loader for entities in the choice list.
16+
*
17+
* @author Benjamin Eberlei <kontakt@beberlei.de>
18+
*/
19+
interface EntityLoaderInterface
20+
{
21+
/**
22+
* Given choice list values this method returns the appropriate entities for it.
23+
*
24+
* @param array $identifier
25+
* @param array $choiceListKeys Array of values of the select option, checkbox or radio button.
26+
* @return object[]
27+
*/
28+
function getEntitiesByKeys(array $identifier, array $choiceListKeys);
29+
30+
/**
31+
* Return an array of entities that are valid choices in the corresponding choice list.
32+
*
33+
* @return array
34+
*/
35+
function getEntities();
36+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\DBAL\Connection;
15+
use Symfony\Component\Form\Exception\FormException;
16+
use Symfony\Component\Form\Exception\UnexpectedTypeException;
17+
use Doctrine\ORM\QueryBuilder;
18+
19+
/**
20+
* Getting Entities through the ORM QueryBuilder
21+
*/
22+
class ORMQueryBuilderLoader implements EntityLoaderInterface
23+
{
24+
/**
25+
* Contains the query builder that builds the query for fetching the
26+
* entities
27+
*
28+
* This property should only be accessed through queryBuilder.
29+
*
30+
* @var Doctrine\ORM\QueryBuilder
31+
*/
32+
private $queryBuilder;
33+
34+
/**
35+
* Construct an ORM Query Builder Loader
36+
*
37+
* @param QueryBuilder $queryBuilder
38+
* @param EntityManager $manager
39+
* @param string $class
40+
*/
41+
public function __construct($queryBuilder, $manager = null, $class = null)
42+
{
43+
// If a query builder was passed, it must be a closure or QueryBuilder
44+
// instance
45+
if (!(null === $queryBuilder || $queryBuilder instanceof QueryBuilder || $queryBuilder instanceof \Closure)) {
46+
throw new UnexpectedTypeException($queryBuilder, 'Doctrine\ORM\QueryBuilder or \Closure');
47+
}
48+
49+
if ($queryBuilder instanceof \Closure) {
50+
$queryBuilder = $queryBuilder($manager->getRepository($class));
51+
52+
if (!$queryBuilder instanceof QueryBuilder) {
53+
throw new UnexpectedTypeException($queryBuilder, 'Doctrine\ORM\QueryBuilder');
54+
}
55+
}
56+
57+
$this->queryBuilder = $queryBuilder;
58+
}
59+
60+
/**
61+
* {@inheritDoc}
62+
*/
63+
public function getEntities()
64+
{
65+
return $this->queryBuilder->getQuery()->execute();
66+
}
67+
68+
/**
69+
* {@inheritDoc}
70+
*/
71+
public function getEntitiesByKeys(array $identifier, array $choiceListKeys)
72+
{
73+
if (count($identifier) != 1) {
74+
throw new FormException("Only entities with one identifier supported by ORM QueryBuilder.");
75+
}
76+
77+
$qb = clone ($this->queryBuilder);
78+
$alias = $qb->getRootAlias();
79+
$where = $qb->expr()->in($alias.'.'.current($identifier), "?1");
80+
81+
return $qb->andWhere($where)
82+
->getQuery()
83+
->setParameter(1, $choiceListKeys, Connection::PARAM_STR_ARRAY)
84+
->getResult();
85+
}
86+
}

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Doctrine\Common\Persistence\ManagerRegistry;
1515
use Symfony\Component\Form\FormBuilder;
1616
use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList;
17+
use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader;
1718
use Symfony\Bridge\Doctrine\Form\EventListener\MergeCollectionListener;
1819
use Symfony\Bridge\Doctrine\Form\DataTransformer\EntitiesToArrayTransformer;
1920
use Symfony\Bridge\Doctrine\Form\DataTransformer\EntityToIdTransformer;
@@ -47,18 +48,28 @@ public function getDefaultOptions(array $options)
4748
'class' => null,
4849
'property' => null,
4950
'query_builder' => null,
51+
'loader' => null,
5052
'choices' => null,
5153
'group_by' => null,
5254
);
5355

5456
$options = array_replace($defaultOptions, $options);
5557

5658
if (!isset($options['choice_list'])) {
59+
$manager = $this->registry->getManager($options['em']);
60+
if (isset($options['query_builder'])) {
61+
$options['loader'] = new ORMQueryBuilderLoader(
62+
$options['query_builder'],
63+
$manager,
64+
$options['class']
65+
);
66+
}
67+
5768
$defaultOptions['choice_list'] = new EntityChoiceList(
58-
$this->registry->getManager($options['em']),
69+
$manager,
5970
$options['class'],
6071
$options['property'],
61-
$options['query_builder'],
72+
$options['loader'],
6273
$options['choices'],
6374
$options['group_by']
6475
);

0 commit comments

Comments
 (0)
0