* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Sonata\AdminBundle\Form\ChoiceList; use Doctrine\Common\Util\ClassUtils; use Sonata\AdminBundle\Model\ModelManagerInterface; use Symfony\Component\Form\Exception\InvalidArgumentException; use Symfony\Component\Form\Exception\RuntimeException; use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; use Symfony\Component\PropertyAccess\PropertyPath; @trigger_error( 'The '.__CLASS__.' class is deprecated since 3.x and will be removed in 4.0.' .' Use '.__NAMESPACE__.'\ModelChoiceLoader instead.', E_USER_DEPRECATED ); /** * NEXT_MAJOR: Remove this class. * * @deprecated Since 3.x, to be removed in 4.0. Use Sonata\AdminBundle\ModelChoiceLoader instead * * @author Thomas Rabaix */ class ModelChoiceList extends SimpleChoiceList { /** * @var ModelManagerInterface */ private $modelManager; /** * @var string */ 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 mixed */ 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 $query; /** * 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(); /** * @var PropertyPath */ private $propertyPath; /** * @var PropertyAccessorInterface */ private $propertyAccessor; /** * @param ModelManagerInterface $modelManager * @param string $class * @param null $property * @param null $query * @param array $choices */ public function __construct(ModelManagerInterface $modelManager, $class, $property = null, $query = null, $choices = array(), PropertyAccessorInterface $propertyAccessor = null) { $this->modelManager = $modelManager; $this->class = $class; $this->query = $query; $this->identifier = $this->modelManager->getIdentifierFieldNames($this->class); // The property option defines, which property (path) is used for // displaying entities as strings if ($property) { $this->propertyPath = new PropertyPath($property); $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); } parent::__construct($this->load($choices)); } /** * @return array */ 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() { return $this->entities; } /** * Returns the entity for the given key. * * If the underlying entities have composite identifiers, the choices * are initialized. 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 isset($entities[$key]) ? $entities[$key] : null; } elseif ($this->entities) { return isset($this->entities[$key]) ? $this->entities[$key] : null; } return $this->modelManager->find($this->class, $key); } /** * 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 InvalidArgumentException If the entity does not exist in Doctrine's * identity map * * @return array */ public function getIdentifierValues($entity) { try { return $this->modelManager->getIdentifierValues($entity); } catch (\Exception $e) { throw new InvalidArgumentException(sprintf('Unable to retrieve the identifier values for entity %s', ClassUtils::getClass($entity)), 0, $e); } } /** * @return ModelManagerInterface */ public function getModelManager() { return $this->modelManager; } /** * @return string */ public function getClass() { return $this->class; } /** * 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" 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(). * * @param $choices * * @return array An array of choices */ protected function load($choices) { if (is_array($choices) && count($choices) > 0) { $entities = $choices; } elseif ($this->query) { $entities = $this->modelManager->executeQuery($this->query); } else { $entities = $this->modelManager->findBy($this->class); } if (null === $entities) { return array(); } $choices = array(); $this->entities = array(); foreach ($entities as $key => $entity) { if ($this->propertyPath) { // If the property option was given, use it $value = $this->propertyAccessor->getValue($entity, $this->propertyPath); } else { // Otherwise expect a __toString() method in the entity try { $value = (string) $entity; } catch (\Exception $e) { throw new RuntimeException(sprintf('Unable to convert the entity "%s" to string, provide ' .'"property" option or implement "__toString()" method in your entity.', ClassUtils::getClass($entity)), 0, $e); } } 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; } /** * 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]; } }