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

fix(graphql): register query parameter arguments with filters #6726

Merged
merged 1 commit into from
Oct 15, 2024
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
129 changes: 69 additions & 60 deletions src/GraphQl/Type/FieldsBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -287,61 +287,6 @@ public function resolveResourceArgs(array $args, Operation $operation): array
$args[$id]['type'] = $this->typeConverter->resolveType($arg['type']);
}

/*
* This is @experimental, read the comment on the parameterToObjectType function as additional information.
*/
foreach ($operation->getParameters() ?? [] as $parameter) {
$key = $parameter->getKey();

if (str_contains($key, ':property')) {
if (!($filterId = $parameter->getFilter()) || !$this->filterLocator->has($filterId)) {
continue;
}

$filter = $this->filterLocator->get($filterId);
$parsedKey = explode('[:property]', $key);
$flattenFields = [];

if ($filter instanceof FilterInterface) {
foreach ($filter->getDescription($operation->getClass()) as $name => $value) {
$values = [];
parse_str($name, $values);
if (isset($values[$parsedKey[0]])) {
$values = $values[$parsedKey[0]];
}

$name = key($values);
$flattenFields[] = ['name' => $name, 'required' => $value['required'] ?? null, 'description' => $value['description'] ?? null, 'leafs' => $values[$name], 'type' => $value['type'] ?? 'string'];
}

$args[$parsedKey[0]] = $this->parameterToObjectType($flattenFields, $parsedKey[0]);
}

if ($filter instanceof OpenApiParameterFilterInterface) {
foreach ($filter->getOpenApiParameters($parameter) as $value) {
$values = [];
parse_str($value->getName(), $values);
if (isset($values[$parsedKey[0]])) {
$values = $values[$parsedKey[0]];
}

$name = key($values);
$flattenFields[] = ['name' => $name, 'required' => $value->getRequired(), 'description' => $value->getDescription(), 'leafs' => $values[$name], 'type' => $value->getSchema()['type'] ?? 'string'];
}

$args[$parsedKey[0]] = $this->parameterToObjectType($flattenFields, $parsedKey[0].$operation->getShortName().$operation->getName());
}

continue;
}

$args[$key] = ['type' => GraphQLType::string()];

if ($parameter->getRequired()) {
$args[$key]['type'] = GraphQLType::nonNull($args[$key]['type']);
}
}

return $args;
}

Expand Down Expand Up @@ -463,12 +408,15 @@ private function getResourceFieldConfiguration(?string $property, ?string $field

$args = [];

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

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

if ($isStandardGraphqlType || $input) {
Expand All @@ -491,6 +439,67 @@ private function getResourceFieldConfiguration(?string $property, ?string $field
return null;
}

/*
* This function is @experimental, read the comment on the parameterToObjectType function for additional information.
* @experimental
*/
private function getParameterArgs(Operation $operation, array $args = []): array
{
foreach ($operation->getParameters() ?? [] as $parameter) {
$key = $parameter->getKey();

if (!str_contains($key, ':property')) {
$args[$key] = ['type' => GraphQLType::string()];

if ($parameter->getRequired()) {
$args[$key]['type'] = GraphQLType::nonNull($args[$key]['type']);
}

continue;
}

if (!($filterId = $parameter->getFilter()) || !$this->filterLocator->has($filterId)) {
continue;
}

$filter = $this->filterLocator->get($filterId);
$parsedKey = explode('[:property]', $key);
$flattenFields = [];

if ($filter instanceof FilterInterface) {
foreach ($filter->getDescription($operation->getClass()) as $name => $value) {
$values = [];
parse_str($name, $values);
if (isset($values[$parsedKey[0]])) {
$values = $values[$parsedKey[0]];
}

$name = key($values);
$flattenFields[] = ['name' => $name, 'required' => $value['required'] ?? null, 'description' => $value['description'] ?? null, 'leafs' => $values[$name], 'type' => $value['type'] ?? 'string'];
}

$args[$parsedKey[0]] = $this->parameterToObjectType($flattenFields, $parsedKey[0]);
}

if ($filter instanceof OpenApiParameterFilterInterface) {
foreach ($filter->getOpenApiParameters($parameter) as $value) {
$values = [];
parse_str($value->getName(), $values);
if (isset($values[$parsedKey[0]])) {
$values = $values[$parsedKey[0]];
}

$name = key($values);
$flattenFields[] = ['name' => $name, 'required' => $value->getRequired(), 'description' => $value->getDescription(), 'leafs' => $values[$name], 'type' => $value->getSchema()['type'] ?? 'string'];
}

$args[$parsedKey[0]] = $this->parameterToObjectType($flattenFields, $parsedKey[0].$operation->getShortName().$operation->getName());
}
}

return $args;
}

private function getGraphQlPaginationArgs(Operation $queryOperation): array
{
$paginationType = $this->pagination->getGraphQlPaginationType($queryOperation);
Expand Down
19 changes: 19 additions & 0 deletions src/Laravel/Tests/GraphQlTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,23 @@ public function testGetBooks(): void
$this->assertArrayHasKey('data', $data);
$this->assertArrayNotHasKey('errors', $data);
}

public function testGetBooksWithPaginationAndOrder(): void
{
BookFactory::new()->has(AuthorFactory::new())->count(10)->create();
$response = $this->postJson('/api/graphql', ['query' => '{
books(first: 3, order: {name: "desc"}) {
edges {
node {
id, name, publicationDate, author { id, name }
}
}
}
}'], ['accept' => ['application/json']]);
$response->assertStatus(200);
$data = $response->json();
$this->assertArrayHasKey('data', $data);
$this->assertCount(3, $data['data']['books']['edges']);
$this->assertArrayNotHasKey('errors', $data);
}
}
11 changes: 11 additions & 0 deletions src/Laravel/workbench/app/Models/Book.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@

use ApiPlatform\Laravel\Eloquent\Filter\DateFilter;
use ApiPlatform\Laravel\Eloquent\Filter\EqualsFilter;
use ApiPlatform\Laravel\Eloquent\Filter\OrderFilter;
use ApiPlatform\Laravel\Eloquent\Filter\OrFilter;
use ApiPlatform\Laravel\Eloquent\Filter\PartialSearchFilter;
use ApiPlatform\Laravel\Eloquent\Filter\RangeFilter;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\GraphQl\Query;
use ApiPlatform\Metadata\GraphQl\QueryCollection;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
Expand All @@ -44,6 +47,14 @@
new Post(),
new Delete(),
new GetCollection(),
],
graphQlOperations: [
new Query(),
new QueryCollection(
parameters: [
new QueryParameter(key: 'order[:property]', filter: OrderFilter::class),
],
),
]
)]
#[QueryParameter(key: 'isbn', filter: PartialSearchFilter::class, constraints: 'min:2')]
Expand Down
Loading