-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ef0bac6
commit 3141cca
Showing
9 changed files
with
761 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
--- | ||
title: Pager | ||
description: PHP Pager | ||
--- | ||
|
||
Simple yet powerful pagination | ||
|
||
## Installation | ||
|
||
```shell | ||
composer require sonsofphp/pager | ||
``` | ||
|
||
## Usage | ||
|
||
Basic Usage | ||
|
||
```php | ||
<?php | ||
|
||
use SonsOfPHP\Component\Pager\Adapter\ArrayAdapter; | ||
use SonsOfPHP\Component\Pager\Pager; | ||
|
||
$pager = new Pager(new ArrayAdapter($results)); | ||
|
||
foreach ($pager as $result) { | ||
// ... | ||
} | ||
``` | ||
|
||
Advance Usage | ||
|
||
```php | ||
<?php | ||
|
||
use SonsOfPHP\Component\Pager\Adapter\ArrayAdapter; | ||
use SonsOfPHP\Component\Pager\Pager; | ||
|
||
// These are the default option values | ||
$pager = new Pager(new ArrayAdapter($results), [ | ||
'current_page' => 1, | ||
'max_per_page' => 10, | ||
]); | ||
|
||
// You can also set current page and max per page | ||
$pager->setCurrentPage(1); | ||
$pager->setMaxPerPage(10); | ||
``` | ||
|
||
## Adapters | ||
|
||
### ArrayAdapter | ||
|
||
```php | ||
<?php | ||
|
||
use SonsOfPHP\Component\Pager\Adapter\ArrayAdapter; | ||
|
||
$adapter = new ArrayAdapter($results); | ||
``` | ||
|
||
### CallableAdabter | ||
|
||
Will take any `callable` arguments. | ||
|
||
```php | ||
<?php | ||
|
||
use SonsOfPHP\Component\Pager\Adapter\CallableAdapter; | ||
|
||
$adapter = new CallableAdapter( | ||
count: function (): int { | ||
// ... | ||
}, | ||
slice: function (int $offset, ?int $length): iterable { | ||
// ... | ||
}, | ||
); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace SonsOfPHP\Component\Pager\Adapter; | ||
|
||
use SonsOfPHP\Contract\Pager\AdapterInterface; | ||
|
||
/** | ||
* @author Joshua Estes <[email protected]> | ||
*/ | ||
class ArrayAdapter implements AdapterInterface | ||
{ | ||
public function __construct( | ||
private array $array, | ||
) {} | ||
|
||
public function count(): int | ||
{ | ||
return count($this->array); | ||
} | ||
|
||
public function getSlice(int $offset, ?int $length): iterable | ||
{ | ||
return array_slice($this->array, $offset, $length); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace SonsOfPHP\Component\Pager\Adapter; | ||
|
||
use SonsOfPHP\Contract\Pager\AdapterInterface; | ||
|
||
/** | ||
* @author Joshua Estes <[email protected]> | ||
*/ | ||
class CallableAdapter implements AdapterInterface | ||
{ | ||
private $count; | ||
private $slice; | ||
|
||
public function __construct(callable $count, callable $slice) | ||
{ | ||
$this->count = $count; | ||
$this->slice = $slice; | ||
} | ||
|
||
public function count(): int | ||
{ | ||
return call_user_func($this->count); | ||
} | ||
|
||
public function getSlice(int $offset, ?int $length): iterable | ||
{ | ||
return call_user_func($this->slice, $offset, $length); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace SonsOfPHP\Component\Pager; | ||
|
||
use SonsOfPHP\Contract\Pager\PagerInterface; | ||
use SonsOfPHP\Contract\Pager\AdapterInterface; | ||
|
||
/** | ||
* @author Joshua Estes <[email protected]> | ||
*/ | ||
class Pager implements PagerInterface | ||
{ | ||
private ?int $count = null; | ||
private int $currentPage = 1; | ||
private ?int $maxPerPage = 10; | ||
private ?iterable $results = null; | ||
|
||
/** | ||
* Available Options | ||
* - current_page (default: 1) | ||
* - max_per_page (default: 10) | ||
*/ | ||
public function __construct( | ||
private AdapterInterface $adapter, | ||
array $options = [], | ||
) { | ||
if (array_key_exists('current_page', $options)) { | ||
$this->setCurrentPage($options['current_page']); | ||
} | ||
|
||
if (array_key_exists('max_per_page', $options)) { | ||
$this->setMaxPerPage($options['max_per_page']); | ||
} | ||
} | ||
|
||
public function getCurrentPageResults(): iterable | ||
{ | ||
if (null === $this->results) { | ||
$offset = 0; | ||
if (null !== $this->getMaxPerPage()) { | ||
$offset = ($this->currentPage - 1) * $this->getMaxPerPage(); | ||
} | ||
|
||
$this->results = $this->adapter->getSlice($offset, $this->getMaxPerPage()); | ||
} | ||
|
||
return $this->results; | ||
} | ||
|
||
public function getTotalResults(): int | ||
{ | ||
if (null === $this->count) { | ||
$this->count = $this->adapter->count(); | ||
} | ||
|
||
return $this->count; | ||
} | ||
|
||
public function getTotalPages(): int | ||
{ | ||
if (null === $this->getMaxPerPage() || 0 === $this->getTotalResults()) { | ||
return 1; | ||
} | ||
|
||
return (int) ceil($this->getTotalResults() / $this->getMaxPerPage()); | ||
} | ||
|
||
public function haveToPaginate(): bool | ||
{ | ||
if (null === $this->getMaxPerPage()) { | ||
return false; | ||
} | ||
|
||
return $this->getTotalResults() > $this->getMaxPerPage(); | ||
} | ||
|
||
public function hasPreviousPage(): bool | ||
{ | ||
return $this->getCurrentPage() > 1; | ||
} | ||
|
||
public function getPreviousPage(): ?int | ||
{ | ||
if ($this->hasPreviousPage()) { | ||
return $this->getCurrentPage() - 1; | ||
} | ||
|
||
return null; | ||
} | ||
|
||
public function hasNextPage(): bool | ||
{ | ||
return $this->getCurrentPage() < $this->getTotalPages(); | ||
} | ||
|
||
public function getNextPage(): ?int | ||
{ | ||
if ($this->hasNextPage()) { | ||
return $this->getCurrentPage() + 1; | ||
} | ||
|
||
return null; | ||
} | ||
|
||
public function getCurrentPage(): int | ||
{ | ||
return $this->currentPage; | ||
} | ||
|
||
public function setCurrentPage(int $page): void | ||
{ | ||
if (1 > $page) { | ||
throw new \InvalidArgumentException('$page is invalid'); | ||
} | ||
|
||
$this->currentPage = $page; | ||
$this->results = null; | ||
} | ||
|
||
public function getMaxPerPage(): ?int | ||
{ | ||
return $this->maxPerPage; | ||
} | ||
|
||
public function setMaxPerPage(?int $maxPerPage): void | ||
{ | ||
if (is_int($maxPerPage) && 1 > $maxPerPage) { | ||
throw new \InvalidArgumentException('maxPerPage is invalid'); | ||
} | ||
|
||
$this->maxPerPage = $maxPerPage; | ||
$this->results = null; | ||
} | ||
|
||
public function count(): int | ||
{ | ||
return $this->getTotalResults(); | ||
} | ||
|
||
public function getIterator(): \Traversable | ||
{ | ||
$results = $this->getCurrentPageResults(); | ||
|
||
if ($results instanceof \Iterator) { | ||
return $results; | ||
} | ||
|
||
if ($results instanceof \IteratorAggregate) { | ||
return $results->getIterator(); | ||
} | ||
|
||
return new \ArrayIterator($results); | ||
} | ||
|
||
public function jsonSerialize(): array | ||
{ | ||
$results = $this->getCurrentPageResults(); | ||
|
||
if ($results instanceof \Traversable) { | ||
return iterator_to_array($results); | ||
} | ||
|
||
return $results; | ||
} | ||
} |
53 changes: 53 additions & 0 deletions
53
src/SonsOfPHP/Component/Pager/Tests/Adapter/ArrayAdapterTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace SonsOfPHP\Component\Pager\Tests\Adapter; | ||
|
||
use PHPUnit\Framework\TestCase; | ||
use SonsOfPHP\Component\Pager\Adapter\ArrayAdapter; | ||
use SonsOfPHP\Contract\Pager\AdapterInterface; | ||
|
||
/** | ||
* @coversDefaultClass \SonsOfPHP\Component\Pager\Adapter\ArrayAdapter | ||
* | ||
* @uses \SonsOfPHP\Component\Pager\Adapter\ArrayAdapter | ||
*/ | ||
final class ArrayAdapterTest extends TestCase | ||
{ | ||
/** | ||
* @covers ::__construct | ||
*/ | ||
public function testItHasTheCorrectInterface(): void | ||
{ | ||
$adapter = new ArrayAdapter([]); | ||
$this->assertInstanceOf(AdapterInterface::class, $adapter); | ||
} | ||
|
||
/** | ||
* @covers ::count | ||
*/ | ||
public function testCount(): void | ||
{ | ||
$adapter = new ArrayAdapter([]); | ||
|
||
$this->assertCount(0, $adapter); | ||
} | ||
|
||
/** | ||
* @covers ::getSlice | ||
*/ | ||
public function testGetSlice(): void | ||
{ | ||
$adapter = new ArrayAdapter([ | ||
'one', | ||
'two', | ||
'three', | ||
]); | ||
|
||
$this->assertSame('one', $adapter->getSlice(0, 1)[0]); | ||
$this->assertSame('two', $adapter->getSlice(1, 1)[0]); | ||
$this->assertSame('three', $adapter->getSlice(2, 1)[0]); | ||
$this->assertCount(3, $adapter->getSlice(0, null)); | ||
} | ||
} |
Oops, something went wrong.