Completed
Pull Request — master (#717)
by Antoine
20:37 queued 16:00
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
use Doctrine\ORM\Query;
26
27
/**
28
 * Item data provider for the Doctrine ORM.
29
 *
30
 * @author Kévin Dunglas <[email protected]>
31
 * @author Samuel ROZE <[email protected]>
32
 */
33
class ItemDataProvider implements ItemDataProviderInterface
34
{
35
    private $managerRegistry;
36
    private $propertyNameCollectionFactory;
37
    private $propertyMetadataFactory;
38
    private $itemExtensions;
39
40
    /**
41
     * @param ManagerRegistry                        $managerRegistry
42
     * @param PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory
43
     * @param PropertyMetadataFactoryInterface       $propertyMetadataFactory
44
     * @param QueryItemExtensionInterface[]          $itemExtensions
45
     */
46
    public function __construct(ManagerRegistry $managerRegistry, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, array $itemExtensions = [])
47
    {
48
        $this->managerRegistry = $managerRegistry;
49
        $this->propertyNameCollectionFactory = $propertyNameCollectionFactory;
50
        $this->propertyMetadataFactory = $propertyMetadataFactory;
51
        $this->itemExtensions = $itemExtensions;
52
    }
53
54
    /**
55
     * Allow to customize the Query object
56
     * For example you can force doctrine to not load lazy relations by using:
57
     * $query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true).
58
     *
59
     * @param QueryBuilder
60
     *
61
     * @return Query
62
     */
63
    public function getQuery(QueryBuilder $queryBuilder): Query
64
    {
65
        return $queryBuilder->getQuery();
66
    }
67
68
    /**
69
     * {@inheritdoc}
70
     */
71
    public function getItem(string $resourceClass, $id, string $operationName = null, bool $fetchData = false)
72
    {
73
        $manager = $this->managerRegistry->getManagerForClass($resourceClass);
74
        if (null === $manager) {
75
            throw new ResourceClassNotSupportedException();
76
        }
77
78
        $identifiers = $this->normalizeIdentifiers($id, $manager, $resourceClass);
79
80
        if (!$fetchData && $manager instanceof EntityManagerInterface) {
81
            return $manager->getReference($resourceClass, $identifiers);
82
        }
83
84
        $repository = $manager->getRepository($resourceClass);
85
        $queryBuilder = $repository->createQueryBuilder('o');
86
        $queryNameGenerator = new QueryNameGenerator();
87
88
        $this->addWhereForIdentifiers($identifiers, $queryBuilder);
89
90
        foreach ($this->itemExtensions as $extension) {
91
            $extension->applyToItem($queryBuilder, $queryNameGenerator, $resourceClass, $identifiers, $operationName);
92
        }
93
94
        return $queryBuilder->getQuery()->getOneOrNullResult();
95
    }
96
97
    /**
98
     * Add WHERE conditions to the query for one or more identifiers (simple or composite).
99
     *
100
     * @param array        $identifiers
101
     * @param QueryBuilder $queryBuilder
102
     */
103
    private function addWhereForIdentifiers(array $identifiers, QueryBuilder $queryBuilder)
104
    {
105
        if (empty($identifiers)) {
106
            return;
107
        }
108
109
        foreach ($identifiers as $identifier => $value) {
110
            $placeholder = ':id_'.$identifier;
111
            $expression = $queryBuilder->expr()->eq(
112
                'o.'.$identifier,
113
                $placeholder
114
            );
115
116
            $queryBuilder->andWhere($expression);
117
118
            $queryBuilder->setParameter($placeholder, $value);
119
        }
120
    }
121
122
    /**
123
     * Transform and check the identifier, composite or not.
124
     *
125
     * @param int|string    $id
126
     * @param ObjectManager $manager
127
     * @param string        $resourceClass
128
     *
129
     * @return array
130
     */
131
    private function normalizeIdentifiers($id, ObjectManager $manager, string $resourceClass) : array
132
    {
133
        $identifierValues = [$id];
134
        $doctrineMetadataIdentifier = $manager->getClassMetadata($resourceClass)->getIdentifier();
135
136
        if (count($doctrineMetadataIdentifier) >= 2) {
137
            $identifiers = explode(';', $id);
138
            $identifiersMap = [];
139
140
            // first transform identifiers to a proper key/value array
141
            foreach ($identifiers as $identifier) {
142
                $keyValue = explode('=', $identifier);
143
                $identifiersMap[$keyValue[0]] = $keyValue[1];
144
            }
145
        }
146
147
        $identifiers = [];
148
        $i = 0;
149
150
        foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $propertyName) {
151
            $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName);
152
153
            $identifier = $propertyMetadata->isIdentifier();
154
            if (null === $identifier || false === $identifier) {
155
                continue;
156
            }
157
158
            $identifier = $identifiersMap[$propertyName] ?? $identifierValues[$i] ?? null;
159
160
            if (null === $identifier) {
161
                throw new InvalidArgumentException(sprintf('Invalid identifier "%s".', $id));
162
            }
163
164
            $identifiers[$propertyName] = $identifier;
165
            ++$i;
166
        }
167
168
        return $identifiers;
169
    }
170
}
171