From 517eebcb317b971785db78a8d086e81acbc4530d Mon Sep 17 00:00:00 2001 From: Benjamin Eberlei Date: Sat, 26 Nov 2011 17:36:55 +0100 Subject: [PATCH] [DoctrineBridge] Refactor entity choice list to be ORM independant using an EntityLoader interface. --- .../Form/ChoiceList/EntityChoiceList.php | 79 +++++------------ .../Form/ChoiceList/EntityLoaderInterface.php | 36 ++++++++ .../Form/ChoiceList/ORMQueryBuilderLoader.php | 86 +++++++++++++++++++ .../Bridge/Doctrine/Form/Type/EntityType.php | 15 +++- 4 files changed, 156 insertions(+), 60 deletions(-) create mode 100644 src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityLoaderInterface.php create mode 100644 src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php index e881496ac208..bfa93e90353c 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php @@ -15,7 +15,7 @@ use Symfony\Component\Form\Exception\FormException; use Symfony\Component\Form\Exception\UnexpectedTypeException; use Symfony\Component\Form\Extension\Core\ChoiceList\ArrayChoiceList; -use Doctrine\ORM\EntityManager; +use Doctrine\Common\Persistence\ObjectManager; use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\NoResultException; @@ -52,7 +52,7 @@ class EntityChoiceList extends ArrayChoiceList * * @var Doctrine\ORM\QueryBuilder */ - private $queryBuilder; + private $entityLoader; /** * The fields of which the identifier of the underlying class consists @@ -63,15 +63,6 @@ class EntityChoiceList extends ArrayChoiceList */ private $identifier = array(); - /** - * A cache for \ReflectionProperty instances for the underlying class - * - * This property should only be accessed through getReflProperty(). - * - * @var array - */ - private $reflProperties = array(); - /** * A cache for the UnitOfWork instance of Doctrine * @@ -79,6 +70,11 @@ class EntityChoiceList extends ArrayChoiceList */ private $unitOfWork; + /** + * Property path to access the key value of this choice-list. + * + * @var PropertyPath + */ private $propertyPath; /** @@ -91,33 +87,20 @@ class EntityChoiceList extends ArrayChoiceList /** * Constructor. * - * @param EntityManager $em An EntityManager instance + * @param ObjectManager $manager An EntityManager instance * @param string $class The class name * @param string $property The property name - * @param QueryBuilder|\Closure $queryBuilder An optional query builder + * @param EntityLoaderInterface $entityLoader An optional query builder * @param array|\Closure $choices An array of choices or a function returning an array + * @param string $groupBy */ - public function __construct(EntityManager $em, $class, $property = null, $queryBuilder = null, $choices = null, $groupBy = null) + public function __construct(ObjectManager $manager, $class, $property = null, EntityLoaderInterface $entityLoader = null, $choices = null, $groupBy = null) { - // If a query builder was passed, it must be a closure or QueryBuilder - // instance - if (!(null === $queryBuilder || $queryBuilder instanceof QueryBuilder || $queryBuilder instanceof \Closure)) { - throw new UnexpectedTypeException($queryBuilder, 'Doctrine\ORM\QueryBuilder or \Closure'); - } - - if ($queryBuilder instanceof \Closure) { - $queryBuilder = $queryBuilder($em->getRepository($class)); - - if (!$queryBuilder instanceof QueryBuilder) { - throw new UnexpectedTypeException($queryBuilder, 'Doctrine\ORM\QueryBuilder'); - } - } - - $this->em = $em; + $this->em = $manager; $this->class = $class; - $this->queryBuilder = $queryBuilder; - $this->unitOfWork = $em->getUnitOfWork(); - $this->identifier = $em->getClassMetadata($class)->getIdentifierFieldNames(); + $this->entityLoader = $entityLoader; + $this->unitOfWork = $manager->getUnitOfWork(); + $this->identifier = $manager->getClassMetadata($class)->getIdentifierFieldNames(); $this->groupBy = $groupBy; // The property option defines, which property (path) is used for @@ -150,8 +133,8 @@ protected function load() if (is_array($this->choices)) { $entities = $this->choices; - } elseif ($qb = $this->queryBuilder) { - $entities = $qb->getQuery()->execute(); + } else if ($entityLoader = $this->entityLoader) { + $entities = $entityLoader->getEntities(); } else { $entities = $this->em->getRepository($this->class)->findAll(); } @@ -171,11 +154,11 @@ protected function load() private function groupEntities($entities, $groupBy) { $grouped = array(); + $path = new PropertyPath($groupBy); foreach ($entities as $entity) { // Get group name from property path try { - $path = new PropertyPath($groupBy); $group = (string) $path->getValue($entity); } catch (UnexpectedTypeException $e) { // PropertyPath cannot traverse entity @@ -307,12 +290,9 @@ public function getEntity($key) return isset($entities[$key]) ? $entities[$key] : null; } elseif ($this->entities) { return isset($this->entities[$key]) ? $this->entities[$key] : null; - } elseif ($qb = $this->queryBuilder) { - // should we clone the builder? - $alias = $qb->getRootAlias(); - $where = $qb->expr()->eq($alias.'.'.current($this->identifier), $key); - - return $qb->andWhere($where)->getQuery()->getSingleResult(); + } else if ($entityLoader = $this->entityLoader) { + $entities = $entityLoader->getEntitiesByKeys($this->identifier, array($key)); + return (isset($entities[0])) ? $entities[0] : false; } return $this->em->find($this->class, $key); @@ -321,23 +301,6 @@ public function getEntity($key) } } - /** - * Returns the \ReflectionProperty instance for a property of the underlying class. - * - * @param string $property The name of the property - * - * @return \ReflectionProperty The reflection instance - */ - private function getReflProperty($property) - { - if (!isset($this->reflProperties[$property])) { - $this->reflProperties[$property] = new \ReflectionProperty($this->class, $property); - $this->reflProperties[$property]->setAccessible(true); - } - - return $this->reflProperties[$property]; - } - /** * Returns the values of the identifier fields of an entity. * diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityLoaderInterface.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityLoaderInterface.php new file mode 100644 index 000000000000..5c6454900ee4 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityLoaderInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Form\ChoiceList; + +/** + * Custom loader for entities in the choice list. + * + * @author Benjamin Eberlei + */ +interface EntityLoaderInterface +{ + /** + * Given choice list values this method returns the appropriate entities for it. + * + * @param array $identifier + * @param array $choiceListKeys Array of values of the select option, checkbox or radio button. + * @return object[] + */ + function getEntitiesByKeys(array $identifier, array $choiceListKeys); + + /** + * Return an array of entities that are valid choices in the corresponding choice list. + * + * @return array + */ + function getEntities(); +} diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php new file mode 100644 index 000000000000..298378242fdd --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Form\ChoiceList; + +use Doctrine\DBAL\Connection; +use Symfony\Component\Form\Exception\FormException; +use Symfony\Component\Form\Exception\UnexpectedTypeException; +use Doctrine\ORM\QueryBuilder; + +/** + * Getting Entities through the ORM QueryBuilder + */ +class ORMQueryBuilderLoader implements EntityLoaderInterface +{ + /** + * Contains the query builder that builds the query for fetching the + * entities + * + * This property should only be accessed through queryBuilder. + * + * @var Doctrine\ORM\QueryBuilder + */ + private $queryBuilder; + + /** + * Construct an ORM Query Builder Loader + * + * @param QueryBuilder $queryBuilder + * @param EntityManager $manager + * @param string $class + */ + public function __construct($queryBuilder, $manager = null, $class = null) + { + // If a query builder was passed, it must be a closure or QueryBuilder + // instance + if (!(null === $queryBuilder || $queryBuilder instanceof QueryBuilder || $queryBuilder instanceof \Closure)) { + throw new UnexpectedTypeException($queryBuilder, 'Doctrine\ORM\QueryBuilder or \Closure'); + } + + if ($queryBuilder instanceof \Closure) { + $queryBuilder = $queryBuilder($manager->getRepository($class)); + + if (!$queryBuilder instanceof QueryBuilder) { + throw new UnexpectedTypeException($queryBuilder, 'Doctrine\ORM\QueryBuilder'); + } + } + + $this->queryBuilder = $queryBuilder; + } + + /** + * {@inheritDoc} + */ + public function getEntities() + { + return $this->queryBuilder->getQuery()->execute(); + } + + /** + * {@inheritDoc} + */ + public function getEntitiesByKeys(array $identifier, array $choiceListKeys) + { + if (count($identifier) != 1) { + throw new FormException("Only entities with one identifier supported by ORM QueryBuilder."); + } + + $qb = clone ($this->queryBuilder); + $alias = $qb->getRootAlias(); + $where = $qb->expr()->in($alias.'.'.current($identifier), "?1"); + + return $qb->andWhere($where) + ->getQuery() + ->setParameter(1, $choiceListKeys, Connection::PARAM_STR_ARRAY) + ->getResult(); + } +} \ No newline at end of file diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php index c641563b582d..4014d67b421f 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php @@ -14,6 +14,7 @@ use Doctrine\Common\Persistence\ManagerRegistry; use Symfony\Component\Form\FormBuilder; use Symfony\Bridge\Doctrine\Form\ChoiceList\EntityChoiceList; +use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader; use Symfony\Bridge\Doctrine\Form\EventListener\MergeCollectionListener; use Symfony\Bridge\Doctrine\Form\DataTransformer\EntitiesToArrayTransformer; use Symfony\Bridge\Doctrine\Form\DataTransformer\EntityToIdTransformer; @@ -47,6 +48,7 @@ public function getDefaultOptions(array $options) 'class' => null, 'property' => null, 'query_builder' => null, + 'loader' => null, 'choices' => null, 'group_by' => null, ); @@ -54,11 +56,20 @@ public function getDefaultOptions(array $options) $options = array_replace($defaultOptions, $options); if (!isset($options['choice_list'])) { + $manager = $this->registry->getManager($options['em']); + if (isset($options['query_builder'])) { + $options['loader'] = new ORMQueryBuilderLoader( + $options['query_builder'], + $manager, + $options['class'] + ); + } + $defaultOptions['choice_list'] = new EntityChoiceList( - $this->registry->getManager($options['em']), + $manager, $options['class'], $options['property'], - $options['query_builder'], + $options['loader'], $options['choices'], $options['group_by'] );