-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added cursor paginator for
hyperf/database
. (#6809)
- Loading branch information
Showing
9 changed files
with
1,250 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
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,93 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
/** | ||
* This file is part of Hyperf. | ||
* | ||
* @link https://www.hyperf.io | ||
* @document https://hyperf.wiki | ||
* @contact [email protected] | ||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE | ||
*/ | ||
|
||
namespace Hyperf\Paginator\Contract; | ||
|
||
use Hyperf\Paginator\Cursor; | ||
|
||
interface CursorPaginator | ||
{ | ||
/** | ||
* Get the URL for a given cursor. | ||
*/ | ||
public function url(?Cursor $cursor): string; | ||
|
||
/** | ||
* Add a set of query string values to the paginator. | ||
*/ | ||
public function appends(null|array|string $key, ?string $value = null): static; | ||
|
||
/** | ||
* Get / set the URL fragment to be appended to URLs. | ||
*/ | ||
public function fragment(?string $fragment = null): null|static|string; | ||
|
||
/** | ||
* Add all current query string values to the paginator. | ||
*/ | ||
public function withQueryString(): static; | ||
|
||
/** | ||
* Get the URL for the previous page, or null. | ||
*/ | ||
public function previousPageUrl(): ?string; | ||
|
||
/** | ||
* The URL for the next page, or null. | ||
*/ | ||
public function nextPageUrl(): ?string; | ||
|
||
/** | ||
* Get all of the items being paginated. | ||
*/ | ||
public function items(): array; | ||
|
||
/** | ||
* Get the "cursor" of the previous set of items. | ||
*/ | ||
public function previousCursor(): ?Cursor; | ||
|
||
/** | ||
* Get the "cursor" of the next set of items. | ||
*/ | ||
public function nextCursor(): ?Cursor; | ||
|
||
/** | ||
* Determine how many items are being shown per page. | ||
*/ | ||
public function perPage(): int; | ||
|
||
/** | ||
* Get the current cursor being paginated. | ||
*/ | ||
public function cursor(): ?Cursor; | ||
|
||
/** | ||
* Determine if there are enough items to split into multiple pages. | ||
*/ | ||
public function hasPages(): bool; | ||
|
||
/** | ||
* Get the base path for paginator generated URLs. | ||
*/ | ||
public function path(): ?string; | ||
|
||
/** | ||
* Determine if the list of items is empty or not. | ||
*/ | ||
public function isEmpty(): bool; | ||
|
||
/** | ||
* Determine if the list of items is not empty. | ||
*/ | ||
public function isNotEmpty(): bool; | ||
} |
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,110 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
/** | ||
* This file is part of Hyperf. | ||
* | ||
* @link https://www.hyperf.io | ||
* @document https://hyperf.wiki | ||
* @contact [email protected] | ||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE | ||
*/ | ||
|
||
namespace Hyperf\Paginator; | ||
|
||
use Hyperf\Contract\Arrayable; | ||
use UnexpectedValueException; | ||
|
||
use function Hyperf\Collection\collect; | ||
|
||
class Cursor implements Arrayable | ||
{ | ||
/** | ||
* Create a new cursor instance. | ||
* @param array $parameters the parameters associated with the cursor | ||
* @param bool $pointsToNextItems determine whether the cursor points to the next or previous set of items | ||
*/ | ||
public function __construct( | ||
protected array $parameters, | ||
protected bool $pointsToNextItems = true | ||
) { | ||
} | ||
|
||
/** | ||
* Get the given parameter from the cursor. | ||
*/ | ||
public function parameter(string $parameterName): ?string | ||
{ | ||
if (! array_key_exists($parameterName, $this->parameters)) { | ||
throw new UnexpectedValueException("Unable to find parameter [{$parameterName}] in pagination item."); | ||
} | ||
|
||
return (string) $this->parameters[$parameterName]; | ||
} | ||
|
||
/** | ||
* Get the given parameters from the cursor. | ||
*/ | ||
public function parameters(array $parameterNames): array | ||
{ | ||
return collect($parameterNames)->map(function ($parameterName) { | ||
return $this->parameter($parameterName); | ||
})->toArray(); | ||
} | ||
|
||
/** | ||
* Determine whether the cursor points to the next set of items. | ||
*/ | ||
public function pointsToNextItems(): bool | ||
{ | ||
return $this->pointsToNextItems; | ||
} | ||
|
||
/** | ||
* Determine whether the cursor points to the previous set of items. | ||
*/ | ||
public function pointsToPreviousItems(): bool | ||
{ | ||
return ! $this->pointsToNextItems; | ||
} | ||
|
||
/** | ||
* Get the array representation of the cursor. | ||
*/ | ||
public function toArray(): array | ||
{ | ||
return array_merge($this->parameters, [ | ||
'_pointsToNextItems' => $this->pointsToNextItems, | ||
]); | ||
} | ||
|
||
/** | ||
* Get the encoded string representation of the cursor to construct a URL. | ||
*/ | ||
public function encode(): string | ||
{ | ||
return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode(json_encode($this->toArray(), JSON_THROW_ON_ERROR))); | ||
} | ||
|
||
/** | ||
* Get a cursor instance from the encoded string representation. | ||
*/ | ||
public static function fromEncoded(?string $encodedString): ?static | ||
{ | ||
if (! is_string($encodedString)) { | ||
return null; | ||
} | ||
|
||
$parameters = json_decode(base64_decode(str_replace(['-', '_'], ['+', '/'], $encodedString)), true); | ||
|
||
if (json_last_error() !== JSON_ERROR_NONE) { | ||
return null; | ||
} | ||
|
||
$pointsToNextItems = $parameters['_pointsToNextItems']; | ||
|
||
unset($parameters['_pointsToNextItems']); | ||
|
||
return new static($parameters, $pointsToNextItems); | ||
} | ||
} |
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,138 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
/** | ||
* This file is part of Hyperf. | ||
* | ||
* @link https://www.hyperf.io | ||
* @document https://hyperf.wiki | ||
* @contact [email protected] | ||
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE | ||
*/ | ||
|
||
namespace Hyperf\Paginator; | ||
|
||
use ArrayAccess; | ||
use Countable; | ||
use Hyperf\Collection\Collection; | ||
use Hyperf\Contract\Arrayable; | ||
use Hyperf\Contract\Jsonable; | ||
use Hyperf\Paginator\Contract\CursorPaginator as CursorPaginatorContract; | ||
use IteratorAggregate; | ||
use JsonSerializable; | ||
|
||
class CursorPaginator extends AbstractCursorPaginator implements ArrayAccess, Arrayable, Countable, IteratorAggregate, Jsonable, JsonSerializable, CursorPaginatorContract | ||
{ | ||
/** | ||
* Indicates whether there are more items in the data source. | ||
* | ||
* @return bool | ||
*/ | ||
protected bool $hasMore; | ||
|
||
/** | ||
* Create a new paginator instance. | ||
*/ | ||
public function __construct(mixed $items, int $perPage, ?Cursor $cursor = null, array $options = []) | ||
{ | ||
$this->options = $options; | ||
|
||
foreach ($options as $key => $value) { | ||
$this->{$key} = $value; | ||
} | ||
|
||
$this->perPage = (int) $perPage; | ||
$this->cursor = $cursor; | ||
$this->path = $this->path !== '/' ? rtrim($this->path, '/') : $this->path; | ||
|
||
$this->setItems($items); | ||
} | ||
|
||
public function __toString(): string | ||
{ | ||
return $this->toJson(); | ||
} | ||
|
||
/** | ||
* Determine if there are more items in the data source. | ||
*/ | ||
public function hasMorePages(): bool | ||
{ | ||
return (is_null($this->cursor) && $this->hasMore) | ||
|| (! is_null($this->cursor) && $this->cursor->pointsToNextItems() && $this->hasMore) | ||
|| (! is_null($this->cursor) && $this->cursor->pointsToPreviousItems()); | ||
} | ||
|
||
/** | ||
* Determine if there are enough items to split into multiple pages. | ||
*/ | ||
public function hasPages(): bool | ||
{ | ||
return ! $this->onFirstPage() || $this->hasMorePages(); | ||
} | ||
|
||
/** | ||
* Determine if the paginator is on the first page. | ||
*/ | ||
public function onFirstPage(): bool | ||
{ | ||
return is_null($this->cursor) || ($this->cursor->pointsToPreviousItems() && ! $this->hasMore); | ||
} | ||
|
||
/** | ||
* Determine if the paginator is on the last page. | ||
*/ | ||
public function onLastPage(): bool | ||
{ | ||
return ! $this->hasMorePages(); | ||
} | ||
|
||
/** | ||
* Get the instance as an array. | ||
*/ | ||
public function toArray(): array | ||
{ | ||
return [ | ||
'data' => $this->items->toArray(), | ||
'path' => $this->path(), | ||
'per_page' => $this->perPage(), | ||
'next_cursor' => $this->nextCursor()?->encode(), | ||
'next_page_url' => $this->nextPageUrl(), | ||
'prev_cursor' => $this->previousCursor()?->encode(), | ||
'prev_page_url' => $this->previousPageUrl(), | ||
]; | ||
} | ||
|
||
/** | ||
* Convert the object into something JSON serializable. | ||
*/ | ||
public function jsonSerialize(): array | ||
{ | ||
return $this->toArray(); | ||
} | ||
|
||
/** | ||
* Convert the object to its JSON representation. | ||
*/ | ||
public function toJson(int $options = 0): string | ||
{ | ||
return json_encode($this->jsonSerialize(), JSON_THROW_ON_ERROR | $options); | ||
} | ||
|
||
/** | ||
* Set the items for the paginator. | ||
* @param mixed $items | ||
*/ | ||
protected function setItems($items) | ||
{ | ||
$this->items = $items instanceof Collection ? $items : Collection::make($items); | ||
|
||
$this->hasMore = $this->items->count() > $this->perPage; | ||
|
||
$this->items = $this->items->slice(0, $this->perPage); | ||
|
||
if (! is_null($this->cursor) && $this->cursor->pointsToPreviousItems()) { | ||
$this->items = $this->items->reverse()->values(); | ||
} | ||
} | ||
} |
Oops, something went wrong.