Skip to content

Commit

Permalink
updates
Browse files Browse the repository at this point in the history
  • Loading branch information
JoshuaEstes committed Nov 15, 2023
1 parent ef0bac6 commit 3141cca
Show file tree
Hide file tree
Showing 9 changed files with 761 additions and 0 deletions.
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ test-cqrs: phpunit
test-logger: PHPUNIT_TESTSUITE=logger
test-logger: phpunit

test-pager: PHPUNIT_TESTSUITE=pager
test-pager: phpunit

phpunit:
XDEBUG_MODE=$(XDEBUG_MODE) \
$(PHP) \
Expand Down Expand Up @@ -120,6 +123,9 @@ coverage-logger: coverage
coverage-money:
XDEBUG_MODE=coverage $(PHP) -dxdebug.mode=coverage $(PHPUNIT) --testsuite money --coverage-html $(COVERAGE_DIR)

coverage-pager: PHPUNIT_TESTSUITE=pager
coverage-pager: coverage

coverage-version:
XDEBUG_MODE=coverage $(PHP) -dxdebug.mode=coverage $(PHPUNIT) --testsuite version --coverage-html $(COVERAGE_DIR)

Expand Down
79 changes: 79 additions & 0 deletions docs/components/pager/index.md
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 {
// ...
},
);
```
4 changes: 4 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@
<directory>src/SonsOfPHP/Component/Money/Tests</directory>
</testsuite>

<testsuite name="pager">
<directory>src/SonsOfPHP/Component/Pager/Tests</directory>
</testsuite>

<testsuite name="version">
<directory>src/SonsOfPHP/Component/Version/Tests</directory>
</testsuite>
Expand Down
27 changes: 27 additions & 0 deletions src/SonsOfPHP/Component/Pager/Adapter/ArrayAdapter.php
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);
}
}
32 changes: 32 additions & 0 deletions src/SonsOfPHP/Component/Pager/Adapter/CallableAdapter.php
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);
}
}
167 changes: 167 additions & 0 deletions src/SonsOfPHP/Component/Pager/Pager.php
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 src/SonsOfPHP/Component/Pager/Tests/Adapter/ArrayAdapterTest.php
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));
}
}
Loading

0 comments on commit 3141cca

Please sign in to comment.