[DoctrineBridge] Refactor entity choice list to be ORM independant using an EntityLoader interface.

This commit is contained in:
Benjamin Eberlei 2011-11-26 17:36:55 +01:00 committed by Christophe Coevoet
parent e6e78f6a81
commit 517eebcb31
4 changed files with 156 additions and 60 deletions

View File

@ -15,7 +15,7 @@ use Symfony\Component\Form\Util\PropertyPath;
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 @@ class EntityChoiceList extends ArrayChoiceList
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 @@ class EntityChoiceList extends ArrayChoiceList
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 @@ class EntityChoiceList extends ArrayChoiceList
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 @@ class EntityChoiceList extends ArrayChoiceList
}
}
/**
* 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.
*

View File

@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 <kontakt@beberlei.de>
*/
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();
}

View File

@ -0,0 +1,86 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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();
}
}

View File

@ -14,6 +14,7 @@ namespace Symfony\Bridge\Doctrine\Form\Type;
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 @@ class EntityType extends AbstractType
'class' => null,
'property' => null,
'query_builder' => null,
'loader' => null,
'choices' => null,
'group_by' => null,
);
@ -54,11 +56,20 @@ class EntityType extends AbstractType
$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']
);