Skip to content

Commit

Permalink
GraphQl: Normalize id based on the origin resource
Browse files Browse the repository at this point in the history
  • Loading branch information
soyuka committed Mar 15, 2019
1 parent e1d667f commit 3ae9ef3
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 14 deletions.
3 changes: 2 additions & 1 deletion features/main/input_output.feature
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ Feature: DTO input and output
"""
{
dummyDtoInputOutput(id: "/dummy_dto_input_outputs/1") {
baz
id, baz
}
}
"""
Expand All @@ -254,6 +254,7 @@ Feature: DTO input and output
{
"data": {
"dummyDtoInputOutput": {
"id": "/dummy_dto_input_outputs/1",
"baz": 1
}
}
Expand Down
1 change: 1 addition & 0 deletions src/Bridge/Symfony/Bundle/Resources/config/graphql.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@

<service id="api_platform.graphql.resolver.resource_field" class="ApiPlatform\Core\GraphQl\Resolver\ResourceFieldResolver" public="false">
<argument type="service" id="api_platform.iri_converter" />
<argument type="service" id="api_platform.resource_class_resolver" />
</service>

<service id="api_platform.graphql.schema_builder" class="ApiPlatform\Core\GraphQl\Type\SchemaBuilder" public="false">
Expand Down
13 changes: 11 additions & 2 deletions src/GraphQl/Resolver/ResourceFieldResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
namespace ApiPlatform\Core\GraphQl\Resolver;

use ApiPlatform\Core\Api\IriConverterInterface;
use ApiPlatform\Core\Api\ResourceClassResolverInterface;
use ApiPlatform\Core\GraphQl\Serializer\ItemNormalizer;
use ApiPlatform\Core\Util\ClassInfoTrait;
use GraphQL\Type\Definition\ResolveInfo;

/**
Expand All @@ -26,18 +28,25 @@
*/
final class ResourceFieldResolver
{
use ClassInfoTrait;

private $iriConverter;
private $resourceClassResolver;

public function __construct(IriConverterInterface $iriConverter)
public function __construct(IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver)
{
$this->iriConverter = $iriConverter;
$this->resourceClassResolver = $resourceClassResolver;
}

public function __invoke($source, $args, $context, ResolveInfo $info)
{
$property = null;
if ('id' === $info->fieldName && isset($source[ItemNormalizer::ITEM_KEY])) {
return $this->iriConverter->getIriFromItem(unserialize($source[ItemNormalizer::ITEM_KEY]));
$object = unserialize($source[ItemNormalizer::ITEM_KEY]);
if ($this->resourceClassResolver->isResourceClass($this->getObjectClass($object))) {
return $this->iriConverter->getIriFromItem($object);
}
}

if ('_id' === $info->fieldName && isset($source['id'])) {
Expand Down
6 changes: 6 additions & 0 deletions src/GraphQl/Serializer/ItemNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public function normalize($object, $format = null, array $context = [])

$context['api_normalize'] = true;
$context['resource_class'] = $this->getObjectClass($transformed);
$context['origin_resource'] = $object;

return $this->serializer->normalize($transformed, $format, $context);
}
Expand All @@ -63,6 +64,11 @@ public function normalize($object, $format = null, array $context = [])
throw new UnexpectedValueException('Expected data to be an array');
}

if (($context['origin_resource'] ?? false) && isset($data['id'])) {
$data['id'] = $this->iriConverter->getIriFromItem($context['origin_resource']);
unset($context['origin_resource']);
}

$data[self::ITEM_KEY] = serialize($object); // calling serialize prevent weird normalization process done by Webonyx's GraphQL PHP

return $data;
Expand Down
18 changes: 10 additions & 8 deletions src/GraphQl/Type/SchemaBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ private function convertType(Type $type, bool $input = false, string $mutationNa
}

if ($this->isCollection($type)) {
return $this->paginationEnabled && !$input ? $this->getResourcePaginatedCollectionType($resourceClass, $graphqlType) : GraphQLType::listOf($graphqlType);
return $this->paginationEnabled && !$input && null !== $resourceClass ? $this->getResourcePaginatedCollectionType($resourceClass, $graphqlType) : GraphQLType::listOf($graphqlType);
}

return $type->isNullable() || (null !== $mutationName && 'update' === $mutationName) ? $graphqlType : GraphQLType::nonNull($graphqlType);
Expand All @@ -401,7 +401,15 @@ private function convertType(Type $type, bool $input = false, string $mutationNa
*/
private function getResourceObjectType(?string $resourceClass, ResourceMetadata $resourceMetadata, bool $input = false, string $mutationName = null, int $depth = 0): GraphQLType
{
$shortName = $resourceMetadata->getShortName();
$ioMetadata = $resourceMetadata->getAttribute($input ? 'input' : 'output');

if (null !== $ioMetadata && \array_key_exists('class', $ioMetadata) && null !== $ioMetadata['class']) {
$resourceClass = $ioMetadata['class'];
$shortName = $ioMetadata['name'];
} else {
$shortName = $resourceMetadata->getShortName();
}

if (null !== $mutationName) {
$shortName = $mutationName.ucfirst($shortName);
}
Expand All @@ -415,12 +423,6 @@ private function getResourceObjectType(?string $resourceClass, ResourceMetadata
return $this->graphqlTypes[$shortName];
}

$ioMetadata = $resourceMetadata->getAttribute($input ? 'input' : 'output');

if (null !== $ioMetadata && \array_key_exists('class', $ioMetadata) && null !== $ioMetadata['class']) {
$resourceClass = $ioMetadata['class'];
}

$configuration = [
'name' => $shortName,
'description' => $resourceMetadata->getDescription(),
Expand Down
28 changes: 25 additions & 3 deletions tests/GraphQl/Resolver/ResourceFieldResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace ApiPlatform\Core\Tests\GraphQl\Resolver;

use ApiPlatform\Core\Api\IriConverterInterface;
use ApiPlatform\Core\Api\ResourceClassResolverInterface;
use ApiPlatform\Core\GraphQl\Resolver\ResourceFieldResolver;
use ApiPlatform\Core\GraphQl\Serializer\ItemNormalizer;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy;
Expand All @@ -28,33 +29,54 @@ public function testId()
{
$dummy = new Dummy();

$resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
$resourceClassResolverProphecy->isResourceClass(Dummy::class)->shouldBeCalled()->willReturn(true);

$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
$iriConverterProphecy->getIriFromItem($dummy)->willReturn('/dummies/1')->shouldBeCalled();

$resolveInfo = new ResolveInfo('id', [], new ObjectType(['name' => '']), new ObjectType(['name' => '']), [], new Schema([]), [], null, null, []);

$resolver = new ResourceFieldResolver($iriConverterProphecy->reveal());
$resolver = new ResourceFieldResolver($iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal());
$this->assertEquals('/dummies/1', $resolver([ItemNormalizer::ITEM_KEY => serialize($dummy)], [], [], $resolveInfo));
}

public function testOriginalId()
{
$resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);

$resolveInfo = new ResolveInfo('_id', [], new ObjectType(['name' => '']), new ObjectType(['name' => '']), [], new Schema([]), [], null, null, []);

$resolver = new ResourceFieldResolver($iriConverterProphecy->reveal());
$resolver = new ResourceFieldResolver($iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal());
$this->assertEquals(1, $resolver(['id' => 1], [], [], $resolveInfo));
}

public function testDirectAccess()
{
$resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);

$resolveInfo = new ResolveInfo('foo', [], new ObjectType(['name' => '']), new ObjectType(['name' => '']), [], new Schema([]), [], null, null, []);

$resolver = new ResourceFieldResolver($iriConverterProphecy->reveal());
$resolver = new ResourceFieldResolver($iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal());
$this->assertEquals('bar', $resolver(['foo' => 'bar'], [], [], $resolveInfo));
$this->assertEquals('bar', $resolver((object) ['foo' => 'bar'], [], [], $resolveInfo));
}

public function testNonResource()
{
$dummy = new Dummy();

$resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
$resourceClassResolverProphecy->isResourceClass(Dummy::class)->shouldBeCalled()->willReturn(false);

$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
$iriConverterProphecy->getIriFromItem($dummy)->willReturn('/dummies/1')->shouldNotBeCalled();

$resolveInfo = new ResolveInfo('id', [], new ObjectType(['name' => '']), new ObjectType(['name' => '']), [], new Schema([]), [], null, null, []);

$resolver = new ResourceFieldResolver($iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal());
$this->assertNull($resolver([ItemNormalizer::ITEM_KEY => serialize($dummy)], [], [], $resolveInfo));
}
}

0 comments on commit 3ae9ef3

Please sign in to comment.