/src/Symfony/Bridge/Doctrine/Form/ChoiceList/EntityChoiceList.php

https://github.com/Exercise/symfony · PHP · 394 lines · 156 code · 55 blank · 183 comment · 26 complexity · c6ec8772d76f06ce93d62c475341bb25 MD5 · raw file

  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Bridge\Doctrine\Form\ChoiceList;
  11. use Symfony\Component\Form\Exception\FormException;
  12. use Symfony\Component\Form\Exception\StringCastException;
  13. use Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList;
  14. use Doctrine\Common\Persistence\ObjectManager;
  15. /**
  16. * A choice list presenting a list of Doctrine entities as choices
  17. *
  18. * @author Bernhard Schussek <bschussek@gmail.com>
  19. */
  20. class EntityChoiceList extends ObjectChoiceList
  21. {
  22. /**
  23. * @var ObjectManager
  24. */
  25. private $em;
  26. /**
  27. * @var string
  28. */
  29. private $class;
  30. /**
  31. * @var \Doctrine\Common\Persistence\Mapping\ClassMetadata
  32. */
  33. private $classMetadata;
  34. /**
  35. * Contains the query builder that builds the query for fetching the
  36. * entities
  37. *
  38. * This property should only be accessed through queryBuilder.
  39. *
  40. * @var EntityLoaderInterface
  41. */
  42. private $entityLoader;
  43. /**
  44. * The identifier field, if the identifier is not composite
  45. *
  46. * @var array
  47. */
  48. private $idField = null;
  49. /**
  50. * Whether to use the identifier for index generation
  51. *
  52. * @var Boolean
  53. */
  54. private $idAsIndex = false;
  55. /**
  56. * Whether to use the identifier for value generation
  57. *
  58. * @var Boolean
  59. */
  60. private $idAsValue = false;
  61. /**
  62. * Whether the entities have already been loaded.
  63. *
  64. * @var Boolean
  65. */
  66. private $loaded = false;
  67. /**
  68. * Creates a new entity choice list.
  69. *
  70. * @param ObjectManager $manager An EntityManager instance
  71. * @param string $class The class name
  72. * @param string $labelPath The property path used for the label
  73. * @param EntityLoaderInterface $entityLoader An optional query builder
  74. * @param array $entities An array of choices
  75. * @param string $groupPath A property path pointing to the property used
  76. * to group the choices. Only allowed if
  77. * the choices are given as flat array.
  78. */
  79. public function __construct(ObjectManager $manager, $class, $labelPath = null, EntityLoaderInterface $entityLoader = null, $entities = null, $groupPath = null)
  80. {
  81. $this->em = $manager;
  82. $this->entityLoader = $entityLoader;
  83. $this->classMetadata = $manager->getClassMetadata($class);
  84. $this->class = $this->classMetadata->getName();
  85. $this->loaded = is_array($entities) || $entities instanceof \Traversable;
  86. $identifier = $this->classMetadata->getIdentifierFieldNames();
  87. if (1 === count($identifier)) {
  88. $this->idField = $identifier[0];
  89. $this->idAsValue = true;
  90. if ('integer' === $this->classMetadata->getTypeOfField($this->idField)) {
  91. $this->idAsIndex = true;
  92. }
  93. }
  94. if (!$this->loaded) {
  95. // Make sure the constraints of the parent constructor are
  96. // fulfilled
  97. $entities = array();
  98. }
  99. parent::__construct($entities, $labelPath, array(), $groupPath);
  100. }
  101. /**
  102. * Returns the list of entities
  103. *
  104. * @return array
  105. *
  106. * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
  107. */
  108. public function getChoices()
  109. {
  110. if (!$this->loaded) {
  111. $this->load();
  112. }
  113. return parent::getChoices();
  114. }
  115. /**
  116. * Returns the values for the entities
  117. *
  118. * @return array
  119. *
  120. * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
  121. */
  122. public function getValues()
  123. {
  124. if (!$this->loaded) {
  125. $this->load();
  126. }
  127. return parent::getValues();
  128. }
  129. /**
  130. * Returns the choice views of the preferred choices as nested array with
  131. * the choice groups as top-level keys.
  132. *
  133. * @return array
  134. *
  135. * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
  136. */
  137. public function getPreferredViews()
  138. {
  139. if (!$this->loaded) {
  140. $this->load();
  141. }
  142. return parent::getPreferredViews();
  143. }
  144. /**
  145. * Returns the choice views of the choices that are not preferred as nested
  146. * array with the choice groups as top-level keys.
  147. *
  148. * @return array
  149. *
  150. * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
  151. */
  152. public function getRemainingViews()
  153. {
  154. if (!$this->loaded) {
  155. $this->load();
  156. }
  157. return parent::getRemainingViews();
  158. }
  159. /**
  160. * Returns the entities corresponding to the given values.
  161. *
  162. * @param array $values
  163. *
  164. * @return array
  165. *
  166. * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
  167. */
  168. public function getChoicesForValues(array $values)
  169. {
  170. if (!$this->loaded) {
  171. // Optimize performance in case we have an entity loader and
  172. // a single-field identifier
  173. if ($this->idAsValue && $this->entityLoader) {
  174. if (empty($values)) {
  175. return array();
  176. }
  177. return $this->entityLoader->getEntitiesByIds($this->idField, $values);
  178. }
  179. $this->load();
  180. }
  181. return parent::getChoicesForValues($values);
  182. }
  183. /**
  184. * Returns the values corresponding to the given entities.
  185. *
  186. * @param array $entities
  187. *
  188. * @return array
  189. *
  190. * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
  191. */
  192. public function getValuesForChoices(array $entities)
  193. {
  194. if (!$this->loaded) {
  195. // Optimize performance for single-field identifiers. We already
  196. // know that the IDs are used as values
  197. // Attention: This optimization does not check choices for existence
  198. if ($this->idAsValue) {
  199. $values = array();
  200. foreach ($entities as $entity) {
  201. if ($entity instanceof $this->class) {
  202. // Make sure to convert to the right format
  203. $values[] = $this->fixValue(current($this->getIdentifierValues($entity)));
  204. }
  205. }
  206. return $values;
  207. }
  208. $this->load();
  209. }
  210. return parent::getValuesForChoices($entities);
  211. }
  212. /**
  213. * Returns the indices corresponding to the given entities.
  214. *
  215. * @param array $entities
  216. *
  217. * @return array
  218. *
  219. * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
  220. */
  221. public function getIndicesForChoices(array $entities)
  222. {
  223. if (!$this->loaded) {
  224. // Optimize performance for single-field identifiers. We already
  225. // know that the IDs are used as indices
  226. // Attention: This optimization does not check choices for existence
  227. if ($this->idAsIndex) {
  228. $indices = array();
  229. foreach ($entities as $entity) {
  230. if ($entity instanceof $this->class) {
  231. // Make sure to convert to the right format
  232. $indices[] = $this->fixIndex(current($this->getIdentifierValues($entity)));
  233. }
  234. }
  235. return $indices;
  236. }
  237. $this->load();
  238. }
  239. return parent::getIndicesForChoices($entities);
  240. }
  241. /**
  242. * Returns the entities corresponding to the given values.
  243. *
  244. * @param array $values
  245. *
  246. * @return array
  247. *
  248. * @see Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface
  249. */
  250. public function getIndicesForValues(array $values)
  251. {
  252. if (!$this->loaded) {
  253. // Optimize performance for single-field identifiers.
  254. // Attention: This optimization does not check values for existence
  255. if ($this->idAsIndex && $this->idAsValue) {
  256. return $this->fixIndices($values);
  257. }
  258. $this->load();
  259. }
  260. return parent::getIndicesForValues($values);
  261. }
  262. /**
  263. * Creates a new unique index for this entity.
  264. *
  265. * If the entity has a single-field identifier, this identifier is used.
  266. *
  267. * Otherwise a new integer is generated.
  268. *
  269. * @param mixed $choice The choice to create an index for
  270. *
  271. * @return integer|string A unique index containing only ASCII letters,
  272. * digits and underscores.
  273. */
  274. protected function createIndex($entity)
  275. {
  276. if ($this->idAsIndex) {
  277. return current($this->getIdentifierValues($entity));
  278. }
  279. return parent::createIndex($entity);
  280. }
  281. /**
  282. * Creates a new unique value for this entity.
  283. *
  284. * If the entity has a single-field identifier, this identifier is used.
  285. *
  286. * Otherwise a new integer is generated.
  287. *
  288. * @param mixed $choice The choice to create a value for
  289. *
  290. * @return integer|string A unique value without character limitations.
  291. */
  292. protected function createValue($entity)
  293. {
  294. if ($this->idAsValue) {
  295. return (string) current($this->getIdentifierValues($entity));
  296. }
  297. return parent::createValue($entity);
  298. }
  299. /**
  300. * Loads the list with entities.
  301. */
  302. private function load()
  303. {
  304. if ($this->entityLoader) {
  305. $entities = $this->entityLoader->getEntities();
  306. } else {
  307. $entities = $this->em->getRepository($this->class)->findAll();
  308. }
  309. try {
  310. // The second parameter $labels is ignored by ObjectChoiceList
  311. // The third parameter $preferredChoices is currently not supported
  312. parent::initialize($entities, array(), array());
  313. } catch (StringCastException $e) {
  314. throw new StringCastException(str_replace('argument $labelPath', 'option "property"', $e->getMessage()), null, $e);
  315. }
  316. $this->loaded = true;
  317. }
  318. /**
  319. * Returns the values of the identifier fields of an entity.
  320. *
  321. * Doctrine must know about this entity, that is, the entity must already
  322. * be persisted or added to the identity map before. Otherwise an
  323. * exception is thrown.
  324. *
  325. * @param object $entity The entity for which to get the identifier
  326. *
  327. * @return array The identifier values
  328. *
  329. * @throws FormException If the entity does not exist in Doctrine's identity map
  330. */
  331. private function getIdentifierValues($entity)
  332. {
  333. if (!$this->em->contains($entity)) {
  334. throw new FormException('Entities passed to the choice field must be managed');
  335. }
  336. $this->em->initializeObject($entity);
  337. return $this->classMetadata->getIdentifierValues($entity);
  338. }
  339. }