DoctrineChoiceLoader.php 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  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\Persistence\ObjectManager;
  12. use Symfony\Component\Form\ChoiceList\Loader\AbstractChoiceLoader;
  13. /**
  14. * Loads choices using a Doctrine object manager.
  15. *
  16. * @author Bernhard Schussek <bschussek@gmail.com>
  17. */
  18. class DoctrineChoiceLoader extends AbstractChoiceLoader
  19. {
  20. private $manager;
  21. private $class;
  22. private $idReader;
  23. private $objectLoader;
  24. /**
  25. * Creates a new choice loader.
  26. *
  27. * Optionally, an implementation of {@link EntityLoaderInterface} can be
  28. * passed which optimizes the object loading for one of the Doctrine
  29. * mapper implementations.
  30. *
  31. * @param string $class The class name of the loaded objects
  32. */
  33. public function __construct(ObjectManager $manager, string $class, IdReader $idReader = null, EntityLoaderInterface $objectLoader = null)
  34. {
  35. $classMetadata = $manager->getClassMetadata($class);
  36. if ($idReader && !$idReader->isSingleId()) {
  37. throw new \InvalidArgumentException(sprintf('The second argument `$idReader` of "%s" must be null when the query cannot be optimized because of composite id fields.', __METHOD__));
  38. }
  39. $this->manager = $manager;
  40. $this->class = $classMetadata->getName();
  41. $this->idReader = $idReader;
  42. $this->objectLoader = $objectLoader;
  43. }
  44. /**
  45. * {@inheritdoc}
  46. */
  47. protected function loadChoices(): iterable
  48. {
  49. return $this->objectLoader
  50. ? $this->objectLoader->getEntities()
  51. : $this->manager->getRepository($this->class)->findAll();
  52. }
  53. /**
  54. * @internal to be remove in Symfony 6
  55. */
  56. protected function doLoadValuesForChoices(array $choices): array
  57. {
  58. // Optimize performance for single-field identifiers. We already
  59. // know that the IDs are used as values
  60. // Attention: This optimization does not check choices for existence
  61. if ($this->idReader) {
  62. trigger_deprecation('symfony/doctrine-bridge', '5.1', 'Not defining explicitly the IdReader as value callback when query can be optimized is deprecated. Don\'t pass the IdReader to "%s" or define the "choice_value" option instead.', __CLASS__);
  63. // Maintain order and indices of the given objects
  64. $values = [];
  65. foreach ($choices as $i => $object) {
  66. if ($object instanceof $this->class) {
  67. $values[$i] = $this->idReader->getIdValue($object);
  68. }
  69. }
  70. return $values;
  71. }
  72. return parent::doLoadValuesForChoices($choices);
  73. }
  74. protected function doLoadChoicesForValues(array $values, ?callable $value): array
  75. {
  76. $legacy = $this->idReader && null === $value;
  77. if ($legacy) {
  78. trigger_deprecation('symfony/doctrine-bridge', '5.1', 'Not defining explicitly the IdReader as value callback when query can be optimized is deprecated. Don\'t pass the IdReader to "%s" or define the "choice_value" option instead.', __CLASS__);
  79. }
  80. // Optimize performance in case we have an object loader and
  81. // a single-field identifier
  82. if (($legacy || \is_array($value) && $this->idReader === $value[0]) && $this->objectLoader) {
  83. $objects = [];
  84. $objectsById = [];
  85. // Maintain order and indices from the given $values
  86. // An alternative approach to the following loop is to add the
  87. // "INDEX BY" clause to the Doctrine query in the loader,
  88. // but I'm not sure whether that's doable in a generic fashion.
  89. foreach ($this->objectLoader->getEntitiesByIds($this->idReader->getIdField(), $values) as $object) {
  90. $objectsById[$this->idReader->getIdValue($object)] = $object;
  91. }
  92. foreach ($values as $i => $id) {
  93. if (isset($objectsById[$id])) {
  94. $objects[$i] = $objectsById[$id];
  95. }
  96. }
  97. return $objects;
  98. }
  99. return parent::doLoadChoicesForValues($values, $value);
  100. }
  101. }