Skip to content

Commit

Permalink
Merge pull request #1014 from teohhanhui/hotfix/identifiers-type-conv…
Browse files Browse the repository at this point in the history
…ersion

Normalize identifiers to the correct type
  • Loading branch information
soyuka committed Apr 8, 2017
2 parents 1da4b40 + 42fd15b commit 219da4f
Show file tree
Hide file tree
Showing 3 changed files with 237 additions and 112 deletions.
27 changes: 18 additions & 9 deletions src/Bridge/Doctrine/Orm/Util/IdentifierManagerTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

use ApiPlatform\Core\Exception\PropertyNotFoundException;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\DBAL\Types\Type as DBALType;
use Doctrine\ORM\EntityManagerInterface;

/**
* @internal
Expand All @@ -33,19 +35,21 @@ trait IdentifierManagerTrait
*
* @return array
*/
public function normalizeIdentifiers($id, ObjectManager $manager, string $resourceClass): array
private function normalizeIdentifiers($id, ObjectManager $manager, string $resourceClass): array
{
$identifierValues = [$id];
$doctrineMetadataIdentifier = $manager->getClassMetadata($resourceClass)->getIdentifier();
$doctrineClassMetadata = $manager->getClassMetadata($resourceClass);
$doctrineIdentifierFields = $doctrineClassMetadata->getIdentifier();
$isOrm = interface_exists(EntityManagerInterface::class) && $manager instanceof EntityManagerInterface;
$platform = $isOrm ? $manager->getConnection()->getDatabasePlatform() : null;

if (2 <= count($doctrineMetadataIdentifier)) {
$identifiers = explode(';', $id);
if (count($doctrineIdentifierFields) > 1) {
$identifiersMap = [];

// first transform identifiers to a proper key/value array
foreach ($identifiers as $identifier) {
$keyValue = explode('=', $identifier);
$identifiersMap[$keyValue[0]] = $keyValue[1];
foreach (explode(';', $id) as $identifier) {
$identifierPair = explode('=', $identifier);
$identifiersMap[$identifierPair[0]] = $identifierPair[1];
}
}

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

$identifier = $propertyMetadata->isIdentifier();
if (null === $identifier || false === $identifier) {
if (!$propertyMetadata->isIdentifier()) {
continue;
}

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

$doctrineTypeName = $doctrineClassMetadata->getTypeOfField($propertyName);

if ($isOrm && null !== $doctrineTypeName && DBALType::hasType($doctrineTypeName)) {
$identifier = DBALType::getType($doctrineTypeName)->convertToPHPValue($identifier, $platform);
}

$identifiers[$propertyName] = $identifier;
++$i;
}
Expand Down
182 changes: 121 additions & 61 deletions tests/Bridge/Doctrine/Orm/ItemDataProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@
use ApiPlatform\Core\Metadata\Property\PropertyNameCollection;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy;
use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\Common\Persistence\ObjectRepository;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Type as DBALType;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\Expr;
Expand All @@ -36,47 +39,6 @@
*/
class ItemDataProviderTest extends \PHPUnit_Framework_TestCase
{
private function getManagerRegistryProphecy(QueryBuilder $queryBuilder, array $identifiers, string $resourceClass)
{
$classMetadataProphecy = $this->prophesize(ClassMetadata::class);
$classMetadataProphecy->getIdentifier()->willReturn($identifiers)->shouldBeCalled();

$repositoryProphecy = $this->prophesize(EntityRepository::class);
$repositoryProphecy->createQueryBuilder('o')->willReturn($queryBuilder)->shouldBeCalled();

$managerProphecy = $this->prophesize(ObjectManager::class);
$managerProphecy->getClassMetadata($resourceClass)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
$managerProphecy->getRepository($resourceClass)->willReturn($repositoryProphecy->reveal())->shouldBeCalled();

$managerRegistryProphecy = $this->prophesize(ManagerRegistry::class);
$managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($managerProphecy->reveal())->shouldBeCalled();

return $managerRegistryProphecy->reveal();
}

private function getMetadataProphecies(array $identifiers, string $resourceClass)
{
$propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
$propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);

$nameCollection = ['foobar'];

foreach ($identifiers as $identifier) {
$metadata = new PropertyMetadata();
$metadata = $metadata->withIdentifier(true);
$propertyMetadataFactoryProphecy->create($resourceClass, $identifier)->willReturn($metadata);

$nameCollection[] = $identifier;
}

//random property to prevent the use of non-identifiers metadata while looping
$propertyMetadataFactoryProphecy->create($resourceClass, 'foobar')->willReturn(new PropertyMetadata());

$propertyNameCollectionFactoryProphecy->create($resourceClass)->willReturn(new PropertyNameCollection($nameCollection));

return [$propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal()];
}

public function testGetItemSingleIdentifier()
{
$context = ['foo' => 'bar', 'fetch_data' => true];
Expand All @@ -97,9 +59,14 @@ public function testGetItemSingleIdentifier()

$queryBuilder = $queryBuilderProphecy->reveal();

$identifiers = ['id'];
list($propertyNameCollectionFactory, $propertyMetadataFactory) = $this->getMetadataProphecies($identifiers, Dummy::class);
$managerRegistry = $this->getManagerRegistryProphecy($queryBuilder, $identifiers, Dummy::class);
list($propertyNameCollectionFactory, $propertyMetadataFactory) = $this->getMetadataFactories(Dummy::class, [
'id',
]);
$managerRegistry = $this->getManagerRegistry(Dummy::class, [
'id' => [
'type' => DBALType::INTEGER,
],
], $queryBuilder);

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

$queryBuilder = $queryBuilderProphecy->reveal();

$identifiers = ['ida', 'idb'];
list($propertyNameCollectionFactory, $propertyMetadataFactory) = $this->getMetadataProphecies($identifiers, Dummy::class);
$managerRegistry = $this->getManagerRegistryProphecy($queryBuilder, $identifiers, Dummy::class);
list($propertyNameCollectionFactory, $propertyMetadataFactory) = $this->getMetadataFactories(Dummy::class, [
'ida',
'idb',
]);
$managerRegistry = $this->getManagerRegistry(Dummy::class, [
'ida' => [
'type' => DBALType::INTEGER,
],
'idb' => [
'type' => DBALType::INTEGER,
],
], $queryBuilder);

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

$queryBuilder = $queryBuilderProphecy->reveal();

$identifiers = ['id'];
list($propertyNameCollectionFactory, $propertyMetadataFactory) = $this->getMetadataProphecies($identifiers, Dummy::class);
$managerRegistry = $this->getManagerRegistryProphecy($queryBuilder, $identifiers, Dummy::class);
list($propertyNameCollectionFactory, $propertyMetadataFactory) = $this->getMetadataFactories(Dummy::class, [
'id',
]);
$managerRegistry = $this->getManagerRegistry(Dummy::class, [
'id' => [
'type' => DBALType::INTEGER,
],
], $queryBuilder);

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

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

$identifiers = ['id'];
list($propertyNameCollectionFactory, $propertyMetadataFactory) = $this->getMetadataProphecies($identifiers, Dummy::class);
list($propertyNameCollectionFactory, $propertyMetadataFactory) = $this->getMetadataFactories(Dummy::class, [
'id',
]);

$dataProvider = new ItemDataProvider($managerRegistryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory, [$extensionProphecy->reveal()]);
$dataProvider->getItem(Dummy::class, 'foo');
Expand All @@ -195,22 +177,100 @@ public function testThrowResourceClassNotSupportedException()
*/
public function testCannotCreateQueryBuilder()
{
$identifiers = ['id'];

$repositoryProphecy = $this->prophesize(ObjectRepository::class);
$classMetadataProphecy = $this->prophesize(ClassMetadata::class);
$classMetadataProphecy->getIdentifier()->willReturn($identifiers)->shouldBeCalled();
$managerProphecy = $this->prophesize(ObjectManager::class);
$managerProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
$managerProphecy->getRepository(Dummy::class)->willReturn($repositoryProphecy->reveal())->shouldBeCalled();
$classMetadataProphecy->getIdentifier()->willReturn([
'id',
]);
$classMetadataProphecy->getTypeOfField('id')->willReturn(DBALType::INTEGER);

$platformProphecy = $this->prophesize(AbstractPlatform::class);

$connectionProphecy = $this->prophesize(Connection::class);
$connectionProphecy->getDatabasePlatform()->willReturn($platformProphecy);

$managerProphecy = $this->prophesize(EntityManagerInterface::class);
$managerProphecy->getClassMetadata(Dummy::class)->willReturn($classMetadataProphecy->reveal());
$managerProphecy->getConnection()->willReturn($connectionProphecy);
$managerProphecy->getRepository(Dummy::class)->willReturn($repositoryProphecy->reveal());

$managerRegistryProphecy = $this->prophesize(ManagerRegistry::class);
$managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($managerProphecy->reveal())->shouldBeCalled();
$managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($managerProphecy->reveal());

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

list($propertyNameCollectionFactory, $propertyMetadataFactory) = $this->getMetadataProphecies($identifiers, Dummy::class);
list($propertyNameCollectionFactory, $propertyMetadataFactory) = $this->getMetadataFactories(Dummy::class, [
'id',
]);

(new ItemDataProvider($managerRegistryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory, [$extensionProphecy->reveal()]))->getItem(Dummy::class, 'foo');
}

/**
* Gets mocked metadata factories.
*
* @param string $resourceClass
* @param array $identifiers
*
* @return array
*/
private function getMetadataFactories(string $resourceClass, array $identifiers): array
{
$propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
$propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);

$nameCollection = ['foobar'];

foreach ($identifiers as $identifier) {
$metadata = new PropertyMetadata();
$metadata = $metadata->withIdentifier(true);
$propertyMetadataFactoryProphecy->create($resourceClass, $identifier)->willReturn($metadata);

$nameCollection[] = $identifier;
}

//random property to prevent the use of non-identifiers metadata while looping
$propertyMetadataFactoryProphecy->create($resourceClass, 'foobar')->willReturn(new PropertyMetadata());

$propertyNameCollectionFactoryProphecy->create($resourceClass)->willReturn(new PropertyNameCollection($nameCollection));

return [$propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal()];
}

/**
* Gets a mocked manager registry.
*
* @param string $resourceClass
* @param array $identifierFields
* @param QueryBuilder $queryBuilder
*
* @return ManagerRegistry
*/
private function getManagerRegistry(string $resourceClass, array $identifierFields, QueryBuilder $queryBuilder): ManagerRegistry
{
$classMetadataProphecy = $this->prophesize(ClassMetadata::class);
$classMetadataProphecy->getIdentifier()->willReturn(array_keys($identifierFields));

foreach ($identifierFields as $name => $field) {
$classMetadataProphecy->getTypeOfField($name)->willReturn($field['type']);
}

$platformProphecy = $this->prophesize(AbstractPlatform::class);

$connectionProphecy = $this->prophesize(Connection::class);
$connectionProphecy->getDatabasePlatform()->willReturn($platformProphecy);

$repositoryProphecy = $this->prophesize(EntityRepository::class);
$repositoryProphecy->createQueryBuilder('o')->willReturn($queryBuilder);

$managerProphecy = $this->prophesize(EntityManagerInterface::class);
$managerProphecy->getClassMetadata($resourceClass)->willReturn($classMetadataProphecy->reveal());
$managerProphecy->getConnection()->willReturn($connectionProphecy);
$managerProphecy->getRepository($resourceClass)->willReturn($repositoryProphecy->reveal());

$managerRegistryProphecy = $this->prophesize(ManagerRegistry::class);
$managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($managerProphecy->reveal());

return $managerRegistryProphecy->reveal();
}
}
Loading

0 comments on commit 219da4f

Please sign in to comment.