vendor/symfony/doctrine-bridge/Form/ChoiceList/DoctrineChoiceLoader.php line 113

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <[email protected]>
  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 <[email protected]>
  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 `$idReader` argument 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. $idReader = null;
  81. if (\is_array($value) && $value[0] instanceof IdReader) {
  82. $idReader = $value[0];
  83. } elseif ($value instanceof \Closure && ($rThis = (new \ReflectionFunction($value))->getClosureThis()) instanceof IdReader) {
  84. $idReader = $rThis;
  85. } elseif ($legacy) {
  86. $idReader = $this->idReader;
  87. }
  88. // Optimize performance in case we have an object loader and
  89. // a single-field identifier
  90. if ($idReader && $this->objectLoader) {
  91. $objects = [];
  92. $objectsById = [];
  93. // Maintain order and indices from the given $values
  94. // An alternative approach to the following loop is to add the
  95. // "INDEX BY" clause to the Doctrine query in the loader,
  96. // but I'm not sure whether that's doable in a generic fashion.
  97. foreach ($this->objectLoader->getEntitiesByIds($idReader->getIdField(), $values) as $object) {
  98. $objectsById[$idReader->getIdValue($object)] = $object;
  99. }
  100. foreach ($values as $i => $id) {
  101. if (isset($objectsById[$id])) {
  102. $objects[$i] = $objectsById[$id];
  103. }
  104. }
  105. return $objects;
  106. }
  107. return parent::doLoadChoicesForValues($values, $value);
  108. }
  109. }