Skip to content

GraphQl: interface implementation #3306

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from
Closed
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
10 changes: 10 additions & 0 deletions src/Annotation/ApiResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,16 @@ final class ApiResource
*/
public $subresourceOperations;

/**
* @var bool
*/
public $isInterface;

/**
* @var array
*/
public $implements;

Comment on lines +119 to +128
Copy link
Contributor

Choose a reason for hiding this comment

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

This should probably be under the graphql-key, as this is only supported for the GraphQL format / endpoints.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@lukasluecke can you explain graphql-key - what does it mean? Prefix for property name?

Copy link
Contributor

Choose a reason for hiding this comment

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

@CvekCoding There is already a configuration key graphql, under which we configure the operations etc. - I think it would make sense to include it inside of that, instead of at the root level.

/**
* @see https://api-platform.com/docs/core/performance/#setting-custom-http-cache-headers
* @see https://github.com/Haehnchen/idea-php-annotation-plugin/issues/112
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 @@ -16,6 +16,7 @@
<argument type="service" id="api_platform.graphql.resolver.stage.serialize" />
<argument type="service" id="api_platform.graphql.query_resolver_locator" />
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
<argument type="service" id="api_platform.graphql.type_builder" />
</service>

<service id="api_platform.graphql.resolver.factory.collection" class="ApiPlatform\Core\GraphQl\Resolver\Factory\CollectionResolverFactory" public="false">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@
<argument type="service" id="api_platform.metadata.property.name_collection_factory.inherited.inner" />
</service>

<service id="api_platform.metadata.property.name_collection_factory.inherited_interface" class="ApiPlatform\Core\Metadata\Property\Factory\InheritedPropertyNameInterfaceCollectionFactory" decorates="api_platform.metadata.property.name_collection_factory" decoration-priority="5" public="false">
<argument type="service" id="api_platform.metadata.resource.name_collection_factory" />
<argument type="service" id="api_platform.metadata.resource.metadata_factory"/>
<argument type="service" id="api_platform.metadata.property.name_collection_factory.property_info" />
<argument type="service" id="api_platform.metadata.property.name_collection_factory.inherited_interface.inner" />
</service>

<service id="api_platform.metadata.property.name_collection_factory.cached" class="ApiPlatform\Core\Metadata\Property\Factory\CachedPropertyNameCollectionFactory" decorates="api_platform.metadata.property.name_collection_factory" decoration-priority="-10" public="false">
<argument type="service" id="api_platform.cache.metadata.property" />
<argument type="service" id="api_platform.metadata.property.name_collection_factory.cached.inner" />
Expand Down
38 changes: 28 additions & 10 deletions src/GraphQl/Resolver/Factory/ItemResolverFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,18 @@

namespace ApiPlatform\Core\GraphQl\Resolver\Factory;

use ApiPlatform\Core\Exception\ResourceClassNotFoundException;
use ApiPlatform\Core\GraphQl\Resolver\QueryItemResolverInterface;
use ApiPlatform\Core\GraphQl\Resolver\Stage\ReadStageInterface;
use ApiPlatform\Core\GraphQl\Resolver\Stage\SecurityPostDenormalizeStageInterface;
use ApiPlatform\Core\GraphQl\Resolver\Stage\SecurityStageInterface;
use ApiPlatform\Core\GraphQl\Resolver\Stage\SerializeStageInterface;
use ApiPlatform\Core\GraphQl\Type\TypeBuilder;
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
use ApiPlatform\Core\Util\ClassInfoTrait;
use ApiPlatform\Core\Util\CloneTrait;
use GraphQL\Error\Error;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ResolveInfo;
use Psr\Container\ContainerInterface;

Expand All @@ -45,15 +48,17 @@ final class ItemResolverFactory implements ResolverFactoryInterface
private $serializeStage;
private $queryResolverLocator;
private $resourceMetadataFactory;
private $typeBuilder;

public function __construct(ReadStageInterface $readStage, SecurityStageInterface $securityStage, SecurityPostDenormalizeStageInterface $securityPostDenormalizeStage, SerializeStageInterface $serializeStage, ContainerInterface $queryResolverLocator, ResourceMetadataFactoryInterface $resourceMetadataFactory)
public function __construct(ReadStageInterface $readStage, SecurityStageInterface $securityStage, SecurityPostDenormalizeStageInterface $securityPostDenormalizeStage, SerializeStageInterface $serializeStage, ContainerInterface $queryResolverLocator, ResourceMetadataFactoryInterface $resourceMetadataFactory, TypeBuilder $typeBuilder)
{
$this->readStage = $readStage;
$this->securityStage = $securityStage;
$this->securityPostDenormalizeStage = $securityPostDenormalizeStage;
$this->serializeStage = $serializeStage;
$this->queryResolverLocator = $queryResolverLocator;
$this->resourceMetadataFactory = $resourceMetadataFactory;
$this->typeBuilder = $typeBuilder;
}

public function __invoke(?string $resourceClass = null, ?string $rootClass = null, ?string $operationName = null): callable
Expand Down Expand Up @@ -84,16 +89,16 @@ public function __invoke(?string $resourceClass = null, ?string $rootClass = nul
}

($this->securityStage)($resourceClass, $operationName, $resolverContext + [
'extra_variables' => [
'object' => $item,
],
]);
'extra_variables' => [
'object' => $item,
],
]);
($this->securityPostDenormalizeStage)($resourceClass, $operationName, $resolverContext + [
'extra_variables' => [
'object' => $item,
'previous_object' => $this->clone($item),
],
]);
'extra_variables' => [
'object' => $item,
'previous_object' => $this->clone($item),
],
]);

return ($this->serializeStage)($item, $resourceClass, $operationName, $resolverContext);
};
Expand All @@ -120,6 +125,19 @@ private function getResourceClass($item, ?string $resourceClass, ResolveInfo $in
return $itemClass;
}

if ($info->returnType instanceof InterfaceType) {
try {
$returnItemMetadata = $this->resourceMetadataFactory->create($itemClass);
} catch (ResourceClassNotFoundException $e) {
throw Error::createLocatedError(sprintf($errorMessage, (new \ReflectionClass($resourceClass))->getShortName(), (new \ReflectionClass($itemClass))->getShortName()), $info->fieldNodes, $info->path);
}

$returnType = $this->typeBuilder->getResourceObjectType($resourceClass, $returnItemMetadata, false, null, null);
if ($returnType->implementsInterface($info->returnType)) {
return $resourceClass;
}
}

if ($resourceClass !== $itemClass) {
throw Error::createLocatedError(sprintf($errorMessage, (new \ReflectionClass($resourceClass))->getShortName(), (new \ReflectionClass($itemClass))->getShortName()), $info->fieldNodes, $info->path);
}
Expand Down
4 changes: 4 additions & 0 deletions src/GraphQl/Type/SchemaBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ public function getSchema(): Schema
continue;
}

if ($resourceMetadata->isInterface()) {
continue;
}

Comment on lines +75 to +78
Copy link
Contributor

Choose a reason for hiding this comment

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

I am not sure skipping the operations for interfaces is the correct approach. And if we do, we should at least trigger warnings or something if they are defined anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Weak place, agree.
I did this because in my project I dont need mutations in interfaces. And I use custom operations very few.
Additionally I think I had some issues with mutations in interface types, but dont remember details now :(

We can safely move this conditions to include custom query operations here

if ($resourceMetadata->getGraphqlAttribute($operationName, 'item_query')) {
$queryFields += $this->fieldsBuilder->getItemQueryFields($resourceClass, $resourceMetadata, $operationName, $value);

Expand Down
Loading