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

[GraphQl] Better pagination support #2142

Merged
merged 8 commits into from
Feb 27, 2019
Merged

Conversation

Reduxx
Copy link
Contributor

@Reduxx Reduxx commented Aug 3, 2018

Q A
Bug fix? yes
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets #2139
License MIT
Doc PR api-platform/docs#561

This is a WIP Pull Request. It aims to make the pagination support closer to the graphql-relay-js implementation.

Bug Fixes:
pageInfo.endCursor is now the last item of the current page

New Features:
pageInfo.startCursor: new Type for the first item of the current page
queryArguments last and before: the counterpart for first and after

PHPUnit Tests: passing

Behat:

  • all tests: 5 not passing
  • GraphQl tests: passing (although there are no tests for startCursor, last and before at the moment)
    I haven't figured out why the other tests aren't running properly, I have to check this.

Any help with the tests are appreciated, this is the first time I've ever seen Behat and used prophecies in PHPUnit

I'll open a PR for the docs and finish this up according to the guidelines when I have the time :)

@Reduxx
Copy link
Contributor Author

Reduxx commented Aug 7, 2018

The phpstan errors seem to be unrelated to this PR, they are also thrown on the base master branch.
I couldn't figure out why it says phpunit-coverage failed, the tests are all passing and circleci says "0 failures" but it has an exit code of 1.

The new behavior now working as intended. PHPUnit and Behat tests are updated to reflect this.

The docs PR is almost ready, just figuring out the wording and spelling. And then I'll finalize this :)

@soyuka
Copy link
Member

soyuka commented Aug 7, 2018

rebasing should do !

@Reduxx
Copy link
Contributor Author

Reduxx commented Aug 8, 2018

I've rebased my commits, but unfortunately something doesn't seem right :-/

But at least I've included the docs PR api-platform/docs#561 :)

@Reduxx Reduxx changed the title [WIP] [GraphQl] Better pagination support [GraphQl] Better pagination support Aug 8, 2018
if (isset($collectionArgs[$resourceClass]['before'])) {
$before = \base64_decode($collectionArgs[$resourceClass]['before'], true);
$firstResult = (int) $before - $itemsPerPage;
$firstResult = (false === $before) ? 0 : (int) $firstResult;
Copy link
Member

Choose a reason for hiding this comment

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

The cast is not needed.

$before = \base64_decode($collectionArgs[$resourceClass]['before'], true);
$firstResult = (int) $before - $itemsPerPage;
$firstResult = (false === $before) ? 0 : (int) $firstResult;
if (false !== $before && 0 > $firstResult) {
Copy link
Member

Choose a reason for hiding this comment

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

I think you can remove the false !== $before part. Since $firstResult will be negative only if $before is not false.

Copy link
Member

Choose a reason for hiding this comment

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

Is this part tested?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is correct I'll change that.
It is tested in features/graphql/collection.feature starting on line 424

@@ -121,6 +122,19 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator
$firstResult = (int) $after;
$firstResult = false === $after ? $firstResult : ++$firstResult;
}
if (isset($collectionArgs[$resourceClass]['last'])) {
Copy link
Member

@alanpoulain alanpoulain Aug 9, 2018

Choose a reason for hiding this comment

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

You are doing an extra query inside a building of a query. Maybe there is a better way to do this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, that's kinda hacky. It's for the case that before isn't set, so you can get the last items in the correct order. I have to think about another way

Copy link
Contributor Author

@Reduxx Reduxx Aug 17, 2018

Choose a reason for hiding this comment

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

I didn't come up with an idea so far :-/ How should we handle this?

Copy link
Member

Choose a reason for hiding this comment

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

I think doing backwards pagination in SQL is tricky and you need to know the total rows the first time in order to do it.
The current implementation needs to be changed though because getArrayResult can be very big and I think it can eat all the memory. You need to use an SQL count.
The Doctrine Paginator is doing it anyway when you get the total items (see its method getCountQuery). You need to do something like this.

}

foreach ($collection as $index => $object) {
if ($index === $collectionLastIndex) {
Copy link
Member

Choose a reason for hiding this comment

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

Why is this needed? The $collectionLastIndex is always the endCursor doesn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You are completly right, it's not needed at all

];
}

if ($collection instanceof PaginatorInterface && ($totalItems = $collection->getTotalItems()) > 0) {
Copy link
Member

Choose a reason for hiding this comment

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

It should be moved inside the previous if.

Add support for graphql before, last, startcursor
@Reduxx
Copy link
Contributor Author

Reduxx commented Aug 22, 2018

No further changes, only updated branch to match base branch

@alanpoulain
Copy link
Member

@Reduxx Could you try to use an SQL count?

@alanpoulain
Copy link
Member

This PR should be good.
@api-platform/core-team, I have introduced an ArrayIterator in order to have pagination information when returning an array, please tell me what you think of it.

Copy link
Member

@soyuka soyuka left a comment

Choose a reason for hiding this comment

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

LGTM

The ___ field and the hardcoded limit0 disturb me but I guess they're fine?

@alanpoulain
Copy link
Member

@soyuka I have cherry-picked the commit (f91d3d8) for the limit 0 which uses constants.

}

if (isset($context['filters']['last']) && !isset($context['filters']['before'])) {
$context['count'] = (new DoctrineOrmPaginator($queryBuilder))->count();
Copy link
Member

Choose a reason for hiding this comment

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

Should I assume that partial pagination is not compatible with Graphql?

Copy link
Member

Choose a reason for hiding this comment

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

Yes. We could make it compatible later.

Copy link
Member

Choose a reason for hiding this comment

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

The code you annotated is only if you are doing backwards pagination though.

@alanpoulain
Copy link
Member

This PR misses the hasPreviousPage field. Please do not merge it yet.

@alanpoulain
Copy link
Member

hasPreviousPage added.

@alanpoulain alanpoulain merged commit 9eb6662 into api-platform:master Feb 27, 2019
@alanpoulain
Copy link
Member

Thank @Reduxx 🙂

@luca-simonetti
Copy link

any plans to have this released soon?

@rajasimon
Copy link

Need this now. My after cursor resulting in empty result after five iteration. Why this not in production?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants