Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The call to interface_exists is useless.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think PHPStan will complain:

  • Existence of classes and interfaces in instanceof, catch, typehints, other language constructs and even annotations. PHP does not do this and just stays silent instead.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we skip the check?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think PHPStan is trying to help us avoid typos and missing imports.

$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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice name!

$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