diff --git a/docs/helpers/relay-paginator.md b/docs/helpers/relay-paginator.md index 4934dd890..9b02a7998 100644 --- a/docs/helpers/relay-paginator.md +++ b/docs/helpers/relay-paginator.md @@ -244,6 +244,26 @@ class Greetings implements QueryInterface } ```` +#### Total count caching + +You may want to cache your total count for paginator result + +````php +auto( + $args, + new TotalCountCache ( + fn () => $backend->countAll(), + ), +); +```` + + #### Promise handling Paginator also supports promises if you [use that feature](https://github.com/webonyx/graphql-php/pull/67) diff --git a/src/Relay/Connection/Paginator.php b/src/Relay/Connection/Paginator.php index d575b3ae9..f19bcbb21 100644 --- a/src/Relay/Connection/Paginator.php +++ b/src/Relay/Connection/Paginator.php @@ -20,7 +20,6 @@ class Paginator public const MODE_PROMISE = true; private bool $promise; - private int $totalCount; private ConnectionBuilder $connectionBuilder; /** @var callable */ @@ -127,16 +126,10 @@ private function handleEntities($entities, callable $callback) /** * @param int|callable $total * - * @return int|mixed + * @return int */ - private function computeTotalCount($total, array $callableArgs = []) + private function computeTotalCount($total, array $callableArgs = []): int { - if (isset($this->totalCount)) { - return $this->totalCount; - } - - $this->totalCount = is_callable($total) ? call_user_func_array($total, $callableArgs) : $total; - - return $this->totalCount; + return is_callable($total) ? call_user_func_array($total, $callableArgs) : $total; } } diff --git a/src/Relay/Connection/TotalCountCache.php b/src/Relay/Connection/TotalCountCache.php new file mode 100644 index 000000000..71d48ca9d --- /dev/null +++ b/src/Relay/Connection/TotalCountCache.php @@ -0,0 +1,40 @@ +total = $total; + } + + public function __invoke(array $callableArgs = []): int + { + if (isset($this->totalCount)) { + return $this->totalCount; + } + + $this->totalCount = is_callable($this->total) ? call_user_func_array($this->total, $callableArgs) : $this->total; + + return $this->totalCount; + } + + public function reset(): void + { + unset($this->totalCount); + } +} diff --git a/tests/Relay/Connection/PaginatorTest.php b/tests/Relay/Connection/PaginatorTest.php index 64f5f65d4..fb753637f 100644 --- a/tests/Relay/Connection/PaginatorTest.php +++ b/tests/Relay/Connection/PaginatorTest.php @@ -9,6 +9,7 @@ use Overblog\GraphQLBundle\Relay\Connection\ConnectionInterface; use Overblog\GraphQLBundle\Relay\Connection\Output\Connection; use Overblog\GraphQLBundle\Relay\Connection\Paginator; +use Overblog\GraphQLBundle\Relay\Connection\TotalCountCache; use PHPUnit\Framework\TestCase; use function array_slice; use function base64_encode; @@ -259,6 +260,30 @@ public function testAutoBackwardWithCallable(): void $this->assertTrue($result->getPageInfo()->getHasPreviousPage()); } + public function testAutoBackwardWithCachedCallable(): void + { + $paginator = new Paginator(function ($offset, $limit) { + $this->assertSame(1, $offset); + $this->assertSame(4, $limit); + + return $this->getData($offset); + }); + + $countCalled = false; + + /** @var Connection $result */ + $result = $paginator->auto(new Argument(['last' => 4]), new TotalCountCache(function () use (&$countCalled) { + $countCalled = true; + + return 5; + })); + + $this->assertTrue($countCalled); + $this->assertCount(4, $result->getEdges()); + $this->assertSameEdgeNodeValue(['B', 'C', 'D', 'E'], $result); + $this->assertTrue($result->getPageInfo()->getHasPreviousPage()); + } + public function testTotalCallableWithArguments(): void { $paginatorBackend = new PaginatorBackend(); diff --git a/tests/Relay/Connection/TotalCountCacheTest.php b/tests/Relay/Connection/TotalCountCacheTest.php new file mode 100644 index 000000000..c0308132f --- /dev/null +++ b/tests/Relay/Connection/TotalCountCacheTest.php @@ -0,0 +1,47 @@ +createMock(TotalCountCache::class); + $total + ->expects($this->once()) + ->method('__invoke') + ->willReturn(7); + + $cache = new TotalCountCache($total); + + $this->assertEquals(7, $cache()); + $this->assertEquals(7, $cache()); + } + + public function testCacheInt(): void + { + $cache = new TotalCountCache(12); + + $this->assertEquals(12, $cache()); + } + + public function testReset(): void + { + $total = $this->createMock(TotalCountCache::class); + $total + ->expects($this->exactly(2)) + ->method('__invoke') + ->willReturn(7); + + $cache = new TotalCountCache($total); + + $this->assertEquals(7, $cache()); + $cache->reset(); + $this->assertEquals(7, $cache()); + } +}