Skip to content

support simple pagination #715

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

Merged
merged 12 commits into from
Apr 5, 2021
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1549,6 +1549,37 @@ Query `posts(limit:10,page:1){data{id},total,per_page}` might return
Note that you need to add in the extra 'data' object when you request paginated resources as the returned data gives you
the paginated resources in a data object at the same level as the returned pagination metadata.

[Simple Pagination](https://laravel.com/docs/pagination#simple-pagination) will be used, if a query or mutation returns a `SimplePaginationType`.

```php
namespace App\GraphQL\Queries;

use Closure;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Query;

class PostsQuery extends Query
{
public function type(): Type
{
return Type::nonNull(GraphQL::simplePaginate('posts'));
}

// ...

public function resolve($root, $args, $context, ResolveInfo $info, Closure $getSelectFields)
{
$fields = $getSelectFields();

return Post::with($fields->getRelations())
->select($fields->getSelect())
->simplePaginate($args['limit'], ['*'], 'page', $args['page']);
}
}
```

### Batching

You can send multiple queries (or mutations) at once by grouping them together. Therefore, instead of creating two HTTP requests:
Expand Down
6 changes: 6 additions & 0 deletions config/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,12 @@
*/
'pagination_type' => \Rebing\GraphQL\Support\PaginationType::class,

/*
* You can define your own simple pagination type.
* Reference \Rebing\GraphQL\Support\SimplePaginationType::class
*/
'simple_pagination_type' => \Rebing\GraphQL\Support\SimplePaginationType::class,

/*
* Config for GraphiQL (see (https://github.com/graphql/graphiql).
*/
Expand Down
13 changes: 13 additions & 0 deletions src/GraphQL.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use Rebing\GraphQL\Exception\TypeNotFound;
use Rebing\GraphQL\Support\Contracts\TypeConvertible;
use Rebing\GraphQL\Support\PaginationType;
use Rebing\GraphQL\Support\SimplePaginationType;

class GraphQL
{
Expand Down Expand Up @@ -386,6 +387,18 @@ public function paginate(string $typeName, string $customName = null): Type
return $this->typesInstances[$name];
}

public function simplePaginate(string $typeName, string $customName = null): Type
{
$name = $customName ?: $typeName.'SimplePagination';

if (! isset($this->typesInstances[$name])) {
$paginationType = config('graphql.simple_pagination_type', SimplePaginationType::class);
$this->wrapType($typeName, $name, $paginationType);
}

return $this->typesInstances[$name];
}

/**
* To add customs result to the query or mutations.
*
Expand Down
1 change: 1 addition & 0 deletions src/Support/Facades/GraphQL.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
* @method static ExecutionResult queryAndReturnResult(string $query, ?array $params = [], array $opts = [])
* @method static Type type(string $name, bool $fresh = false)
* @method static Type paginate(string $typeName, string $customName = null)
* @method static Type simplePaginate(string $typeName, string $customName = null)
* @method static array<string,object|string> getTypes()
* @method static Schema schema(Schema|array|string $schema = null)
* @method static array getSchemas()
Expand Down
3 changes: 2 additions & 1 deletion src/Support/SelectFields.php
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,8 @@ protected static function handleFields(
$queryable = static::isQueryable($fieldObject->config);

// Pagination
if (is_a($parentType, config('graphql.pagination_type', PaginationType::class))) {
if (is_a($parentType, config('graphql.pagination_type', PaginationType::class))
|| is_a($parentType, config('graphql.simple_pagination_type', SimplePaginationType::class))) {
/* @var GraphqlType $fieldType */
$fieldType = $fieldObject->config['type'];
static::handleFields(
Expand Down
89 changes: 89 additions & 0 deletions src/Support/SimplePaginationType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php

declare(strict_types=1);

namespace Rebing\GraphQL\Support;

use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type as GraphQLType;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Collection;
use Rebing\GraphQL\Support\Facades\GraphQL;

class SimplePaginationType extends ObjectType
{
public function __construct(string $typeName, string $customName = null)
{
$name = $customName ?: $typeName.'SimplePagination';

$config = [
'name' => $name,
'fields' => $this->getPaginationFields($typeName),
];

$underlyingType = GraphQL::type($typeName);
if (isset($underlyingType->config['model'])) {
$config['model'] = $underlyingType->config['model'];
}

parent::__construct($config);
}

/**
* @param string $typeName
*
* @return array<string, array<string,mixed>>
*/
protected function getPaginationFields(string $typeName): array
{
return [
'data' => [
'type' => GraphQLType::listOf(GraphQL::type($typeName)),
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think this field can ever return null:

Suggested change
'type' => GraphQLType::listOf(GraphQL::type($typeName)),
'type' => Type::nonNull(GraphQLType::listOf(GraphQL::type($typeName))),

Copy link
Contributor Author

@lamtranb lamtranb Apr 4, 2021

Choose a reason for hiding this comment

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

Type::nonNull seems not work with ListOf. ListOf can return an empty list. I will add a new test case for this. Or you mean this?
GraphQLType::listOf(Type::nonNull(GraphQL::type($typeName)))

Copy link
Collaborator

Choose a reason for hiding this comment

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

Good question!

Type::nonNull seems not work with ListOf

Oh, but it should!

In this case I meant that the value returned by resolve cannot be null, ever, because even the closure has the typehint Collection.

This means there will always be a collection returned, never null, so wrapping the existing GraphQLType::listOf(GraphQL::type($typeName)), into non-null like GraphQLType::nonNull(GraphQLType::listOf(GraphQL::type($typeName))), should be possible!

(sorry I see in my feedback I just write Type::nonNull which is usually the alias used, but in this class it's GraphQLType, so maybe that's why it may look confusing).

ListOf can return an empty list

Exactly! But "empty list" !== "null"!

Or you mean this? GraphQLType::listOf(Type::nonNull(GraphQL::type($typeName)))

I would love to (always good to avoid null values, if possible), but I think a collection containing null values is technically allowed (however unlikely), so therefore sadly no, didn't meant this one.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @mfn
Thanks for taking your time to review my code.

I updated the code into this:
GraphQLType::nonNull(GraphQLType::listOf(GraphQL::type($typeName)))
The tests failed.
I checked in the file tests/Database/SelectFields/PrimaryKeyTests/PrimaryKeySimplePaginationQuery.php
/** @var SelectFields $selectFields */ $selectFields = $getSelectFields(); $selectFields->getSelect()
only return posts.id. If I remove GraphQLType::nonNull, it will return posts.id, posts.title. I think there is a bug in src/Support/SelectFields.php or miss check somewhere. Can you help me? I'm not familiar with this. Thanks very much.

Copy link
Collaborator

Choose a reason for hiding this comment

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

TBH I've no idea.

I don't consider it a showstopper and I also didn't write the existing PaginationType which works exactly the same way, so I actually do feel bad for side-tracking this 😞

'description' => 'List of items on the current page',
'resolve' => function (Paginator $data): Collection {
return $data->getCollection();
},
],
'per_page' => [
'type' => GraphQLType::nonNull(GraphQLType::int()),
'description' => 'Number of items returned per page',
'resolve' => function (Paginator $data): int {
return $data->perPage();
},
'selectable' => false,
],
'current_page' => [
'type' => GraphQLType::nonNull(GraphQLType::int()),
'description' => 'Current page of the cursor',
'resolve' => function (Paginator $data): int {
return $data->currentPage();
},
'selectable' => false,
],
'from' => [
'type' => GraphQLType::int(),
'description' => 'Number of the first item returned',
'resolve' => function (Paginator $data): ?int {
return $data->firstItem();
},
'selectable' => false,
],
'to' => [
'type' => GraphQLType::int(),
'description' => 'Number of the last item returned',
'resolve' => function (Paginator $data): ?int {
return $data->lastItem();
},
'selectable' => false,
],
'has_more_pages' => [
'type' => GraphQLType::nonNull(GraphQLType::boolean()),
'description' => 'Determines if cursor has more pages after the current page',
'resolve' => function (Paginator $data): bool {
return $data->hasMorePages();
},
'selectable' => false,
],
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

declare(strict_types=1);

namespace Rebing\GraphQL\Tests\Database\SelectFields\PrimaryKeyTests;

use Closure;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Illuminate\Contracts\Pagination\Paginator;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Query;
use Rebing\GraphQL\Support\SelectFields;
use Rebing\GraphQL\Tests\Support\Models\Post;

class PrimaryKeySimplePaginationQuery extends Query
{
/**
* @var array<string, string>
*/
protected $attributes = [
'name' => 'primaryKeySimplePaginationQuery',
];

/**
* @return Type
*/
public function type(): Type
{
return GraphQL::simplePaginate('Post');
}

/**
* @param mixed $root
* @param mixed $args
* @param mixed $ctx
* @param ResolveInfo $info
* @param Closure $getSelectFields
*
* @return Paginator
*/
public function resolve($root, $args, $ctx, ResolveInfo $info, Closure $getSelectFields)
{
/** @var SelectFields $selectFields */
$selectFields = $getSelectFields();

return Post
::with($selectFields->getRelations())
->select($selectFields->getSelect())
->simplePaginate(1);
}
}
Loading