DoctrineChoiceLoader.php 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  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 Doctrine\Common\Persistence\ObjectManager;
  12. use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
  13. use Symfony\Component\Form\ChoiceList\Factory\ChoiceListFactoryInterface;
  14. use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
  15. /**
  16. * Loads choices using a Doctrine object manager.
  17. *
  18. * @author Bernhard Schussek <bschussek@gmail.com>
  19. */
  20. class DoctrineChoiceLoader implements ChoiceLoaderInterface
  21. {
  22. private $factory;
  23. private $manager;
  24. private $class;
  25. private $idReader;
  26. private $objectLoader;
  27. /**
  28. * @var ChoiceListInterface
  29. */
  30. private $choiceList;
  31. /**
  32. * Creates a new choice loader.
  33. *
  34. * Optionally, an implementation of {@link EntityLoaderInterface} can be
  35. * passed which optimizes the object loading for one of the Doctrine
  36. * mapper implementations.
  37. *
  38. * @param ChoiceListFactoryInterface $factory The factory for creating the loaded choice list
  39. * @param ObjectManager $manager The object manager
  40. * @param string $class The class name of the loaded objects
  41. * @param IdReader $idReader The reader for the object IDs
  42. * @param EntityLoaderInterface|null $objectLoader The objects loader
  43. */
  44. public function __construct(ChoiceListFactoryInterface $factory, ObjectManager $manager, $class, IdReader $idReader = null, EntityLoaderInterface $objectLoader = null)
  45. {
  46. $classMetadata = $manager->getClassMetadata($class);
  47. $this->factory = $factory;
  48. $this->manager = $manager;
  49. $this->class = $classMetadata->getName();
  50. $this->idReader = $idReader ?: new IdReader($manager, $classMetadata);
  51. $this->objectLoader = $objectLoader;
  52. }
  53. /**
  54. * {@inheritdoc}
  55. */
  56. public function loadChoiceList($value = null)
  57. {
  58. if ($this->choiceList) {
  59. return $this->choiceList;
  60. }
  61. $objects = $this->objectLoader
  62. ? $this->objectLoader->getEntities()
  63. : $this->manager->getRepository($this->class)->findAll();
  64. $this->choiceList = $this->factory->createListFromChoices($objects, $value);
  65. return $this->choiceList;
  66. }
  67. /**
  68. * {@inheritdoc}
  69. */
  70. public function loadValuesForChoices(array $choices, $value = null)
  71. {
  72. // Performance optimization
  73. if (empty($choices)) {
  74. return array();
  75. }
  76. // Optimize performance for single-field identifiers. We already
  77. // know that the IDs are used as values
  78. $optimize = null === $value || \is_array($value) && $value[0] === $this->idReader;
  79. // Attention: This optimization does not check choices for existence
  80. if ($optimize && !$this->choiceList && $this->idReader->isSingleId()) {
  81. $values = array();
  82. // Maintain order and indices of the given objects
  83. foreach ($choices as $i => $object) {
  84. if ($object instanceof $this->class) {
  85. // Make sure to convert to the right format
  86. $values[$i] = (string) $this->idReader->getIdValue($object);
  87. }
  88. }
  89. return $values;
  90. }
  91. return $this->loadChoiceList($value)->getValuesForChoices($choices);
  92. }
  93. /**
  94. * {@inheritdoc}
  95. */
  96. public function loadChoicesForValues(array $values, $value = null)
  97. {
  98. // Performance optimization
  99. // Also prevents the generation of "WHERE id IN ()" queries through the
  100. // object loader. At least with MySQL and on the development machine
  101. // this was tested on, no exception was thrown for such invalid
  102. // statements, consequently no test fails when this code is removed.
  103. // https://github.com/symfony/symfony/pull/8981#issuecomment-24230557
  104. if (empty($values)) {
  105. return array();
  106. }
  107. // Optimize performance in case we have an object loader and
  108. // a single-field identifier
  109. $optimize = null === $value || \is_array($value) && $value[0] === $this->idReader;
  110. if ($optimize && !$this->choiceList && $this->objectLoader && $this->idReader->isSingleId()) {
  111. $unorderedObjects = $this->objectLoader->getEntitiesByIds($this->idReader->getIdField(), $values);
  112. $objectsById = array();
  113. $objects = array();
  114. // Maintain order and indices from the given $values
  115. // An alternative approach to the following loop is to add the
  116. // "INDEX BY" clause to the Doctrine query in the loader,
  117. // but I'm not sure whether that's doable in a generic fashion.
  118. foreach ($unorderedObjects as $object) {
  119. $objectsById[(string) $this->idReader->getIdValue($object)] = $object;
  120. }
  121. foreach ($values as $i => $id) {
  122. if (isset($objectsById[$id])) {
  123. $objects[$i] = $objectsById[$id];
  124. }
  125. }
  126. return $objects;
  127. }
  128. return $this->loadChoiceList($value)->getChoicesForValues($values);
  129. }
  130. }