8000 [DoctrineBridge] Refactor EntityChoiceList by stof · Pull Request #2921 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[DoctrineBridge] Refactor EntityChoiceList #2921

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Dec 19, 2011
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 41 additions & 93 deletions src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,25 @@
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\ORM\QueryBuilder;
use Doctrine\ORM\NoResultException;
use Doctrine\Common\Persistence\ObjectManager;

class EntityChoiceList extends ArrayChoiceList
{
/**
* @var Doctrine\ORM\EntityManager
* @var ObjectManager
*/
private $em;

/**
* @var Doctrine\ORM\Mapping\ClassMetadata
* @var string
*/
private $class;

/**
* @var \Doctrine\Common\Persistence\Mapping\ClassMetadata
*/
private $classMetadata;

/**
* The entities from which the user can choose
*
Expand All @@ -40,7 +43,7 @@ class EntityChoiceList extends ArrayChoiceList
* This property is initialized by initializeChoices(). It should only
* be accessed through getEntity() and getEntities().
*
* @var Collection
* @var array
*/
private $entities = array();

Expand All @@ -50,9 +53,9 @@ class EntityChoiceList extends ArrayChoiceList
*
* This property should only be accessed through queryBuilder.
*
* @var Doctrine\ORM\QueryBuilder
* @var EntityLoaderInterface
*/
private $queryBuilder;
private $entityLoader;

/**
* The fields of which the identifier of the underlying class consists
Expand All @@ -64,21 +67,10 @@ class EntityChoiceList extends ArrayChoiceList
private $identifier = array();

/**
* A cache for \ReflectionProperty instances for the underlying class
* Property path to access the key value of this choice-list.
*
* This property should only be accessed through getReflProperty().
*
* @var array
* @var PropertyPath
*/
private $reflProperties = array();

/**
* A cache for the UnitOfWork instance of Doctrine
*
* @var Doctrine\ORM\UnitOfWork
*/
private $unitOfWork;

private $propertyPath;

/**
Expand All @@ -91,39 +83,29 @@ 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->classMetadata = $manager->getClassMetadata($class);
$this->identifier = $this->classMetadata->getIdentifierFieldNames();
$this->groupBy = $groupBy;

// The property option defines, which property (path) is used for
// displaying entities as strings
if ($property) {
$this->propertyPath = new PropertyPath($property);
} elseif (!method_exists($this->class, '__toString')) {
// Otherwise expect a __toString() method in the entity
throw new FormException('Entities passed to the choice field must have a "__toString()" method defined (or you can also override the "property" option).');
}

if (!is_array($choices) && !$choices instanceof \Closure && !is_null($choices)) {
Expand All @@ -150,8 +132,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 +153,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 @@ -219,11 +201,6 @@ private function loadEntities($entities, $group = null)
// If the property option was given, use it
$value = $this->propertyPath->getValue($entity);
} else {
// Otherwise expect a __toString() method in the entity
if (!method_exists($entity, '__toString')) {
throw new FormException('Entities passed to the choice field must have a "__toString()" method defined (or you can also override the "property" option).');
}

$value = (string) $entity;
}

Expand Down Expand Up @@ -278,7 +255,7 @@ public function getEntities()
}

/**
* Returns the entity for the given key.
* Returns the entities for the given keys.
*
* If the underlying entities have composite identifiers, the choices
* are initialized. The key is expected to be the index in the choices
Expand All @@ -287,55 +264,26 @@ public function getEntities()
* If they have single identifiers, they are either fetched from the
* internal entity cache (if filled) or loaded from the database.
*
* @param string $key The choice key (for entities with composite
* identifiers) or entity ID (for entities with single
* identifiers)
*
* @return object The matching entity
* @param array $keys The choice key (for entities with composite
* identifiers) or entity ID (for entities with single
* identifiers)
* @return object[] The matching entity
*/
public function getEntity($key)
public function getEntitiesByKeys(array $keys)
{
if (!$this->loaded) {
$this->load();
}

try {
if (count($this->identifier) > 1) {
// $key is a collection index
$entities = $this->getEntities();

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();
}
$found = array();

return $this->em->find($this->class, $key);
} catch (NoResultException $e) {
return null;
}
}

/**
* 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);
foreach ($keys as $key) {
if (isset($this->entities[$key])) {
$found[] = $this->entities[$key];
}
}

return $this->reflProperties[$property];
return $found;
}

/**
Expand All @@ -353,10 +301,10 @@ private function getReflProperty($property)
*/
public function getIdentifierValues($entity)
{
if (!$this->unitOfWork->isInIdentityMap($entity)) {
if (!$this->em->contains($entity)) {
throw new FormException('Entities passed to the choice field must be managed');
}

return $this->unitOfWork->getEntityIdentifier($entity);
return $this->classMetadata->getIdentifierValues($entity);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?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
{
/**
* Return an array of entities that are valid choices in the corresponding choice list.
*
* @return array
*/
function getEntities();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?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 (!($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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public function transform($collection)

foreach ($collection as $entity) {
// identify choices by their collection key
$key = array_search($entity, $availableEntities);
$key = array_search($entity, $availableEntities, true);
$array[] = $key;
}
} else {
Expand Down Expand Up @@ -84,19 +84,13 @@ public function reverseTransform($keys)
throw new UnexpectedTypeException($keys, 'array');
}

$notFound = array();

// optimize this into a SELECT WHERE IN query
foreach ($keys as $key) {
if ($entity = $this->choiceList->getEntity($key)) {
$collection->add($entity);
} else {
$notFound[] = $key;
}
$entities = $this->choiceList->getEntitiesByKeys($keys);
if (count($keys) !== count($entities)) {
throw new TransformationFailedException('Not all entities matching the keys were found.');
}

if (count($notFound) > 0) {
throw new TransformationFailedException(sprintf('The entities with keys "%s" could not be found', implode('", "', $notFound)));
foreach ($entities as $entity) {
$collection->add($entity);
}

return $collection;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,10 @@ public function reverseTransform($key)
throw new UnexpectedTypeException($key, 'numeric');
}

if (!($entity = $this->choiceList->getEntity($key))) {
if (!($entities = $this->choiceList->getEntitiesByKeys(array($key)))) {
throw new TransformationFailedException(sprintf('The entity with key "%s" could not be found', $key));
}

return $entity;
return $entities[0];
}
}
Loading
0