Skip to content
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

Revert "fix(graphql): use right nested operation" #5111

Merged
merged 1 commit into from
Nov 4, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
46 changes: 0 additions & 46 deletions features/graphql/collection.feature
Original file line number Diff line number Diff line change
Expand Up @@ -910,49 +910,3 @@ Feature: GraphQL collection support
Then the response status code should be 200
And the response should be in JSON
And the JSON node "data.fooDummies.collection" should have 1 element

@createSchema
Scenario: Retrieve paginated collections using mixed pagination
Given there are 5 fooDummy objects with fake names
When I send the following GraphQL request:
"""
{
fooDummies(page: 1) {
collection {
id
name
soManies(first: 2) {
edges {
node {
content
}
cursor
}
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
}
}
}
paginationInfo {
itemsPerPage
lastPage
totalCount
}
}
}
"""
Then the response status code should be 200
And the response should be in JSON
And the JSON node "data.fooDummies.collection" should have 3 elements
And the JSON node "data.fooDummies.collection[2].id" should exist
And the JSON node "data.fooDummies.collection[2].name" should exist
And the JSON node "data.fooDummies.collection[2].soManies" should exist
And the JSON node "data.fooDummies.collection[2].soManies.edges" should have 2 elements
And the JSON node "data.fooDummies.collection[2].soManies.edges[1].node.content" should be equal to "So many 1"
And the JSON node "data.fooDummies.collection[2].soManies.pageInfo.startCursor" should be equal to "MA=="
And the JSON node "data.fooDummies.paginationInfo.itemsPerPage" should be equal to the number 3
And the JSON node "data.fooDummies.paginationInfo.lastPage" should be equal to the number 2
And the JSON node "data.fooDummies.paginationInfo.totalCount" should be equal to the number 5
36 changes: 5 additions & 31 deletions features/main/default_order.feature
Original file line number Diff line number Diff line change
Expand Up @@ -79,61 +79,35 @@ Feature: Default order
"@type": "FooDummy",
"id": 5,
"name": "Balbo",
"dummy": "/dummies/5",
"soManies": [
"/so_manies/13",
"/so_manies/14",
"/so_manies/15"
]

