|
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\Extension\QueryResultItemExtensionInterface; |
|
16
|
|
|
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGenerator; |
|
17
|
|
|
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; |
|
18
|
|
|
use ApiPlatform\Core\Exception\PropertyNotFoundException; |
|
19
|
|
|
use ApiPlatform\Core\Exception\ResourceClassNotSupportedException; |
|
20
|
|
|
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; |
|
21
|
|
|
use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; |
|
22
|
|
|
use Doctrine\Common\Persistence\ManagerRegistry; |
|
23
|
|
|
use Doctrine\Common\Persistence\ObjectManager; |
|
24
|
|
|
use Doctrine\ORM\EntityManagerInterface; |
|
25
|
|
|
use Doctrine\ORM\QueryBuilder; |
|
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
|
|
|
* {@inheritdoc} |
|
56
|
|
|
*/ |
|
57
|
|
|
public function getItem(string $resourceClass, $id, string $operationName = null, bool $fetchData = false) |
|
58
|
|
|
{ |
|
59
|
|
|
$manager = $this->managerRegistry->getManagerForClass($resourceClass); |
|
60
|
|
|
if (null === $manager) { |
|
61
|
|
|
throw new ResourceClassNotSupportedException(); |
|
62
|
|
|
} |
|
63
|
|
|
|
|
64
|
|
|
$identifiers = $this->normalizeIdentifiers($id, $manager, $resourceClass); |
|
65
|
|
|
|
|
66
|
|
|
if (!$fetchData && $manager instanceof EntityManagerInterface) { |
|
67
|
|
|
return $manager->getReference($resourceClass, $identifiers); |
|
68
|
|
|
} |
|
69
|
|
|
|
|
70
|
|
|
$repository = $manager->getRepository($resourceClass); |
|
71
|
|
|
$queryBuilder = $repository->createQueryBuilder('o'); |
|
72
|
|
|
$queryNameGenerator = new QueryNameGenerator(); |
|
73
|
|
|
|
|
74
|
|
|
$this->addWhereForIdentifiers($identifiers, $queryBuilder); |
|
75
|
|
|
|
|
76
|
|
View Code Duplication |
foreach ($this->itemExtensions as $extension) { |
|
|
|
|
|
|
77
|
|
|
$extension->applyToItem($queryBuilder, $queryNameGenerator, $resourceClass, $identifiers, $operationName); |
|
78
|
|
|
|
|
79
|
|
|
if ($extension instanceof QueryResultItemExtensionInterface && $extension->supportsResult($resourceClass, $operationName)) { |
|
80
|
|
|
return $extension->getResult($queryBuilder); |
|
81
|
|
|
} |
|
82
|
|
|
} |
|
83
|
|
|
|
|
84
|
|
|
return $queryBuilder->getQuery()->getOneOrNullResult(); |
|
85
|
|
|
} |
|
86
|
|
|
|
|
87
|
|
|
/** |
|
88
|
|
|
* Add WHERE conditions to the query for one or more identifiers (simple or composite). |
|
89
|
|
|
* |
|
90
|
|
|
* @param array $identifiers |
|
91
|
|
|
* @param QueryBuilder $queryBuilder |
|
92
|
|
|
*/ |
|
93
|
|
|
private function addWhereForIdentifiers(array $identifiers, QueryBuilder $queryBuilder) |
|
94
|
|
|
{ |
|
95
|
|
|
foreach ($identifiers as $identifier => $value) { |
|
96
|
|
|
$placeholder = ':id_'.$identifier; |
|
97
|
|
|
$expression = $queryBuilder->expr()->eq( |
|
98
|
|
|
'o.'.$identifier, |
|
99
|
|
|
$placeholder |
|
100
|
|
|
); |
|
101
|
|
|
|
|
102
|
|
|
$queryBuilder->andWhere($expression); |
|
103
|
|
|
|
|
104
|
|
|
$queryBuilder->setParameter($placeholder, $value); |
|
105
|
|
|
} |
|
106
|
|
|
} |
|
107
|
|
|
|
|
108
|
|
|
/** |
|
109
|
|
|
* Transform and check the identifier, composite or not. |
|
110
|
|
|
* |
|
111
|
|
|
* @param int|string $id |
|
112
|
|
|
* @param ObjectManager $manager |
|
113
|
|
|
* @param string $resourceClass |
|
114
|
|
|
* |
|
115
|
|
|
* @return array |
|
116
|
|
|
*/ |
|
117
|
|
|
private function normalizeIdentifiers($id, ObjectManager $manager, string $resourceClass) : array |
|
118
|
|
|
{ |
|
119
|
|
|
$identifierValues = [$id]; |
|
120
|
|
|
$doctrineMetadataIdentifier = $manager->getClassMetadata($resourceClass)->getIdentifier(); |
|
121
|
|
|
|
|
122
|
|
|
if (count($doctrineMetadataIdentifier) >= 2) { |
|
123
|
|
|
$identifiers = explode(';', $id); |
|
124
|
|
|
$identifiersMap = []; |
|
125
|
|
|
|
|
126
|
|
|
// first transform identifiers to a proper key/value array |
|
127
|
|
|
foreach ($identifiers as $identifier) { |
|
128
|
|
|
$keyValue = explode('=', $identifier); |
|
129
|
|
|
$identifiersMap[$keyValue[0]] = $keyValue[1]; |
|
130
|
|
|
} |
|
131
|
|
|
} |
|
132
|
|
|
|
|
133
|
|
|
$identifiers = []; |
|
134
|
|
|
$i = 0; |
|
135
|
|
|
|
|
136
|
|
|
foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $propertyName) { |
|
137
|
|
|
$propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName); |
|
138
|
|
|
|
|
139
|
|
|
$identifier = $propertyMetadata->isIdentifier(); |
|
140
|
|
|
if (null === $identifier || false === $identifier) { |
|
141
|
|
|
continue; |
|
142
|
|
|
} |
|
143
|
|
|
|
|
144
|
|
|
$identifier = !isset($identifiersMap) ? $identifierValues[$i] ?? null : $identifiersMap[$propertyName] ?? null; |
|
145
|
|
|
|
|
146
|
|
|
if (null === $identifier) { |
|
147
|
|
|
throw new PropertyNotFoundException(sprintf('Invalid identifier "%s", "%s" has not been found.', $id, $propertyName)); |
|
148
|
|
|
} |
|
149
|
|
|
|
|
150
|
|
|
$identifiers[$propertyName] = $identifier; |
|
151
|
|
|
++$i; |
|
152
|
|
|
} |
|
153
|
|
|
|
|
154
|
|
|
return $identifiers; |
|
155
|
|
|
} |
|
156
|
|
|
} |
|
157
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.