From 813ec54fa10aab94e1f2498a2cf7e2deb6d0c8bf Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Wed, 16 Feb 2011 16:01:01 +0100 Subject: [PATCH] [Form] Refactored parts of the choice fields into ChoiceList instances --- src/Symfony/Component/Form/ChoiceField.php | 132 ++----- .../Form/ChoiceList/ChoiceListInterface.php | 27 ++ .../Form/ChoiceList/DefaultChoiceList.php | 138 ++++++++ .../Form/ChoiceList/EntityChoiceList.php | 268 +++++++++++++++ .../Component/Form/EntityChoiceField.php | 321 ++---------------- .../Tests/Component/Form/ChoiceFieldTest.php | 15 +- .../Component/Form/EntityChoiceFieldTest.php | 4 +- 7 files changed, 491 insertions(+), 414 deletions(-) create mode 100644 src/Symfony/Component/Form/ChoiceList/ChoiceListInterface.php create mode 100644 src/Symfony/Component/Form/ChoiceList/DefaultChoiceList.php create mode 100644 src/Symfony/Component/Form/ChoiceList/EntityChoiceList.php diff --git a/src/Symfony/Component/Form/ChoiceField.php b/src/Symfony/Component/Form/ChoiceField.php index 9e16eee718..46e164d783 100644 --- a/src/Symfony/Component/Form/ChoiceField.php +++ b/src/Symfony/Component/Form/ChoiceField.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Form; -use Symfony\Component\Form\Exception\InvalidOptionsException; +use Symfony\Component\Form\ChoiceList\DefaultChoiceList; /** * Lets the user select between different choices. @@ -37,18 +37,27 @@ use Symfony\Component\Form\Exception\InvalidOptionsException; */ class ChoiceField extends HybridField { - /** - * Stores the preferred choices with the choices as keys - * @var array - */ - protected $preferredChoices = array(); + protected $choiceList; - /** - * Stores the choices - * You should only access this property through getChoices() - * @var array - */ - private $choices = array(); + public function __construct($name = null, array $options = array()) + { + parent::__construct($name, $options); + + // until we have DI, this MUST happen after configure() + if ($this->isExpanded()) { + $this->setFieldMode(self::FORM); + + foreach ($this->choiceList->getPreferredChoices() as $choice => $value) { + $this->add($this->newChoiceField($choice, $value)); + } + + foreach ($this->choiceList->getOtherChoices() as $choice => $value) { + $this->add($this->newChoiceField($choice, $value)); + } + } else { + $this->setFieldMode(self::FIELD); + } + } protected function configure() { @@ -60,37 +69,12 @@ class ChoiceField extends HybridField parent::configure(); - $choices = $this->getOption('choices'); - - if (!is_array($choices) && !$choices instanceof \Closure) { - throw new InvalidOptionsException('The choices option must be an array or a closure', array('choices')); - } - - if (!is_array($this->getOption('preferred_choices'))) { - throw new InvalidOptionsException('The preferred_choices option must be an array', array('preferred_choices')); - } - - if (count($this->getOption('preferred_choices')) > 0) { - $this->preferredChoices = array_flip($this->getOption('preferred_choices')); - } - - if ($this->isExpanded()) { - $this->setFieldMode(self::FORM); - - $choices = $this->getChoices(); - - foreach ($this->preferredChoices as $choice => $_) { - $this->add($this->newChoiceField($choice, $choices[$choice])); - } - - foreach ($choices as $choice => $value) { - if (!isset($this->preferredChoices[$choice])) { - $this->add($this->newChoiceField($choice, $value)); - } - } - } else { - $this->setFieldMode(self::FIELD); - } + $this->choiceList = new DefaultChoiceList( + $this->getOption('choices'), + $this->getOption('preferred_choices'), + $this->getOption('empty_value'), + $this->isRequired() + ); } public function getName() @@ -108,79 +92,29 @@ class ChoiceField extends HybridField return $name; } - /** - * Initializes the choices - * - * If the choices were given as a closure, the closure is executed now. - * - * @return array - */ - protected function initializeChoices() - { - if (!$this->choices) { - $this->choices = $this->getInitializedChoices(); - - if (!$this->isRequired()) { - $this->choices = array('' => $this->getOption('empty_value')) + $this->choices; - } - } - } - - protected function getInitializedChoices() - { - $choices = $this->getOption('choices'); - - if ($choices instanceof \Closure) { - $choices = $choices->__invoke(); - } - - if (!is_array($choices)) { - throw new InvalidOptionsException('The "choices" option must be an array or a closure returning an array', array('choices')); - } - - return $choices; - } - - /** - * Returns the choices - * - * If the choices were given as a closure, the closure is executed on - * the first call of this method. - * - * @return array - */ - protected function getChoices() - { - $this->initializeChoices(); - - return $this->choices; - } - public function getPreferredChoices() { - return array_intersect_key($this->getChoices(), $this->preferredChoices); + return $this->choiceList->getPreferredChoices(); } public function getOtherChoices() { - return array_diff_key($this->getChoices(), $this->preferredChoices); + return $this->choiceList->getOtherChoices(); } public function getLabel($choice) { - $choices = $this->getChoices(); - - return isset($choices[$choice]) ? $choices[$choice] : null; + return $this->choiceList->getLabel($choice); } public function isChoiceGroup($choice) { - return is_array($choice) || $choice instanceof \Traversable; + return $this->choiceList->isChoiceGroup($choice); } public function isChoiceSelected($choice) { - return in_array((string) $choice, (array) $this->getDisplayedData(), true); + return $this->choiceList->isChoiceSelected($choice, $this->getDisplayedData()); } public function isMultipleChoice() @@ -244,7 +178,7 @@ class ChoiceField extends HybridField { if ($this->isExpanded()) { $value = parent::transform($value); - $choices = $this->getChoices(); + $choices = $this->choiceList->getChoices(); foreach ($choices as $choice => $_) { $choices[$choice] = $this->isMultipleChoice() diff --git a/src/Symfony/Component/Form/ChoiceList/ChoiceListInterface.php b/src/Symfony/Component/Form/ChoiceList/ChoiceListInterface.php new file mode 100644 index 0000000000..e982f0396d --- /dev/null +++ b/src/Symfony/Component/Form/ChoiceList/ChoiceListInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList; + +interface ChoiceListInterface +{ + function getLabel($choice); + + function getChoices(); + + function getOtherChoices(); + + function getPreferredChoices(); + + function isChoiceGroup($choice); + + function isChoiceSelected($choice, $displayedData); +} \ No newline at end of file diff --git a/src/Symfony/Component/Form/ChoiceList/DefaultChoiceList.php b/src/Symfony/Component/Form/ChoiceList/DefaultChoiceList.php new file mode 100644 index 0000000000..fb024fcc84 --- /dev/null +++ b/src/Symfony/Component/Form/ChoiceList/DefaultChoiceList.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList; + +use Symfony\Component\Form\Exception\UnexpectedTypeException; + +class DefaultChoiceList implements ChoiceListInterface +{ + /** + * Stores the preferred choices with the choices as keys + * @var array + */ + private $preferredChoices = array(); + + /** + * Stores the choices + * You should only access this property through getChoices() + * @var array + */ + private $choices = array(); + + private $initialized = false; + + private $emptyValue; + + private $required; + + public function __construct($choices, array $preferredChoices = array(), $emptyValue = '', $required = false) + { + if (!is_array($choices) && !$choices instanceof \Closure) { + throw new UnexpectedTypeException($choices, 'array or \Closure'); + } + + $this->choices = $choices; + $this->preferredChoices = array_flip($preferredChoices); + $this->emptyValue = $emptyValue; + $this->required = $required; + } + + /** + * {@inheritDoc} + */ + public function getLabel($choice) + { + $choices = $this->getChoices(); + + return isset($choices[$choice]) ? $choices[$choice] : null; + } + + /** + * Returns the choices + * + * If the choices were given as a closure, the closure is executed on + * the first call of this method. + * + * @return array + */ + public function getChoices() + { + $this->initializeChoices(); + + return $this->choices; + } + + /** + * {@inheritDoc} + */ + public function getOtherChoices() + { + return array_diff_key($this->getChoices(), $this->preferredChoices); + } + + /** + * {@inheritDoc} + */ + public function getPreferredChoices() + { + return array_intersect_key($this->getChoices(), $this->preferredChoices); + } + + /** + * {@inheritDoc} + */ + public function isChoiceGroup($choice) + { + return is_array($choice) || $choice instanceof \Traversable; + } + + /** + * {@inheritDoc} + */ + public function isChoiceSelected($choice, $displayedData) + { + return in_array((string) $choice, (array) $displayedData, true); + } + + /** + * Initializes the choices + * + * If the choices were given as a closure, the closure is executed now. + * + * @return array + */ + protected function initializeChoices() + { + if (!$this->initialized) { + $this->choices = $this->getInitializedChoices($this->choices); + + if (!$this->required) { + $this->choices = array('' => $this->emptyValue) + $this->choices; + } + + $this->initialized = true; + } + } + + protected function getInitializedChoices($choices) + { + if ($choices instanceof \Closure) { + $choices = $choices->__invoke(); + + if (!is_array($choices)) { + throw new UnexpectedTypeException($choices, 'array'); + } + } + + return $choices; + } +} \ No newline at end of file diff --git a/src/Symfony/Component/Form/ChoiceList/EntityChoiceList.php b/src/Symfony/Component/Form/ChoiceList/EntityChoiceList.php new file mode 100644 index 0000000000..0d0c8bb62f --- /dev/null +++ b/src/Symfony/Component/Form/ChoiceList/EntityChoiceList.php @@ -0,0 +1,268 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList; + +use Symfony\Component\Form\PropertyPath; +use Symfony\Component\Form\Exception\FormException; +use Symfony\Component\Form\Exception\UnexpectedTypeException; +use Doctrine\ORM\EntityManager; +use Doctrine\ORM\QueryBuilder; +use Doctrine\ORM\NoResultException; + +class EntityChoiceList extends DefaultChoiceList +{ + private $em; + + private $class; + + /** + * The entities from which the user can choose + * + * This array is either indexed by ID (if the ID is a single field) + * or by key in the choices array (if the ID consists of multiple fields) + * + * This property is initialized by initializeChoices(). It should only + * be accessed through getEntity() and getEntities(). + * + * @var Collection + */ + private $entities = array(); + + /** + * 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; + + /** + * The fields of which the identifier of the underlying class consists + * + * This property should only be accessed through identifier. + * + * @var array + */ + 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; + + private $propertyPath; + + public function __construct(EntityManager $em, $class, $property = null, $queryBuilder = null, $choices = null, array $preferredChoices = array(), $emptyValue = '', $required = false) + { + // 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->class = $class; + $this->queryBuilder = $queryBuilder; + $this->unitOfWork = $em->getUnitOfWork(); + $this->identifier = $em->getClassMetadata($class)->getIdentifierFieldNames(); + + // The propery option defines, which property (path) is used for + // displaying entities as strings + if ($property) { + $this->propertyPath = new PropertyPath($property); + } + + parent::__construct($choices, $preferredChoices, $emptyValue, $required); + + // The entities can be passed directly in the "choices" option. + // In this case, initializing the entity cache is a cheap operation + // so do it now! + if (is_array($choices) && count($choices) > 0) { + $this->initializeChoices(); + } + } + + /** + * Initializes the choices and returns them + * + * The choices are generated from the entities. If the entities have a + * composite identifier, the choices are indexed using ascending integers. + * Otherwise the identifiers are used as indices. + * + * If the entities were passed in the "choices" option, this method + * does not have any significant overhead. Otherwise, if a query builder + * was passed in the "query_builder" option, this builder is now used + * to construct a query which is executed. In the last case, all entities + * for the underlying class are fetched from the repository. + * + * If the option "property" was passed, the property path in that option + * is used as option values. Otherwise this method tries to convert + * objects to strings using __toString(). + * + * @return array An array of choices + */ + protected function getInitializedChoices($choices) + { + if ($choices) { + $entities = parent::getInitializedChoices($choices); + } else if ($qb = $this->queryBuilder) { + $entities = $qb->getQuery()->execute(); + } else { + $entities = $this->em->getRepository($this->class)->findAll(); + } + + $propertyPath = null; + $choices = array(); + $this->entities = array(); + + foreach ($entities as $key => $entity) { + if ($this->propertyPath) { + // If the property option was given, use it + $value = $this->propertyPath->getValue($entity); + } else { + // Otherwise expect a __toString() method in the entity + $value = (string)$entity; + } + + if (count($this->identifier) > 1) { + // When the identifier consists of multiple field, use + // naturally ordered keys to refer to the choices + $choices[$key] = $value; + $this->entities[$key] = $entity; + } else { + // When the identifier is a single field, index choices by + // entity ID for performance reasons + $id = current($this->getIdentifierValues($entity)); + $choices[$id] = $value; + $this->entities[$id] = $entity; + } + } + + return $choices; + } + + public function getIdentifier() + { + return $this->identifier; + } + + /** + * Returns the according entities for the choices + * + * If the choices were not initialized, they are initialized now. This + * is an expensive operation, except if the entities were passed in the + * "choices" option. + * + * @return array An array of entities + */ + public function getEntities() + { + if (!$this->entities) { + // indirectly initializes the entities property + $this->initializeChoices(); + } + + return $this->entities; + } + + /** + * Returns the entity for the given key + * + * If the underlying entities have composite identifiers, the choices + * are intialized. The key is expected to be the index in the choices + * array in this case. + * + * 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 + */ + public function getEntity($key) + { + if (count($this->identifier) > 1) { + // $key is a collection index + $entities = $this->getEntities(); + return $entities[$key]; + } else if ($this->entities) { + return $this->entities[$key]; + } else if ($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(); + } + + return $this->em->find($this->class, $key); + } + + /** + * Returns the \ReflectionProperty instance for a property of the + * underlying class + * + * @param string $property The name of the property + * @return \ReflectionProperty The reflection instsance + */ + protected 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 + * + * Doctrine must know about this entity, that is, the entity must already + * be persisted or added to the identity map before. Otherwise an + * exception is thrown. + * + * @param object $entity The entity for which to get the identifier + * @throws FormException If the entity does not exist in Doctrine's + * identity map + */ + public function getIdentifierValues($entity) + { + if (!$this->unitOfWork->isInIdentityMap($entity)) { + throw new FormException('Entities passed to the choice field must be managed'); + } + + return $this->unitOfWork->getEntityIdentifier($entity); + } +} \ No newline at end of file diff --git a/src/Symfony/Component/Form/EntityChoiceField.php b/src/Symfony/Component/Form/EntityChoiceField.php index e60af98ddf..4431ee68a6 100644 --- a/src/Symfony/Component/Form/EntityChoiceField.php +++ b/src/Symfony/Component/Form/EntityChoiceField.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Form; +use Symfony\Component\Form\ChoiceList\EntityChoiceList; use Symfony\Component\Form\ValueTransformer\TransformationFailedException; use Symfony\Component\Form\Exception\FormException; use Symfony\Component\Form\Exception\InvalidOptionsException; @@ -61,54 +62,6 @@ use Doctrine\ORM\NoResultException; */ class EntityChoiceField extends ChoiceField { - /** - * The entities from which the user can choose - * - * This array is either indexed by ID (if the ID is a single field) - * or by key in the choices array (if the ID consists of multiple fields) - * - * This property is initialized by initializeChoices(). It should only - * be accessed through getEntity() and getEntities(). - * - * @var Collection - */ - protected $entities = null; - - /** - * Contains the query builder that builds the query for fetching the - * entities - * - * This property should only be accessed through getQueryBuilder(). - * - * @var Doctrine\ORM\QueryBuilder - */ - protected $queryBuilder = null; - - /** - * The fields of which the identifier of the underlying class consists - * - * This property should only be accessed through getIdentifierFields(). - * - * @var array - */ - protected $identifier = array(); - - /** - * A cache for \ReflectionProperty instances for the underlying class - * - * This property should only be accessed through getReflProperty(). - * - * @var array - */ - protected $reflProperties = array(); - - /** - * A cache for the UnitOfWork instance of Doctrine - * - * @var Doctrine\ORM\UnitOfWork - */ - protected $unitOfWork = null; - /** * {@inheritDoc} */ @@ -124,247 +77,16 @@ class EntityChoiceField extends ChoiceField parent::configure(); - // The entities can be passed directly in the "choices" option. - // In this case, initializing the entity cache is a cheap operation - // so do it now! - if (is_array($this->getOption('choices')) && count($this->getOption('choices')) > 0) { - $this->initializeChoices(); - } - - // If a query builder was passed, it must be a closure or QueryBuilder - // instance - if ($qb = $this->getOption('query_builder')) { - if (!($qb instanceof QueryBuilder || $qb instanceof \Closure)) { - throw new InvalidOptionsException( - 'The option "query_builder" most contain a closure or a QueryBuilder instance', - array('query_builder')); - } - } - } - - /** - * Returns the query builder instance for the choices of this field - * - * @return Doctrine\ORM\QueryBuilder The query builder - * @throws InvalidOptionsException When the query builder was passed as - * closure and that closure does not - * return a QueryBuilder instance - */ - protected function getQueryBuilder() - { - if (!$this->getOption('query_builder')) { - return null; - } - - if (!$this->queryBuilder) { - $qb = $this->getOption('query_builder'); - - if ($qb instanceof \Closure) { - $class = $this->getOption('class'); - $em = $this->getOption('em'); - $qb = $qb($em->getRepository($class)); - - if (!$qb instanceof QueryBuilder) { - throw new InvalidOptionsException( - 'The closure in the option "query_builder" should return a QueryBuilder instance', - array('query_builder')); - } - } - - $this->queryBuilder = $qb; - } - - return $this->queryBuilder; - } - - /** - * Returns the unit of work of the entity manager - * - * This object is cached for faster lookups. - * - * @return Doctrine\ORM\UnitOfWork The unit of work - */ - protected function getUnitOfWork() - { - if (!$this->unitOfWork) { - $this->unitOfWork = $this->getOption('em')->getUnitOfWork(); - } - - return $this->unitOfWork; - } - - /** - * Initializes the choices and returns them - * - * The choices are generated from the entities. If the entities have a - * composite identifier, the choices are indexed using ascending integers. - * Otherwise the identifiers are used as indices. - * - * If the entities were passed in the "choices" option, this method - * does not have any significant overhead. Otherwise, if a query builder - * was passed in the "query_builder" option, this builder is now used - * to construct a query which is executed. In the last case, all entities - * for the underlying class are fetched from the repository. - * - * If the option "property" was passed, the property path in that option - * is used as option values. Otherwise this method tries to convert - * objects to strings using __toString(). - * - * @return array An array of choices - */ - protected function getInitializedChoices() - { - if ($this->getOption('choices')) { - $entities = parent::getInitializedChoices(); - } else if ($qb = $this->getQueryBuilder()) { - $entities = $qb->getQuery()->execute(); - } else { - $class = $this->getOption('class'); - $em = $this->getOption('em'); - $entities = $em->getRepository($class)->findAll(); - } - - $propertyPath = null; - $choices = array(); - $this->entities = array(); - - // The propery option defines, which property (path) is used for - // displaying entities as strings - if ($this->getOption('property')) { - $propertyPath = new PropertyPath($this->getOption('property')); - } - - foreach ($entities as $key => $entity) { - if ($propertyPath) { - // If the property option was given, use it - $value = $propertyPath->getValue($entity); - } else { - // Otherwise expect a __toString() method in the entity - $value = (string)$entity; - } - - if (count($this->getIdentifierFields()) > 1) { - // When the identifier consists of multiple field, use - // naturally ordered keys to refer to the choices - $choices[$key] = $value; - $this->entities[$key] = $entity; - } else { - // When the identifier is a single field, index choices by - // entity ID for performance reasons - $id = current($this->getIdentifierValues($entity)); - $choices[$id] = $value; - $this->entities[$id] = $entity; - } - } - - return $choices; - } - - /** - * Returns the according entities for the choices - * - * If the choices were not initialized, they are initialized now. This - * is an expensive operation, except if the entities were passed in the - * "choices" option. - * - * @return array An array of entities - */ - protected function getEntities() - { - if (!$this->entities) { - // indirectly initializes the entities property - $this->initializeChoices(); - } - - return $this->entities; - } - - /** - * Returns the entity for the given key - * - * If the underlying entities have composite identifiers, the choices - * are intialized. The key is expected to be the index in the choices - * array in this case. - * - * 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 - */ - protected function getEntity($key) - { - $id = $this->getIdentifierFields(); - - if (count($id) > 1) { - // $key is a collection index - $entities = $this->getEntities(); - return $entities[$key]; - } else if ($this->entities) { - return $this->entities[$key]; - } else if ($qb = $this->getQueryBuilder()) { - // should we clone the builder? - $alias = $qb->getRootAlias(); - $where = $qb->expr()->eq($alias.'.'.current($id), $key); - - return $qb->andWhere($where)->getQuery()->getSingleResult(); - } - - return $this->getOption('em')->find($this->getOption('class'), $key); - } - - /** - * Returns the \ReflectionProperty instance for a property of the - * underlying class - * - * @param string $property The name of the property - * @return \ReflectionProperty The reflection instsance - */ - protected function getReflProperty($property) - { - if (!isset($this->reflProperties[$property])) { - $this->reflProperties[$property] = new \ReflectionProperty($this->getOption('class'), $property); - $this->reflProperties[$property]->setAccessible(true); - } - - return $this->reflProperties[$property]; - } - - /** - * Returns the fields included in the identifier of the underlying class - * - * @return array An array of field names - */ - protected function getIdentifierFields() - { - if (!$this->identifier) { - $metadata = $this->getOption('em')->getClassMetadata($this->getOption('class')); - $this->identifier = $metadata->getIdentifierFieldNames(); - } - - return $this->identifier; - } - - /** - * Returns the values of the identifier fields of an entity - * - * Doctrine must know about this entity, that is, the entity must already - * be persisted or added to the identity map before. Otherwise an - * exception is thrown. - * - * @param object $entity The entity for which to get the identifier - * @throws FormException If the entity does not exist in Doctrine's - * identity map - */ - protected function getIdentifierValues($entity) - { - if (!$this->getUnitOfWork()->isInIdentityMap($entity)) { - throw new FormException('Entities passed to the choice field must be managed'); - } - - return $this->getUnitOfWork()->getEntityIdentifier($entity); + $this->choiceList = new EntityChoiceList( + $this->getOption('em'), + $this->getOption('class'), + $this->getOption('property'), + $this->getOption('query_builder'), + $this->getOption('choices'), + $this->getOption('preferred_choices'), + $this->getOption('empty_value'), + $this->isRequired() + ); } /** @@ -421,10 +143,10 @@ class EntityChoiceField extends ChoiceField $notFound = array(); - if (count($this->getIdentifierFields()) > 1) { - $notFound = array_diff((array)$keyOrKeys, array_keys($this->getEntities())); - } else if ($this->entities) { - $notFound = array_diff((array)$keyOrKeys, array_keys($this->entities)); + if (count($this->choiceList->getIdentifier()) > 1) { + $notFound = array_diff((array)$keyOrKeys, array_keys($this->choiceList->getEntities())); + } else if ($this->choiceList->getEntities()) { + $notFound = array_diff((array)$keyOrKeys, array_keys($this->choiceList->getEntities())); } if (0 === count($notFound)) { @@ -434,14 +156,14 @@ class EntityChoiceField extends ChoiceField // optimize this into a SELECT WHERE IN query foreach ($keyOrKeys as $key) { try { - $result->add($this->getEntity($key)); + $result->add($this->choiceList->getEntity($key)); } catch (NoResultException $e) { $notFound[] = $key; } } } else { try { - $result = $this->getEntity($keyOrKeys); + $result = $this->choiceList->getEntity($keyOrKeys); } catch (NoResultException $e) { $notFound[] = $keyOrKeys; } @@ -469,9 +191,9 @@ class EntityChoiceField extends ChoiceField return $this->getOption('multiple') ? array() : ''; } - if (count($this->identifier) > 1) { + if (count($this->choiceList->getIdentifier()) > 1) { // load all choices - $availableEntities = $this->getEntities(); + $availableEntities = $this->choiceList->getEntities(); if ($collectionOrEntity instanceof Collection) { $result = array(); @@ -489,14 +211,13 @@ class EntityChoiceField extends ChoiceField $result = array(); foreach ($collectionOrEntity as $entity) { - $result[] = current($this->getIdentifierValues($entity)); + $result[] = current($this->choiceList->getIdentifierValues($entity)); } } else { - $result = current($this->getIdentifierValues($collectionOrEntity)); + $result = current($this->choiceList->getIdentifierValues($collectionOrEntity)); } } - return parent::transform($result); } } \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/Form/ChoiceFieldTest.php b/tests/Symfony/Tests/Component/Form/ChoiceFieldTest.php index a69a469858..b88797e3b3 100644 --- a/tests/Symfony/Tests/Component/Form/ChoiceFieldTest.php +++ b/tests/Symfony/Tests/Component/Form/ChoiceFieldTest.php @@ -103,7 +103,7 @@ class ChoiceFieldTest extends \PHPUnit_Framework_TestCase } /** - * @expectedException Symfony\Component\Form\Exception\InvalidOptionsException + * @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException */ public function testConfigureChoicesWithNonArray() { @@ -112,17 +112,6 @@ class ChoiceFieldTest extends \PHPUnit_Framework_TestCase )); } - /** - * @expectedException Symfony\Component\Form\Exception\InvalidOptionsException - */ - public function testConfigurePreferredChoicesWithNonArray() - { - $field = new ChoiceField('name', array( - 'choices' => $this->choices, - 'preferred_choices' => new \ArrayObject(), - )); - } - public function getChoicesVariants() { $choices = $this->choices; @@ -144,7 +133,7 @@ class ChoiceFieldTest extends \PHPUnit_Framework_TestCase } /** - * @expectedException Symfony\Component\Form\Exception\InvalidOptionsException + * @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException */ public function testClosureShouldReturnArray() { diff --git a/tests/Symfony/Tests/Component/Form/EntityChoiceFieldTest.php b/tests/Symfony/Tests/Component/Form/EntityChoiceFieldTest.php index 21faab8f5d..9422fc247b 100644 --- a/tests/Symfony/Tests/Component/Form/EntityChoiceFieldTest.php +++ b/tests/Symfony/Tests/Component/Form/EntityChoiceFieldTest.php @@ -103,7 +103,7 @@ class EntityChoiceFieldTest extends DoctrineOrmTestCase // } /** - * @expectedException Symfony\Component\Form\Exception\InvalidOptionsException + * @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException */ public function testConfigureQueryBuilderWithNonQueryBuilderAndNonClosure() { @@ -115,7 +115,7 @@ class EntityChoiceFieldTest extends DoctrineOrmTestCase } /** - * @expectedException Symfony\Component\Form\Exception\InvalidOptionsException + * @expectedException Symfony\Component\Form\Exception\UnexpectedTypeException */ public function testConfigureQueryBuilderWithClosureReturningNonQueryBuilder() {