Skip to content

Commit

Permalink
[DoctrineBridge] Refactor entity choice list to be ORM independant us…
Browse files Browse the repository at this point in the history
…ing an EntityLoader interface.
  • Loading branch information
beberlei authored and stof committed Dec 19, 2011
1 parent e6e78f6 commit 517eebc
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 60 deletions.
79 changes: 21 additions & 58 deletions src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php
Expand Up @@ -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;

Expand Down Expand Up @@ -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
Expand All @@ -63,22 +63,18 @@ 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
*
* @var Doctrine\ORM\UnitOfWork
*/
private $unitOfWork;

/**
* Property path to access the key value of this choice-list.
*
* @var PropertyPath
*/
private $propertyPath;

/**
Expand All @@ -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
Expand Down Expand Up @@ -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();
}
Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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.
*
Expand Down
@@ -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();
}
@@ -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();
}
}
15 changes: 13 additions & 2 deletions src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php
Expand Up @@ -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;
Expand Down Expand Up @@ -47,18 +48,28 @@ public function getDefaultOptions(array $options)
'class' => null,
'property' => null,
'query_builder' => null,
'loader' => null,
'choices' => null,
'group_by' => null,
);

$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']
);
Expand Down

0 comments on commit 517eebc

Please sign in to comment.