Completed
Pull Request — master (#717)
by Antoine
11:39 queued 07:22
created

ItemDataProvider::getQuery()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace ApiPlatform\Core\Bridge\Doctrine\Orm;
13
14
use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryItemExtensionInterface;
15
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGenerator;
16
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
17
use ApiPlatform\Core\Exception\InvalidArgumentException;
18
use ApiPlatform\Core\Exception\ResourceClassNotSupportedException;
19
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
20
use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
21
use Doctrine\Common\Persistence\ManagerRegistry;
22
use Doctrine\Common\Persistence\ObjectManager;
23
use Doctrine\ORM\EntityManagerInterface;
24
use Doctrine\ORM\QueryBuilder;
25
26
/**
27
 * Item data provider for the Doctrine ORM.
28
 *
29
 * @author Kévin Dunglas <[email protected]>
30
 * @author Samuel ROZE <[email protected]>
31
 */
32
class ItemDataProvider implements ItemDataProviderInterface
33
{
34
    private $managerRegistry;
35
    private $propertyNameCollectionFactory;
36
    private $propertyMetadataFactory;
37
    private $itemExtensions;
38
39
    /**
40
     * @param ManagerRegistry                        $managerRegistry
41
     * @param PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory
42
     * @param PropertyMetadataFactoryInterface       $propertyMetadataFactory
43
     * @param QueryItemExtensionInterface[]          $itemExtensions
44
     */
45
    public function __construct(ManagerRegistry $managerRegistry, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, array $itemExtensions = [])
46
    {
47
        $this->managerRegistry = $managerRegistry;
48
        $this->propertyNameCollectionFactory = $propertyNameCollectionFactory;
49
        $this->propertyMetadataFactory = $propertyMetadataFactory;
50
        $this->itemExtensions = $itemExtensions;
51
    }
52
53
    /**
54
     * Allow to customize the Query object
55
     * For example you can force doctrine to not load lazy relations by using:
56
     * $query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true).
57
     *
58
     * @param QueryBuilder
59
     *
60
     * @return Query
61
     */
62
    public function getQuery(QueryBuilder $queryBuilder): Query
63
    {
64
        return $queryBuilder->getQuery();
65
    }
66
67
    /**
68
     * {@inheritdoc}
69
     */
70
    public function getItem(string $resourceClass, $id, string $operationName = null, bool $fetchData = false)
71
    {
72
        $manager = $this->managerRegistry->getManagerForClass($resourceClass);
73
        if (null === $manager) {
74
            throw new ResourceClassNotSupportedException();
75
        }
76
77
        $identifiers = $this->normalizeIdentifiers($id, $manager, $resourceClass);
78
79
        if (!$fetchData && $manager instanceof EntityManagerInterface) {
80
            return $manager->getReference($resourceClass, $identifiers);
81
        }
82
83
        $repository = $manager->getRepository($resourceClass);
84
        $queryBuilder = $repository->createQueryBuilder('o');
85
        $queryNameGenerator = new QueryNameGenerator();
86
87
        $this->addWhereForIdentifiers($identifiers, $queryBuilder);
88
89
        foreach ($this->itemExtensions as $extension) {
90
            $extension->applyToItem($queryBuilder, $queryNameGenerator, $resourceClass, $identifiers, $operationName);
91
        }
92
93
        return $queryBuilder->getQuery()->getOneOrNullResult();
94
    }
95
96
    /**
97
     * Add WHERE conditions to the query for one or more identifiers (simple or composite).
98
     *
99
     * @param array        $identifiers
100
     * @param QueryBuilder $queryBuilder
101
     */
102
    private function addWhereForIdentifiers(array $identifiers, QueryBuilder $queryBuilder)
103
    {
104
        if (empty($identifiers)) {
105
            return;
106
        }
107
108
        foreach ($identifiers as $identifier => $value) {
109
            $placeholder = ':id_'.$identifier;
110
            $expression = $queryBuilder->expr()->eq(
111
                'o.'.$identifier,
112
                $placeholder
113
            );
114
115
            $queryBuilder->andWhere($expression);
116
117
            $queryBuilder->setParameter($placeholder, $value);
118
        }
119
    }
120
121
    /**
122
     * Transform and check the identifier, composite or not.
123
     *
124
     * @param int|string    $id
125
     * @param ObjectManager $manager
126
     * @param string        $resourceClass
127
     *
128
     * @return array
129
     */
130
    private function normalizeIdentifiers($id, ObjectManager $manager, string $resourceClass) : array
131
    {
132
        $identifierValues = [$id];
133
        $doctrineMetadataIdentifier = $manager->getClassMetadata($resourceClass)->getIdentifier();
134
135
        if (count($doctrineMetadataIdentifier) >= 2) {
136
            $identifiers = explode(';', $id);
137
            $identifiersMap = [];
138
139
            // first transform identifiers to a proper key/value array
140
            foreach ($identifiers as $identifier) {
141
                $keyValue = explode('=', $identifier);
142
                $identifiersMap[$keyValue[0]] = $keyValue[1];
143
            }
144
        }
145
146
        $identifiers = [];
147
        $i = 0;
148
149
        foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $propertyName) {
150
            $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName);
151
152
            $identifier = $propertyMetadata->isIdentifier();
153
            if (null === $identifier || false === $identifier) {
154
                continue;
155
            }
156
157
            $identifier = $identifiersMap[$propertyName] ?? $identifierValues[$i] ?? null;
158
159
            if (null === $identifier) {
160
                throw new InvalidArgumentException(sprintf('Invalid identifier "%s".', $id));
161
            }
162
163
            $identifiers[$propertyName] = $identifier;
164
            ++$i;
165
        }
166
167
        return $identifiers;
168
    }
169
}
170