From 2cff2e7096638c9e71c9d86ea22d463cc4fc42ec Mon Sep 17 00:00:00 2001 From: OwlyCode Date: Tue, 22 Nov 2016 17:32:05 +0100 Subject: [PATCH] Added paginator helper to convert first/after and before/last to offset/limit --- Relay/Connection/Paginator.php | 90 ++++++++++++++++++++++ Tests/Relay/Connection/PaginatorTest.php | 97 ++++++++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 Relay/Connection/Paginator.php create mode 100644 Tests/Relay/Connection/PaginatorTest.php diff --git a/Relay/Connection/Paginator.php b/Relay/Connection/Paginator.php new file mode 100644 index 000000000..d2f38da99 --- /dev/null +++ b/Relay/Connection/Paginator.php @@ -0,0 +1,90 @@ +fetcher = $fetcher; + } + + /** + * @param Argument|array $args + * @param int|callable $total + * + * @return Connection + */ + public function backward($args, $total) + { + $args = $this->protectArgs($args); + $limit = $args['last']; + $offset = max(0, ConnectionBuilder::getOffsetWithDefault($args['before'], $total) - $limit); + + $entities = call_user_func($this->fetcher, $offset, $limit); + + return ConnectionBuilder::connectionFromArraySlice($entities, $args, [ + 'sliceStart' => $offset, + 'arrayLength' => $total, + ]); + } + + /** + * @param Argument|array $args + * + * @return Connection + */ + public function forward($args) + { + $args = $this->protectArgs($args); + $limit = $args['first']; + $offset = ConnectionBuilder::getOffsetWithDefault($args['after'], 0); + + // The extra fetched element is here to determine if there is a next page. + $entities = call_user_func($this->fetcher, $offset, $limit + 1); + + return ConnectionBuilder::connectionFromArraySlice($entities, $args, [ + 'sliceStart' => $offset, + 'arrayLength' => $offset + count($entities), + ]); + } + + /** + * @param Argument|array $args + * @param int|callable $total + * + * @return Connection + */ + public function auto($args, $total) + { + $args = $this->protectArgs($args); + + if ($args['last']) { + return $this->backward($args, is_callable($total) ? call_user_func($total) : $total); + } else { + return $this->forward($args); + } + } + + /** + * @param Argument|array $args + * + * @return Argument + */ + private function protectArgs($args) + { + return $args instanceof Argument ? $args : new Argument($args); + } +} diff --git a/Tests/Relay/Connection/PaginatorTest.php b/Tests/Relay/Connection/PaginatorTest.php new file mode 100644 index 000000000..c147b50f1 --- /dev/null +++ b/Tests/Relay/Connection/PaginatorTest.php @@ -0,0 +1,97 @@ +assertSame(0, $offset); + $this->assertSame(6, $limit); // Includes the extra element to check if next page is available + + return array_fill(0, 6, 'item'); + }); + + $this->assertCount(5, $paginator->forward(new Argument(['first' => 5]))->edges); + } + + public function testForwardAfter() + { + $paginator = new Paginator(function ($offset, $limit) { + $this->assertSame(5, $offset); + $this->assertSame(6, $limit); // Includes the extra element to check if next page is available + + return array_fill(0, 6, 'item'); + }); + + $this->assertCount(5, $paginator->forward(new Argument(['first' => 5, 'after' => base64_encode('arrayconnection:5') ]))->edges); + } + + public function testBackward() + { + $paginator = new Paginator(function ($offset, $limit) { + $this->assertSame(5, $offset); + $this->assertSame(5, $limit); + + return array_fill(0, 5, 'item'); + }); + + $this->assertCount(5, $paginator->backward(new Argument(['last' => 5]), 10)->edges); + } + + public function testBackwardBefore() + { + $paginator = new Paginator(function ($offset, $limit) { + $this->assertSame(0, $offset); + $this->assertSame(5, $limit); + + return array_fill(0, 5, 'item'); + }); + + $this->assertCount(5, $paginator->backward(new Argument(['last' => 5, 'before' => base64_encode('arrayconnection:5')]), 10)->edges); + } + + public function testAuto() + { + // Backward + $paginator = new Paginator(function ($offset, $limit) { + $this->assertSame(5, $offset); + $this->assertSame(5, $limit); + + return array_fill(0, 5, 'item'); + }); + + $this->assertCount(5, $paginator->auto(new Argument(['last' => 5]), 10)->edges); + + // Forward + $paginator = new Paginator(function ($offset, $limit) { + $this->assertSame(0, $offset); + $this->assertSame(6, $limit); // Includes the extra element to check if next page is available + + return array_fill(0, 5, 'item'); + }); + + $this->assertCount(5, $paginator->auto(new Argument(['first' => 5]), 10)->edges); + + // Backward + callable + $paginator = new Paginator(function ($offset, $limit) { + $this->assertSame(5, $offset); + $this->assertSame(5, $limit); + + return array_fill(0, 5, 'item'); + }); + + $countCalled = false; + $result = $paginator->auto(new Argument(['last' => 5]), function () use (&$countCalled) { + $countCalled = true; + return 10; + }); + + $this->assertTrue($countCalled); + $this->assertCount(5, $result->edges); + } +}