Skip to content

Commit 219da4f

Browse files
authored
Merge pull request #1014 from teohhanhui/hotfix/identifiers-type-conversion
Normalize identifiers to the correct type
2 parents 1da4b40 + 42fd15b commit 219da4f

File tree

3 files changed

+237
-112
lines changed

3 files changed

+237
-112
lines changed

src/Bridge/Doctrine/Orm/Util/IdentifierManagerTrait.php

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
use ApiPlatform\Core\Exception\PropertyNotFoundException;
1515
use Doctrine\Common\Persistence\ObjectManager;
16+
use Doctrine\DBAL\Types\Type as DBALType;
17+
use Doctrine\ORM\EntityManagerInterface;
1618

1719
/**
1820
* @internal
@@ -33,19 +35,21 @@ trait IdentifierManagerTrait
3335
*
3436
* @return array
3537
*/
36-
public function normalizeIdentifiers($id, ObjectManager $manager, string $resourceClass): array
38+
private function normalizeIdentifiers($id, ObjectManager $manager, string $resourceClass): array
3739
{
3840
$identifierValues = [$id];
39-
$doctrineMetadataIdentifier = $manager->getClassMetadata($resourceClass)->getIdentifier();
41+
$doctrineClassMetadata = $manager->getClassMetadata($resourceClass);
42+
$doctrineIdentifierFields = $doctrineClassMetadata->getIdentifier();
43+
$isOrm = interface_exists(EntityManagerInterface::class) && $manager instanceof EntityManagerInterface;
44+
$platform = $isOrm ? $manager->getConnection()->getDatabasePlatform() : null;
4045

41-
if (2 <= count($doctrineMetadataIdentifier)) {
42-
$identifiers = explode(';', $id);
46+
if (count($doctrineIdentifierFields) > 1) {
4347
$identifiersMap = [];
4448

4549
// first transform identifiers to a proper key/value array
46-
foreach ($identifiers as $identifier) {
47-
$keyValue = explode('=', $identifier);
48-
$identifiersMap[$keyValue[0]] = $keyValue[1];
50+
foreach (explode(';', $id) as $identifier) {
51+
$identifierPair = explode('=', $identifier);
52+
$identifiersMap[$identifierPair[0]] = $identifierPair[1];
4953
}
5054
}
5155

@@ -55,8 +59,7 @@ public function normalizeIdentifiers($id, ObjectManager $manager, string $resour
5559
foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $propertyName) {
5660
$propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName);
5761

58-
$identifier = $propertyMetadata->isIdentifier();
59-
if (null === $identifier || false === $identifier) {
62+
if (!$propertyMetadata->isIdentifier()) {
6063
continue;
6164
}
6265

@@ -65,6 +68,12 @@ public function normalizeIdentifiers($id, ObjectManager $manager, string $resour
6568
throw new PropertyNotFoundException(sprintf('Invalid identifier "%s", "%s" has not been found.', $id, $propertyName));
6669
}
6770

71+
$doctrineTypeName = $doctrineClassMetadata->getTypeOfField($propertyName);
72+
73+
if ($isOrm && null !== $doctrineTypeName && DBALType::hasType($doctrineTypeName)) {
74+
$identifier = DBALType::getType($doctrineTypeName)->convertToPHPValue($identifier, $platform);
75+
}
76+
6877
$identifiers[$propertyName] = $identifier;
6978
++$i;
7079
}

tests/Bridge/Doctrine/Orm/ItemDataProviderTest.php

Lines changed: 121 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@
2121
use ApiPlatform\Core\Metadata\Property\PropertyNameCollection;
2222
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy;
2323
use Doctrine\Common\Persistence\ManagerRegistry;
24-
use Doctrine\Common\Persistence\ObjectManager;
2524
use Doctrine\Common\Persistence\ObjectRepository;
25+
use Doctrine\DBAL\Connection;
26+
use Doctrine\DBAL\Platforms\AbstractPlatform;
27+
use Doctrine\DBAL\Types\Type as DBALType;
2628
use Doctrine\ORM\AbstractQuery;
29+
use Doctrine\ORM\EntityManagerInterface;
2730
use Doctrine\ORM\EntityRepository;
2831
use Doctrine\ORM\Mapping\ClassMetadata;
2932
use Doctrine\ORM\Query\Expr;
@@ -36,47 +39,6 @@
3639
*/
3740
class ItemDataProviderTest extends \PHPUnit_Framework_TestCase
3841
{
39-
private function getManagerRegistryProphecy(QueryBuilder $queryBuilder, array $identifiers, string $resourceClass)
40-
{
41-
$classMetadataProphecy = $this->prophesize(ClassMetadata::class);
42-
$classMetadataProphecy->getIdentifier()->willReturn($identifiers)->shouldBeCalled();
43-
44-
$repositoryProphecy = $this->prophesize(EntityRepository::class);
45-
$repositoryProphecy->createQueryBuilder('o')->willReturn($queryBuilder)->shouldBeCalled();
46-
47-
$managerProphecy = $this->prophesize(ObjectManager::class);
48-
$managerProphecy->getClassMetadata($resourceClass)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
49-
$managerProphecy->getRepository($resourceClass)->willReturn($repositoryProphecy->reveal())->shouldBeCalled();
50-
51-
$managerRegistryProphecy = $this->prophesize(ManagerRegistry::class);
52-
$managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($managerProphecy->reveal())->shouldBeCalled();
53-
54-
return $managerRegistryProphecy->reveal();
55-
}
56-
57-
private function getMetadataProphecies(array $identifiers, string $resourceClass)
58-
{
59-
$propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
60-
$propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
61-
62-
$nameCollection = ['foobar'];
63-
64-
foreach ($identifiers as $identifier) {
65-
$metadata = new PropertyMetadata();
66-
$metadata = $metadata->withIdentifier(true);
67-
$propertyMetadataFactoryProphecy->create($resourceClass, $identifier)->willReturn($metadata);
68-
69-
$nameCollection[] = $identifier;
70-
}
71-
72-
//random property to prevent the use of non-identifiers metadata while looping
73-
$propertyMetadataFactoryProphecy->create($resourceClass, 'foobar')->willReturn(new PropertyMetadata());
74-
75-
$propertyNameCollectionFactoryProphecy->create($resourceClass)->willReturn(new PropertyNameCollection($nameCollection));
76-
77-
return [$propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal()];
78-
}
79-
8042
public function testGetItemSingleIdentifier()
8143
{
8244
$context = ['foo' => 'bar', 'fetch_data' => true];
@@ -97,9 +59,14 @@ public function testGetItemSingleIdentifier()
9759

9860
$queryBuilder = $queryBuilderProphecy->reveal();
9961

100-
$identifiers = ['id'];
101-
list($propertyNameCollectionFactory, $propertyMetadataFactory) = $this->getMetadataProphecies($identifiers, Dummy::class);
102-
$managerRegistry = $this->getManagerRegistryProphecy($queryBuilder, $identifiers, Dummy::class);
62+
list($propertyNameCollectionFactory, $propertyMetadataFactory) = $this->getMetadataFactories(Dummy::class, [
63+
'id',
64+
]);
65+
$managerRegistry = $this->getManagerRegistry(Dummy::class, [
66+
'id' => [
67+
'type' => DBALType::INTEGER,
68+
],
69+
], $queryBuilder);
10370

10471
$extensionProphecy = $this->prophesize(QueryItemExtensionInterface::class);
10572
$extensionProphecy->applyToItem($queryBuilder, Argument::type(QueryNameGeneratorInterface::class), Dummy::class, ['id' => 1], 'foo', $context)->shouldBeCalled();
@@ -131,9 +98,18 @@ public function testGetItemDoubleIdentifier()
13198

13299
$queryBuilder = $queryBuilderProphecy->reveal();
133100

134-
$identifiers = ['ida', 'idb'];
135-
list($propertyNameCollectionFactory, $propertyMetadataFactory) = $this->getMetadataProphecies($identifiers, Dummy::class);
136-
$managerRegistry = $this->getManagerRegistryProphecy($queryBuilder, $identifiers, Dummy::class);
101+
list($propertyNameCollectionFactory, $propertyMetadataFactory) = $this->getMetadataFactories(Dummy::class, [
102+
'ida',
103+
'idb',
104+
]);
105+
$managerRegistry = $this->getManagerRegistry(Dummy::class, [
106+
'ida' => [
107+
'type' => DBALType::INTEGER,
108+
],
109+
'idb' => [
110+
'type' => DBALType::INTEGER,
111+
],
112+
], $queryBuilder);
137113

138114
$extensionProphecy = $this->prophesize(QueryItemExtensionInterface::class);
139115
$extensionProphecy->applyToItem($queryBuilder, Argument::type(QueryNameGeneratorInterface::class), Dummy::class, ['ida' => 1, 'idb' => 2], 'foo', [])->shouldBeCalled();
@@ -158,9 +134,14 @@ public function testQueryResultExtension()
158134

159135
$queryBuilder = $queryBuilderProphecy->reveal();
160136

161-
$identifiers = ['id'];
162-
list($propertyNameCollectionFactory, $propertyMetadataFactory) = $this->getMetadataProphecies($identifiers, Dummy::class);
163-
$managerRegistry = $this->getManagerRegistryProphecy($queryBuilder, $identifiers, Dummy::class);
137+
list($propertyNameCollectionFactory, $propertyMetadataFactory) = $this->getMetadataFactories(Dummy::class, [
138+
'id',
139+
]);
140+
$managerRegistry = $this->getManagerRegistry(Dummy::class, [
141+
'id' => [
142+
'type' => DBALType::INTEGER,
143+
],
144+
], $queryBuilder);
164145

165146
$extensionProphecy = $this->prophesize(QueryResultItemExtensionInterface::class);
166147
$extensionProphecy->applyToItem($queryBuilder, Argument::type(QueryNameGeneratorInterface::class), Dummy::class, ['id' => 1], 'foo', [])->shouldBeCalled();
@@ -182,8 +163,9 @@ public function testThrowResourceClassNotSupportedException()
182163

183164
$extensionProphecy = $this->prophesize(QueryItemExtensionInterface::class);
184165

185-
$identifiers = ['id'];
186-
list($propertyNameCollectionFactory, $propertyMetadataFactory) = $this->getMetadataProphecies($identifiers, Dummy::class);
166+
list($propertyNameCollectionFactory, $propertyMetadataFactory) = $this->getMetadataFactories(Dummy::class, [
167+
'id',
168+
]);
187169

188170
$dataProvider = new ItemDataProvider($managerRegistryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory, [$extensionProphecy->reveal()]);
189171
$dataProvider->getItem(Dummy::class, 'foo');
@@ -195,22 +177,100 @@ public function testThrowResourceClassNotSupportedException()
195177
*/
196178
public function testCannotCreateQueryBuilder()
197179
{
198-
$identifiers = ['id'];
199-
200180
$repositoryProphecy = $this->prophesize(ObjectRepository::class);
201181
$classMetadataProphecy = $this->prophesize(ClassMetadata::class);
202-
$classMetadataProphecy->getIdentifier()->willReturn($identifiers)->shouldBeCalled();
203-
$managerProphecy = $this->prophesize(ObjectManager::class);
204-
$managerProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
205-
$managerProphecy->getRepository(Dummy::class)->willReturn($repositoryProphecy->reveal())->shouldBeCalled();
182+
$classMetadataProphecy->getIdentifier()->willReturn([
183+
'id',
184+
]);
185+
$classMetadataProphecy->getTypeOfField('id')->willReturn(DBALType::INTEGER);
186+
187+
$platformProphecy = $this->prophesize(AbstractPlatform::class);
188+
189+
$connectionProphecy = $this->prophesize(Connection::class);
190+
$connectionProphecy->getDatabasePlatform()->willReturn($platformProphecy);
191+
192+
$managerProphecy = $this->prophesize(EntityManagerInterface::class);
193+
$managerProphecy->getClassMetadata(Dummy::class)->willReturn($classMetadataProphecy->reveal());
194+
$managerProphecy->getConnection()->willReturn($connectionProphecy);
195+
$managerProphecy->getRepository(Dummy::class)->willReturn($repositoryProphecy->reveal());
206196

207197
$managerRegistryProphecy = $this->prophesize(ManagerRegistry::class);
208-
$managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($managerProphecy->reveal())->shouldBeCalled();
198+
$managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($managerProphecy->reveal());
209199

210200
$extensionProphecy = $this->prophesize(QueryItemExtensionInterface::class);
211201

212-
list($propertyNameCollectionFactory, $propertyMetadataFactory) = $this->getMetadataProphecies($identifiers, Dummy::class);
202+
list($propertyNameCollectionFactory, $propertyMetadataFactory) = $this->getMetadataFactories(Dummy::class, [
203+
'id',
204+
]);
213205

214206
(new ItemDataProvider($managerRegistryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory, [$extensionProphecy->reveal()]))->getItem(Dummy::class, 'foo');
215207
}
208+
209+
/**
210+
* Gets mocked metadata factories.
211+
*
212+
* @param string $resourceClass
213+
* @param array $identifiers
214+
*
215+
* @return array
216+
*/
217+
private function getMetadataFactories(string $resourceClass, array $identifiers): array
218+
{
219+
$propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
220+
$propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
221+
222+
$nameCollection = ['foobar'];
223+
224+
foreach ($identifiers as $identifier) {
225+
$metadata = new PropertyMetadata();
226+
$metadata = $metadata->withIdentifier(true);
227+
$propertyMetadataFactoryProphecy->create($resourceClass, $identifier)->willReturn($metadata);
228+
229+
$nameCollection[] = $identifier;
230+
}
231+
232+
//random property to prevent the use of non-identifiers metadata while looping
233+
$propertyMetadataFactoryProphecy->create($resourceClass, 'foobar')->willReturn(new PropertyMetadata());
234+
235+
$propertyNameCollectionFactoryProphecy->create($resourceClass)->willReturn(new PropertyNameCollection($nameCollection));
236+
237+
return [$propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal()];
238+
}
239+
240+
/**
241+
* Gets a mocked manager registry.
242+
*
243+
* @param string $resourceClass
244+
* @param array $identifierFields
245+
* @param QueryBuilder $queryBuilder
246+
*
247+
* @return ManagerRegistry
248+
*/
249+
private function getManagerRegistry(string $resourceClass, array $identifierFields, QueryBuilder $queryBuilder): ManagerRegistry
250+
{
251+
$classMetadataProphecy = $this->prophesize(ClassMetadata::class);
252+
$classMetadataProphecy->getIdentifier()->willReturn(array_keys($identifierFields));
253+
254+
foreach ($identifierFields as $name => $field) {
255+
$classMetadataProphecy->getTypeOfField($name)->willReturn($field['type']);
256+
}
257+
258+
$platformProphecy = $this->prophesize(AbstractPlatform::class);
259+
260+
$connectionProphecy = $this->prophesize(Connection::class);
261+
$connectionProphecy->getDatabasePlatform()->willReturn($platformProphecy);
262+
263+
$repositoryProphecy = $this->prophesize(EntityRepository::class);
264+
$repositoryProphecy->createQueryBuilder('o')->willReturn($queryBuilder);
265+
266+
$managerProphecy = $this->prophesize(EntityManagerInterface::class);
267+
$managerProphecy->getClassMetadata($resourceClass)->willReturn($classMetadataProphecy->reveal());
268+
$managerProphecy->getConnection()->willReturn($connectionProphecy);
269+
$managerProphecy->getRepository($resourceClass)->willReturn($repositoryProphecy->reveal());
270+
271+
$managerRegistryProphecy = $this->prophesize(ManagerRegistry::class);
272+
$managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($managerProphecy->reveal());
273+
274+
return $managerRegistryProphecy->reveal();
275+
}
216276
}

0 commit comments

Comments
 (0)