"dummy": "/dummies/5"
},
{
"@id": "/foo_dummies/3",
"@type": "FooDummy",
"id": 3,
"name": "Sthenelus",
"dummy": "/dummies/3",
"soManies": [
"/so_manies/7",
"/so_manies/8",
"/so_manies/9"
]
"dummy": "/dummies/3"
},
{
"@id": "/foo_dummies/2",
"@type": "FooDummy",
"id": 2,
"name": "Ephesian",
"dummy": "/dummies/2",
"soManies": [
"/so_manies/4",
"/so_manies/5",
"/so_manies/6"
]
"dummy": "/dummies/2"
},
{
"@id": "/foo_dummies/1",
"@type": "FooDummy",
"id": 1,
"name": "Hawsepipe",
"dummy": "/dummies/1",
"soManies": [
"/so_manies/1",
"/so_manies/2",
"/so_manies/3"
]
"dummy": "/dummies/1"
},
{
"@id": "/foo_dummies/4",
"@type": "FooDummy",
"id": 4,
"name": "Separativeness",
"dummy": "/dummies/4",
"soManies": [
"/so_manies/10",
"/so_manies/11",
"/so_manies/12"
]
"dummy": "/dummies/4"
}
],
"hydra:totalItems": 5,
Expand Down
75 changes: 40 additions & 35 deletions src/GraphQl/Type/FieldsBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@
use ApiPlatform\Exception\OperationNotFoundException;
use ApiPlatform\GraphQl\Resolver\Factory\ResolverFactoryInterface;
use ApiPlatform\GraphQl\Type\Definition\TypeInterface;
use ApiPlatform\Metadata\Extractor\DynamicResourceExtractorInterface;
use ApiPlatform\Metadata\GraphQl\Mutation;
use ApiPlatform\Metadata\GraphQl\Operation;
use ApiPlatform\Metadata\GraphQl\Query;
use ApiPlatform\Metadata\GraphQl\QueryCollection;
use ApiPlatform\Metadata\GraphQl\Subscription;
use ApiPlatform\Metadata\Operation as AbstractOperation;
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
Expand All @@ -45,7 +47,7 @@
*/
final class FieldsBuilder implements FieldsBuilderInterface
{
public function __construct(private readonly PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, private readonly PropertyMetadataFactoryInterface $propertyMetadataFactory, private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, private readonly DynamicResourceExtractorInterface $dynamicResourceExtractor, private readonly ResourceClassResolverInterface $resourceClassResolver, private readonly TypesContainerInterface $typesContainer, private readonly TypeBuilderInterface $typeBuilder, private readonly TypeConverterInterface $typeConverter, private readonly ResolverFactoryInterface $itemResolverFactory, private readonly ResolverFactoryInterface $collectionResolverFactory, private readonly ResolverFactoryInterface $itemMutationResolverFactory, private readonly ResolverFactoryInterface $itemSubscriptionResolverFactory, private readonly ContainerInterface $filterLocator, private readonly Pagination $pagination, private readonly ?NameConverterInterface $nameConverter, private readonly string $nestingSeparator)
public function __construct(private readonly PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, private readonly PropertyMetadataFactoryInterface $propertyMetadataFactory, private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, private readonly ResourceClassResolverInterface $resourceClassResolver, private readonly TypesContainerInterface $typesContainer, private readonly TypeBuilderInterface $typeBuilder, private readonly TypeConverterInterface $typeConverter, private readonly ResolverFactoryInterface $itemResolverFactory, private readonly ResolverFactoryInterface $collectionResolverFactory, private readonly ResolverFactoryInterface $itemMutationResolverFactory, private readonly ResolverFactoryInterface $itemSubscriptionResolverFactory, private readonly ContainerInterface $filterLocator, private readonly Pagination $pagination, private readonly ?NameConverterInterface $nameConverter, private readonly string $nestingSeparator)
{
}

Expand Down Expand Up @@ -254,25 +256,7 @@ private function getResourceFieldConfiguration(?string $property, ?string $field
$resourceClass = $type->getClassName();
}

$resourceOperation = $rootOperation;
if ($resourceClass && $rootOperation->getClass() && $this->resourceClassResolver->isResourceClass($resourceClass) && $rootOperation->getClass() !== $resourceClass) {
$resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass);
try {
$resourceOperation = $resourceMetadataCollection->getOperation($isCollectionType ? 'collection_query' : 'item_query');
} catch (OperationNotFoundException) {
// If there is no query operation for a nested resource, use a dynamic resource to get one.
$dynamicResourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($this->dynamicResourceExtractor->addResource($resourceClass));

$resourceOperation = $dynamicResourceMetadataCollection->getOperation($isCollectionType ? 'collection_query' : 'item_query')
->withResource($resourceMetadataCollection[0]);
}
}

if (!$resourceOperation instanceof Operation) {
throw new \LogicException('The resource operation should be a GraphQL operation.');
}

$graphqlType = $this->convertType($type, $input, $resourceOperation, $rootOperation, $resourceClass ?? '', $rootResource, $property, $depth, $forceNullable);
$graphqlType = $this->convertType($type, $input, $rootOperation, $resourceClass ?? '', $rootResource, $property, $depth, $forceNullable);

$graphqlWrappedType = $graphqlType instanceof WrappingType ? $graphqlType->getWrappedType(true) : $graphqlType;
$isStandardGraphqlType = \in_array($graphqlWrappedType, GraphQLType::getStandardTypes(), true);
Expand All @@ -287,22 +271,43 @@ private function getResourceFieldConfiguration(?string $property, ?string $field

$args = [];

$resolverOperation = $rootOperation;

if ($resourceClass && $this->resourceClassResolver->isResourceClass($resourceClass) && $rootOperation->getClass() !== $resourceClass) {
$resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass);
$resolverOperation = $resourceMetadataCollection->getOperation(null, $isCollectionType);

if (!$resolverOperation instanceof Operation) {
$resolverOperation = ($isCollectionType ? new QueryCollection() : new Query())->withOperation($resolverOperation);
}
}

