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
24 changes: 23 additions & 1 deletion features/graphql/filters.feature
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,33 @@ Feature: Collections filtering
}
}
"""
And the JSON node "data.dummies.edges[0].node.relatedDummies.edges" should have 0 elements
Then the JSON node "data.dummies.edges[0].node.relatedDummies.edges" should have 0 elements
And the JSON node "data.dummies.edges[1].node.relatedDummies.edges" should have 0 elements
And the JSON node "data.dummies.edges[2].node.relatedDummies.edges" should have 1 element
And the JSON node "data.dummies.edges[2].node.relatedDummies.edges[0].node.name" should be equal to "RelatedDummy13"

@createSchema
Scenario: Use a filter of a nested collection
Given there is a DummyCar entity with related colors
When I send the following GraphQL request:
"""
{
dummyCar(id: "/dummy_cars/1") {
id
colors(prop: "blue") {
edges {
node {
id
prop
}
}
}
}
}
"""
Then the JSON node "data.dummyCar.colors.edges" should have 1 element
And the JSON node "data.dummyCar.colors.edges[0].node.prop" should be equal to "blue"

@createSchema
Scenario: Retrieve a collection filtered using the related search filter
Given there are 1 dummy objects having each 2 relatedDummies
Expand Down
96 changes: 54 additions & 42 deletions src/GraphQl/Type/SchemaBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,12 @@ private function getQueryFields(string $resourceClass, ResourceMetadata $resourc
$shortName = $resourceMetadata->getShortName();
$deprecationReason = $resourceMetadata->getGraphqlAttribute('query', 'deprecation_reason', '', true);

if ($fieldConfiguration = $this->getResourceFieldConfiguration($resourceClass, $resourceMetadata, null, $deprecationReason, new Type(Type::BUILTIN_TYPE_OBJECT, true, $resourceClass), $resourceClass)) {
if ($fieldConfiguration = $this->getResourceFieldConfiguration(null, $deprecationReason, new Type(Type::BUILTIN_TYPE_OBJECT, true, $resourceClass), $resourceClass)) {
$fieldConfiguration['args'] += ['id' => ['type' => GraphQLType::id()]];
$queryFields[lcfirst($shortName)] = $fieldConfiguration;
}

if ($fieldConfiguration = $this->getResourceFieldConfiguration($resourceClass, $resourceMetadata, null, $deprecationReason, new Type(Type::BUILTIN_TYPE_OBJECT, false, null, true, null, new Type(Type::BUILTIN_TYPE_OBJECT, false, $resourceClass)), $resourceClass)) {
if ($fieldConfiguration = $this->getResourceFieldConfiguration(null, $deprecationReason, new Type(Type::BUILTIN_TYPE_OBJECT, false, null, true, null, new Type(Type::BUILTIN_TYPE_OBJECT, false, $resourceClass)), $resourceClass)) {
$queryFields[lcfirst(Inflector::pluralize($shortName))] = $fieldConfiguration;
}

Expand All @@ -189,8 +189,8 @@ private function getMutationFields(string $resourceClass, ResourceMetadata $reso
$resourceType = new Type(Type::BUILTIN_TYPE_OBJECT, true, $resourceClass);
$deprecationReason = $resourceMetadata->getGraphqlAttribute($mutationName, 'deprecation_reason', '', true);

if ($fieldConfiguration = $this->getResourceFieldConfiguration($resourceClass, $resourceMetadata, ucfirst("{$mutationName}s a $shortName."), $deprecationReason, $resourceType, $resourceClass, false, $mutationName)) {
$fieldConfiguration['args'] += ['input' => $this->getResourceFieldConfiguration($resourceClass, $resourceMetadata, null, $deprecationReason, $resourceType, $resourceClass, true, $mutationName)];
if ($fieldConfiguration = $this->getResourceFieldConfiguration(ucfirst("{$mutationName}s a $shortName."), $deprecationReason, $resourceType, $resourceClass, false, $mutationName)) {
$fieldConfiguration['args'] += ['input' => $this->getResourceFieldConfiguration(null, $deprecationReason, $resourceType, $resourceClass, true, $mutationName)];

if (!$this->isCollection($resourceType)) {
$itemMutationResolverFactory = $this->itemMutationResolverFactory;
Expand All @@ -206,19 +206,27 @@ private function getMutationFields(string $resourceClass, ResourceMetadata $reso
*
* @see http://webonyx.github.io/graphql-php/type-system/object-types/
*/
private function getResourceFieldConfiguration(string $resourceClass, ResourceMetadata $resourceMetadata, ?string $fieldDescription, string $deprecationReason, Type $type, string $rootResource, bool $input = false, string $mutationName = null, int $depth = 0): ?array
private function getResourceFieldConfiguration(?string $fieldDescription, string $deprecationReason, Type $type, string $rootResource, bool $input = false, string $mutationName = null, int $depth = 0): ?array
{
try {
$resourceClass = $this->isCollection($type) && ($collectionValueType = $type->getCollectionValueType()) ? $collectionValueType->getClassName() : $type->getClassName();

if (null === $graphqlType = $this->convertType($type, $input, $mutationName, $depth)) {
return null;
}

$graphqlWrappedType = $graphqlType instanceof WrappingType ? $graphqlType->getWrappedType() : $graphqlType;
$isStandardGraphqlType = \in_array($graphqlWrappedType, GraphQLType::getStandardTypes(), true);
if ($isStandardGraphqlType) {
$className = '';
} else {
$className = $this->isCollection($type) && ($collectionValueType = $type->getCollectionValueType()) ? $collectionValueType->getClassName() : $type->getClassName();
$resourceClass = '';
}

$resourceMetadata = null;
if (!empty($resourceClass)) {
try {
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
} catch (ResourceClassNotFoundException $e) {
}
}

$args = [];
Expand All @@ -236,39 +244,14 @@ private function getResourceFieldConfiguration(string $resourceClass, ResourceMe
];
}

foreach ($resourceMetadata->getGraphqlAttribute('query', 'filters', [], true) as $filterId) {
if (null === $this->filterLocator || !$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, null, $depth);

if ('[]' === substr($key, -2)) {
$graphqlFilterType = GraphQLType::listOf($graphqlFilterType);
$key = substr($key, 0, -2).'_list';
}

parse_str($key, $parsed);
if (\array_key_exists($key, $parsed) && \is_array($parsed[$key])) {
$parsed = [$key => ''];
}
array_walk_recursive($parsed, function (&$value) use ($graphqlFilterType) {
$value = $graphqlFilterType;
});
$args = $this->mergeFilterArgs($args, $parsed, $resourceMetadata, $key);
}
}
$args = $this->convertFilterArgsToTypes($args);
$args = $this->getFilterArgs($args, $resourceClass, $resourceMetadata, $depth);
}

if ($isStandardGraphqlType || $input) {
$resolve = null;
} elseif ($this->isCollection($type)) {
$resolverFactory = $this->collectionResolverFactory;
$resolve = $resolverFactory($className, $rootResource, $mutationName);
$resolve = $resolverFactory($resourceClass, $rootResource, $mutationName);
} else {
$resolve = $this->itemResolver;
}
Expand All @@ -287,6 +270,41 @@ private function getResourceFieldConfiguration(string $resourceClass, ResourceMe
return null;
}

private function getFilterArgs(array $args, ?string $resourceClass, ?ResourceMetadata $resourceMetadata, int $depth): array
{
if (null === $resourceMetadata || null === $resourceClass) {
return $args;
}

foreach ($resourceMetadata->getGraphqlAttribute('query', 'filters', [], true) as $filterId) {
if (null === $this->filterLocator || !$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, null, $depth);

if ('[]' === substr($key, -2)) {
$graphqlFilterType = GraphQLType::listOf($graphqlFilterType);
$key = substr($key, 0, -2).'_list';
}

parse_str($key, $parsed);
if (\array_key_exists($key, $parsed) && \is_array($parsed[$key])) {
$parsed = [$key => ''];
}
array_walk_recursive($parsed, function (&$value) use ($graphqlFilterType) {
$value = $graphqlFilterType;
});
$args = $this->mergeFilterArgs($args, $parsed, $resourceMetadata, $key);
}
}

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

private function mergeFilterArgs(array $args, array $parsed, ResourceMetadata $resourceMetadata = null, $original = ''): array
{
foreach ($parsed as $key => $value) {
Expand Down Expand Up @@ -508,15 +526,9 @@ private function getResourceObjectTypeFields(?string $resourceClass, ResourceMet
continue;
}

$rootResource = $resourceClass;
if (null !== $propertyMetadata->getSubresource()) {
$resourceClass = $propertyMetadata->getSubresource()->getResourceClass();
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
}
if ($fieldConfiguration = $this->getResourceFieldConfiguration($resourceClass, $resourceMetadata, $propertyMetadata->getDescription(), $propertyMetadata->getAttribute('deprecation_reason', ''), $propertyType, $rootResource, $input, $mutationName, $depth)) {
if ($fieldConfiguration = $this->getResourceFieldConfiguration($propertyMetadata->getDescription(), $propertyMetadata->getAttribute('deprecation_reason', ''), $propertyType, $resourceClass, $input, $mutationName, $depth)) {
$fields['id' === $property ? '_id' : $property] = $fieldConfiguration;
}
$resourceClass = $rootResource;
}
}

Expand Down
3 changes: 3 additions & 0 deletions tests/Fixtures/TestBundle/Document/DummyCarColor.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@

namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Document;

use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Filter\SearchFilter;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Symfony\Component\Serializer\Annotation as Serializer;
use Symfony\Component\Validator\Constraints as Assert;
Expand Down Expand Up @@ -43,6 +45,7 @@ class DummyCarColor
* @var string
*
* @ODM\Field(nullable=false)
* @ApiFilter(SearchFilter::class)
* @Assert\NotBlank
*
* @Serializer\Groups({"colors"})
Expand Down
3 changes: 3 additions & 0 deletions tests/Fixtures/TestBundle/Entity/DummyCarColor.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@

namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity;

use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation as Serializer;
use Symfony\Component\Validator\Constraints as Assert;
Expand Down Expand Up @@ -46,6 +48,7 @@ class DummyCarColor
* @var string
*
* @ORM\Column(nullable=false)
* @ApiFilter(SearchFilter::class)
* @Assert\NotBlank
*
* @Serializer\Groups({"colors"})
Expand Down
1 change: 1 addition & 0 deletions tests/GraphQl/Type/SchemaBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ private function createSchemaBuilder($propertyMetadataMockBuilder, bool $paginat
);
$resourceMetadataFactoryProphecy->create($resourceClassName)->willReturn($resourceMetadata);
$resourceMetadataFactoryProphecy->create('unknownResource')->willThrow(new ResourceClassNotFoundException());
$resourceMetadataFactoryProphecy->create('DateTime')->willThrow(new ResourceClassNotFoundException());

$propertyNames = [];
foreach (Type::$builtinTypes as $builtinType) {
Expand Down