123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540 |
- <?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;
- @trigger_error('The '.__NAMESPACE__.'\EntityChoiceList class is deprecated since Symfony 2.7 and will be removed in 3.0. Use Symfony\Bridge\Doctrine\Form\ChoiceList\DoctrineChoiceLoader instead.', E_USER_DEPRECATED);
- use Doctrine\Common\Persistence\Mapping\ClassMetadata;
- use Doctrine\Common\Persistence\ObjectManager;
- use Symfony\Component\Form\Exception\RuntimeException;
- use Symfony\Component\Form\Exception\StringCastException;
- use Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList;
- use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
- /**
- * A choice list presenting a list of Doctrine entities as choices.
- *
- * @author Bernhard Schussek <bschussek@gmail.com>
- *
- * @deprecated Deprecated since Symfony 2.7, to be removed in Symfony 3.0.
- * Use {@link DoctrineChoiceLoader} instead.
- */
- class EntityChoiceList extends ObjectChoiceList
- {
- /**
- * @var ObjectManager
- */
- private $em;
- /**
- * @var string
- */
- private $class;
- /**
- * @var ClassMetadata
- */
- private $classMetadata;
- /**
- * Metadata for target class of primary key association.
- *
- * @var ClassMetadata
- */
- private $idClassMetadata;
- /**
- * Contains the query builder that builds the query for fetching the
- * entities.
- *
- * This property should only be accessed through queryBuilder.
- *
- * @var EntityLoaderInterface
- */
- private $entityLoader;
- /**
- * The identifier field, if the identifier is not composite.
- *
- * @var array
- */
- private $idField = null;
- /**
- * Whether to use the identifier for index generation.
- *
- * @var bool
- */
- private $idAsIndex = false;
- /**
- * Whether to use the identifier for value generation.
- *
- * @var bool
- */
- private $idAsValue = false;
- /**
- * Whether the entities have already been loaded.
- *
- * @var bool
- */
- private $loaded = false;
- /**
- * The preferred entities.
- *
- * @var array
- */
- private $preferredEntities = array();
- /**
- * Creates a new entity choice list.
- *
- * @param ObjectManager $manager An EntityManager instance
- * @param string $class The class name
- * @param string $labelPath The property path used for the label
- * @param EntityLoaderInterface $entityLoader An optional query builder
- * @param array|\Traversable|null $entities An array of choices or null to lazy load
- * @param array $preferredEntities An array of preferred choices
- * @param string $groupPath A property path pointing to the property used
- * to group the choices. Only allowed if
- * the choices are given as flat array.
- * @param PropertyAccessorInterface $propertyAccessor The reflection graph for reading property paths
- */
- public function __construct(ObjectManager $manager, $class, $labelPath = null, EntityLoaderInterface $entityLoader = null, $entities = null, array $preferredEntities = array(), $groupPath = null, PropertyAccessorInterface $propertyAccessor = null)
- {
- $this->em = $manager;
- $this->entityLoader = $entityLoader;
- $this->classMetadata = $manager->getClassMetadata($class);
- $this->class = $this->classMetadata->getName();
- $this->loaded = \is_array($entities) || $entities instanceof \Traversable;
- $this->preferredEntities = $preferredEntities;
- list(
- $this->idAsIndex,
- $this->idAsValue,
- $this->idField
- ) = $this->getIdentifierInfoForClass($this->classMetadata);
- if (null !== $this->idField && $this->classMetadata->hasAssociation($this->idField)) {
- $this->idClassMetadata = $this->em->getClassMetadata(
- $this->classMetadata->getAssociationTargetClass($this->idField)
- );
- list(
- $this->idAsIndex,
- $this->idAsValue
- ) = $this->getIdentifierInfoForClass($this->idClassMetadata);
- }
- if (!$this->loaded) {
- // Make sure the constraints of the parent constructor are
- // fulfilled
- $entities = array();
- }
- parent::__construct($entities, $labelPath, $preferredEntities, $groupPath, null, $propertyAccessor);
- }
- /**
- * Returns the list of entities.
- *
- * @return array
- *
- * @see ChoiceListInterface
- */
- public function getChoices()
- {
- if (!$this->loaded) {
- $this->load();
- }
- return parent::getChoices();
- }
- /**
- * Returns the values for the entities.
- *
- * @return array
- *
- * @see ChoiceListInterface
- */
- public function getValues()
- {
- if (!$this->loaded) {
- $this->load();
- }
- return parent::getValues();
- }
- /**
- * Returns the choice views of the preferred choices as nested array with
- * the choice groups as top-level keys.
- *
- * @return array
- *
- * @see ChoiceListInterface
- */
- public function getPreferredViews()
- {
- if (!$this->loaded) {
- $this->load();
- }
- return parent::getPreferredViews();
- }
- /**
- * Returns the choice views of the choices that are not preferred as nested
- * array with the choice groups as top-level keys.
- *
- * @return array
- *
- * @see ChoiceListInterface
- */
- public function getRemainingViews()
- {
- if (!$this->loaded) {
- $this->load();
- }
- return parent::getRemainingViews();
- }
- /**
- * Returns the entities corresponding to the given values.
- *
- * @return array
- *
- * @see ChoiceListInterface
- */
- public function getChoicesForValues(array $values)
- {
- // Performance optimization
- // Also prevents the generation of "WHERE id IN ()" queries through the
- // entity loader. At least with MySQL and on the development machine
- // this was tested on, no exception was thrown for such invalid
- // statements, consequently no test fails when this code is removed.
- // https://github.com/symfony/symfony/pull/8981#issuecomment-24230557
- if (empty($values)) {
- return array();
- }
- if (!$this->loaded) {
- // Optimize performance in case we have an entity loader and
- // a single-field identifier
- if ($this->idAsValue && $this->entityLoader) {
- $unorderedEntities = $this->entityLoader->getEntitiesByIds($this->idField, $values);
- $entitiesByValue = array();
- $entities = array();
- // Maintain order and indices from the given $values
- // An alternative approach to the following loop is to add the
- // "INDEX BY" clause to the Doctrine query in the loader,
- // but I'm not sure whether that's doable in a generic fashion.
- foreach ($unorderedEntities as $entity) {
- $value = $this->fixValue($this->getSingleIdentifierValue($entity));
- $entitiesByValue[$value] = $entity;
- }
- foreach ($values as $i => $value) {
- if (isset($entitiesByValue[$value])) {
- $entities[$i] = $entitiesByValue[$value];
- }
- }
- return $entities;
- }
- $this->load();
- }
- return parent::getChoicesForValues($values);
- }
- /**
- * Returns the values corresponding to the given entities.
- *
- * @return array
- *
- * @see ChoiceListInterface
- */
- public function getValuesForChoices(array $entities)
- {
- // Performance optimization
- if (empty($entities)) {
- return array();
- }
- if (!$this->loaded) {
- // Optimize performance for single-field identifiers. We already
- // know that the IDs are used as values
- // Attention: This optimization does not check choices for existence
- if ($this->idAsValue) {
- $values = array();
- foreach ($entities as $i => $entity) {
- if ($entity instanceof $this->class) {
- // Make sure to convert to the right format
- $values[$i] = $this->fixValue($this->getSingleIdentifierValue($entity));
- }
- }
- return $values;
- }
- $this->load();
- }
- return parent::getValuesForChoices($entities);
- }
- /**
- * Returns the indices corresponding to the given entities.
- *
- * @param array $entities
- *
- * @return array
- *
- * @see ChoiceListInterface
- * @deprecated since version 2.4, to be removed in 3.0.
- */
- public function getIndicesForChoices(array $entities)
- {
- @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.4 and will be removed in 3.0.', E_USER_DEPRECATED);
- // Performance optimization
- if (empty($entities)) {
- return array();
- }
- if (!$this->loaded) {
- // Optimize performance for single-field identifiers. We already
- // know that the IDs are used as indices
- // Attention: This optimization does not check choices for existence
- if ($this->idAsIndex) {
- $indices = array();
- foreach ($entities as $i => $entity) {
- if ($entity instanceof $this->class) {
- // Make sure to convert to the right format
- $indices[$i] = $this->fixIndex($this->getSingleIdentifierValue($entity));
- }
- }
- return $indices;
- }
- $this->load();
- }
- return parent::getIndicesForChoices($entities);
- }
- /**
- * Returns the entities corresponding to the given values.
- *
- * @param array $values
- *
- * @return array
- *
- * @see ChoiceListInterface
- * @deprecated since version 2.4, to be removed in 3.0.
- */
- public function getIndicesForValues(array $values)
- {
- @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.4 and will be removed in 3.0.', E_USER_DEPRECATED);
- // Performance optimization
- if (empty($values)) {
- return array();
- }
- if (!$this->loaded) {
- // Optimize performance for single-field identifiers.
- // Attention: This optimization does not check values for existence
- if ($this->idAsIndex && $this->idAsValue) {
- return $this->fixIndices($values);
- }
- $this->load();
- }
- return parent::getIndicesForValues($values);
- }
- /**
- * Creates a new unique index for this entity.
- *
- * If the entity has a single-field identifier, this identifier is used.
- *
- * Otherwise a new integer is generated.
- *
- * @param mixed $entity The choice to create an index for
- *
- * @return int|string a unique index containing only ASCII letters,
- * digits and underscores
- */
- protected function createIndex($entity)
- {
- if ($this->idAsIndex) {
- return $this->fixIndex($this->getSingleIdentifierValue($entity));
- }
- return parent::createIndex($entity);
- }
- /**
- * Creates a new unique value for this entity.
- *
- * If the entity has a single-field identifier, this identifier is used.
- *
- * Otherwise a new integer is generated.
- *
- * @param mixed $entity The choice to create a value for
- *
- * @return int|string A unique value without character limitations
- */
- protected function createValue($entity)
- {
- if ($this->idAsValue) {
- return (string) $this->getSingleIdentifierValue($entity);
- }
- return parent::createValue($entity);
- }
- /**
- * {@inheritdoc}
- */
- protected function fixIndex($index)
- {
- $index = parent::fixIndex($index);
- // If the ID is a single-field integer identifier, it is used as
- // index. Replace any leading minus by underscore to make it a valid
- // form name.
- if ($this->idAsIndex && $index < 0) {
- $index = strtr($index, '-', '_');
- }
- return $index;
- }
- /**
- * Get identifier information for a class.
- *
- * @param ClassMetadata $classMetadata The entity metadata
- *
- * @return array Return an array with idAsIndex, idAsValue and identifier
- */
- private function getIdentifierInfoForClass(ClassMetadata $classMetadata)
- {
- $identifier = null;
- $idAsIndex = false;
- $idAsValue = false;
- $identifiers = $classMetadata->getIdentifierFieldNames();
- if (1 === \count($identifiers)) {
- $identifier = $identifiers[0];
- if (!$classMetadata->hasAssociation($identifier)) {
- $idAsValue = true;
- if (\in_array($classMetadata->getTypeOfField($identifier), array('integer', 'smallint', 'bigint'))) {
- $idAsIndex = true;
- }
- }
- }
- return array($idAsIndex, $idAsValue, $identifier);
- }
- /**
- * Loads the list with entities.
- *
- * @throws StringCastException
- */
- private function load()
- {
- if ($this->entityLoader) {
- $entities = $this->entityLoader->getEntities();
- } else {
- $entities = $this->em->getRepository($this->class)->findAll();
- }
- try {
- // The second parameter $labels is ignored by ObjectChoiceList
- parent::initialize($entities, array(), $this->preferredEntities);
- } catch (StringCastException $e) {
- throw new StringCastException(str_replace('argument $labelPath', 'option "property"', $e->getMessage()), null, $e);
- }
- $this->loaded = true;
- }
- /**
- * Returns the first (and only) value 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
- *
- * @return array The identifier values
- *
- * @throws RuntimeException If the entity does not exist in Doctrine's identity map
- */
- private function getSingleIdentifierValue($entity)
- {
- $value = current($this->getIdentifierValues($entity));
- if ($this->idClassMetadata) {
- $class = $this->idClassMetadata->getName();
- if ($value instanceof $class) {
- $value = current($this->idClassMetadata->getIdentifierValues($value));
- }
- }
- return $value;
- }
- /**
- * 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
- *
- * @return array The identifier values
- *
- * @throws RuntimeException If the entity does not exist in Doctrine's identity map
- */
- private function getIdentifierValues($entity)
- {
- if (!$this->em->contains($entity)) {
- throw new RuntimeException('Entities passed to the choice field must be managed. Maybe persist them in the entity manager?');
- }
- $this->em->initializeObject($entity);
- return $this->classMetadata->getIdentifierValues($entity);
- }
- }
|