if (!$input && !$rootOperation instanceof Mutation && !$rootOperation instanceof Subscription && !$isStandardGraphqlType && $isCollectionType) {
if ($this->pagination->isGraphQlEnabled($resourceOperation)) {
$args = $this->getGraphQlPaginationArgs($resourceOperation);
if ($this->pagination->isGraphQlEnabled($rootOperation)) {
$args = $this->getGraphQlPaginationArgs($rootOperation);
}

// Find the collection operation to get filters, there might be a smarter way to do this
$operation = null;
if (!empty($resourceClass)) {
$resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($resourceClass);
try {
$operation = $resourceMetadataCollection->getOperation(null, true);
} catch (OperationNotFoundException) {
}
}

$args = $this->getFilterArgs($args, $resourceClass, $rootResource, $resourceOperation, $rootOperation, $property, $depth);
$args = $this->getFilterArgs($args, $resourceClass, $rootResource, $rootOperation, $property, $depth, $operation);
}

if ($isStandardGraphqlType || $input) {
$resolve = null;
} elseif (($rootOperation instanceof Mutation || $rootOperation instanceof Subscription) && $depth <= 0) {
$resolve = $rootOperation instanceof Mutation ? ($this->itemMutationResolverFactory)($resourceClass, $rootResource, $resourceOperation) : ($this->itemSubscriptionResolverFactory)($resourceClass, $rootResource, $resourceOperation);
$resolve = $rootOperation instanceof Mutation ? ($this->itemMutationResolverFactory)($resourceClass, $rootResource, $resolverOperation) : ($this->itemSubscriptionResolverFactory)($resourceClass, $rootResource, $resolverOperation);
} elseif ($this->typeBuilder->isCollection($type)) {
$resolve = ($this->collectionResolverFactory)($resourceClass, $rootResource, $resourceOperation);
$resolve = ($this->collectionResolverFactory)($resourceClass, $rootResource, $resolverOperation);
} else {
$resolve = ($this->itemResolverFactory)($resourceClass, $rootResource, $resourceOperation);
$resolve = ($this->itemResolverFactory)($resourceClass, $rootResource, $resolverOperation);
}

return [
Expand Down Expand Up @@ -363,21 +368,21 @@ private function getGraphQlPaginationArgs(Operation $queryOperation): array
return $args;
}

private function getFilterArgs(array $args, ?string $resourceClass, string $rootResource, Operation $resourceOperation, Operation $rootOperation, ?string $property, int $depth): array
private function getFilterArgs(array $args, ?string $resourceClass, string $rootResource, Operation $rootOperation, ?string $property, int $depth, ?AbstractOperation $operation = null): array
{
if (null === $resourceClass) {
if (null === $operation || null === $resourceClass) {
return $args;
}

foreach ($resourceOperation->getFilters() ?? [] as $filterId) {
foreach ($operation->getFilters() ?? [] as $filterId) {
if (!$this->filterLocator->has($filterId)) {
continue;
}

foreach ($this->filterLocator->get($filterId)->getDescription($resourceClass) as $key => $value) {
$nullable = isset($value['required']) ? !$value['required'] : true;
$filterType = \in_array($value['type'], Type::$builtinTypes, true) ? new Type($value['type'], $nullable) : new Type('object', $nullable, $value['type']);
$graphqlFilterType = $this->convertType($filterType, false, $resourceOperation, $rootOperation, $resourceClass, $rootResource, $property, $depth);
$graphqlFilterType = $this->convertType($filterType, false, $rootOperation, $resourceClass, $rootResource, $property, $depth);

if (str_ends_with($key, '[]')) {
$graphqlFilterType = GraphQLType::listOf($graphqlFilterType);
Expand All @@ -394,14 +399,14 @@ private function getFilterArgs(array $args, ?string $resourceClass, string $root
array_walk_recursive($parsed, static function (&$value) use ($graphqlFilterType): void {
$value = $graphqlFilterType;
});
$args = $this->mergeFilterArgs($args, $parsed, $resourceOperation, $key);
$args = $this->mergeFilterArgs($args, $parsed, $operation, $key);
}
}

return $this->convertFilterArgsToTypes($args);
}

private function mergeFilterArgs(array $args, array $parsed, ?Operation $operation = null, string $original = ''): array
private function mergeFilterArgs(array $args, array $parsed, ?AbstractOperation $operation = null, string $original = ''): array
{
foreach ($parsed as $key => $value) {
// Never override keys that cannot be merged
Expand Down Expand Up @@ -465,7 +470,7 @@ private function convertFilterArgsToTypes(array $args): array
*
* @throws InvalidTypeException
*/
private function convertType(Type $type, bool $input, Operation $resourceOperation, Operation $rootOperation, string $resourceClass, string $rootResource, ?string $property, int $depth, bool $forceNullable = false): GraphQLType|ListOfType|NonNull
private function convertType(Type $type, bool $input, Operation $rootOperation, string $resourceClass, string $rootResource, ?string $property, int $depth, bool $forceNullable = false): GraphQLType|ListOfType|NonNull
{
$graphqlType = $this->typeConverter->convertType($type, $input, $rootOperation, $resourceClass, $rootResource, $property, $depth);

Expand All @@ -482,7 +487,7 @@ private function convertType(Type $type, bool $input, Operation $resourceOperati
}

if ($this->typeBuilder->isCollection($type)) {
return $this->pagination->isGraphQlEnabled($resourceOperation) && !$input ? $this->typeBuilder->getResourcePaginatedCollectionType($graphqlType, $resourceOperation) : GraphQLType::listOf($graphqlType);
return $this->pagination->isGraphQlEnabled($rootOperation) && !$input ? $this->typeBuilder->getResourcePaginatedCollectionType($graphqlType, $resourceClass, $rootOperation) : GraphQLType::listOf($graphqlType);
}

return $forceNullable || !$graphqlType instanceof NullableType || $type->isNullable() || ($rootOperation instanceof Mutation && 'update' === $rootOperation->getName())
Expand Down
2 changes: 1 addition & 1 deletion src/GraphQl/Type/TypeBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ public function getNodeInterface(): InterfaceType
/**
* {@inheritdoc}
*/
public function getResourcePaginatedCollectionType(GraphQLType $resourceType, Operation $operation): GraphQLType
public function getResourcePaginatedCollectionType(GraphQLType $resourceType, string $resourceClass, Operation $operation): GraphQLType
{
$shortName = $resourceType->name;
$paginationType = $this->pagination->getGraphQlPaginationType($operation);
Expand Down
2 changes: 1 addition & 1 deletion src/GraphQl/Type/TypeBuilderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public function getNodeInterface(): InterfaceType;
/**
* Gets the type of a paginated collection of the given resource type.
*/
public function getResourcePaginatedCollectionType(GraphQLType $resourceType, Operation $operation): GraphQLType;
public function getResourcePaginatedCollectionType(GraphQLType $resourceType, string $resourceClass, Operation $operation): GraphQLType;

/**
* Returns true if a type is a collection.
Expand Down
50 changes: 0 additions & 50 deletions src/Metadata/Extractor/DynamicResourceExtractor.php

This file was deleted.

24 changes: 0 additions & 24 deletions src/Metadata/Extractor/DynamicResourceExtractorInterface.php

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,6 @@ public function create(string $resourceClass): ResourceMetadataCollection
$resourceMetadataCollection = $this->decorated->create($resourceClass);
}

if ($resourceMetadataCollection->isDynamic()) {
return $resourceMetadataCollection;
}

try {
$reflectionClass = new \ReflectionClass($resourceClass);
} catch (\ReflectionException) {
Expand Down