DoctrineChoiceLoader::doLoadValuesForChoices()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 10
ccs 0
cts 4
cp 0
crap 6
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
/*
4
 * This file is part of Biurad opensource projects.
5
 *
6
 * @copyright 2019 Biurad Group (https://biurad.com/)
7
 * @license   https://opensource.org/licenses/BSD-3-Clause License
8
 *
9
 * For the full copyright and license information, please view the LICENSE
10
 * file that was distributed with this source code.
11
 */
12
13
namespace Flange\Database\Doctrine\Form\ChoiceList;
14
15
use Doctrine\Persistence\ObjectManager;
16
use Symfony\Component\Form\ChoiceList\Loader\AbstractChoiceLoader;
17
use Symfony\Component\Form\Exception\LogicException;
18
19
/**
20
 * Loads choices using a Doctrine object manager.
21
 *
22
 * @author Bernhard Schussek <[email protected]>
23
 */
24
class DoctrineChoiceLoader extends AbstractChoiceLoader
25
{
26
    private ObjectManager $manager;
27
    private string $class;
28
    private ?IdReader $idReader;
29
    private ?EntityLoaderInterface $objectLoader;
30
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 string $class The class name of the loaded objects
39
     */
40
    public function __construct(ObjectManager $manager, string $class, IdReader $idReader = null, EntityLoaderInterface $objectLoader = null)
41
    {
42
        $classMetadata = $manager->getClassMetadata($class);
43
44
        if ($idReader && !$idReader->isSingleId()) {
45
            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__));
46
        }
47
48
        $this->manager = $manager;
49
        $this->class = $classMetadata->getName();
50
        $this->idReader = $idReader;
51
        $this->objectLoader = $objectLoader;
52
    }
53
54
    /**
55
     * {@inheritdoc}
56
     */
57
    protected function loadChoices(): iterable
58
    {
59
        return $this->objectLoader
60
            ? $this->objectLoader->getEntities()
61
            : $this->manager->getRepository($this->class)->findAll();
62
    }
63
64
    /**
65
     * @internal to be remove in Symfony 6
66
     */
67
    protected function doLoadValuesForChoices(array $choices): array
68
    {
69
        // Optimize performance for single-field identifiers. We already
70
        // know that the IDs are used as values
71
        // Attention: This optimization does not check choices for existence
72
        if ($this->idReader) {
73
            throw new LogicException('Not defining the IdReader explicitly as a value callback when the query can be optimized is not supported.');
74
        }
75
76
        return parent::doLoadValuesForChoices($choices);
77
    }
78
79
    protected function doLoadChoicesForValues(array $values, ?callable $value): array
80
    {
81
        if ($this->idReader && null === $value) {
82
            throw new LogicException('Not defining the IdReader explicitly as a value callback when the query can be optimized is not supported.');
83
        }
84
85
        $idReader = null;
86
87
        if (\is_array($value) && $value[0] instanceof IdReader) {
88
            $idReader = $value[0];
89
        } elseif ($value instanceof \Closure && ($rThis = (new \ReflectionFunction($value))->getClosureThis()) instanceof IdReader) {
90
            $idReader = $rThis;
91
        }
92
93
        // Optimize performance in case we have an object loader and
94
        // a single-field identifier
95
        if ($idReader && $this->objectLoader) {
96
            $objects = [];
97
            $objectsById = [];
98
99
            // Maintain order and indices from the given $values
100
            // An alternative approach to the following loop is to add the
101
            // "INDEX BY" clause to the Doctrine query in the loader,
102
            // but I'm not sure whether that's doable in a generic fashion.
103
            foreach ($this->objectLoader->getEntitiesByIds($idReader->getIdField(), $values) as $object) {
104
                $objectsById[$idReader->getIdValue($object)] = $object;
105
            }
106
107
            foreach ($values as $i => $id) {
108
                if (isset($objectsById[$id])) {
109
                    $objects[$i] = $objectsById[$id];
110
                }
111
            }
112
113
            return $objects;
114
        }
115
116
        return parent::doLoadChoicesForValues($values, $value);
117
    }
118
}
119