-
-
Notifications
You must be signed in to change notification settings - Fork 181
feat(database): add simplePaginate() to SelectQueryBuilder #2250
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 3.x
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| <?php | ||
|
|
||
| namespace Tempest\Support\Paginator; | ||
|
|
||
| use JsonSerializable; | ||
|
|
||
| /** | ||
| * @template T | ||
| */ | ||
| final class SimplePaginatedData implements JsonSerializable | ||
| { | ||
| /** | ||
| * @param array<T> $data | ||
| */ | ||
| public function __construct( | ||
| public array $data, | ||
| public int $currentPage, | ||
| public int $itemsPerPage, | ||
| public int $offset, | ||
| public int $limit, | ||
| public bool $hasNext, | ||
| public bool $hasPrevious, | ||
| public ?int $nextPage, | ||
| public ?int $previousPage, | ||
| ) {} | ||
|
|
||
| public int $count { | ||
| get => count($this->data); | ||
| } | ||
|
|
||
| public bool $isEmpty { | ||
| get => $this->count === 0; | ||
| } | ||
|
|
||
| public bool $isNotEmpty { | ||
| get => ! $this->isEmpty; | ||
| } | ||
|
|
||
| /** | ||
| * @template U | ||
| * | ||
| * @param callable(T): U $callback | ||
| * | ||
| * @return SimplePaginatedData<U> | ||
| */ | ||
| public function map(callable $callback): self | ||
| { | ||
| return new self( | ||
| data: array_map($callback, $this->data), | ||
| currentPage: $this->currentPage, | ||
| itemsPerPage: $this->itemsPerPage, | ||
| offset: $this->offset, | ||
| limit: $this->limit, | ||
| hasNext: $this->hasNext, | ||
| hasPrevious: $this->hasPrevious, | ||
| nextPage: $this->nextPage, | ||
| previousPage: $this->previousPage, | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * @return array{ | ||
| * data: array<T>, | ||
| * pagination: array{ | ||
| * current_page: int, | ||
| * items_per_page: int, | ||
| * offset: int, | ||
| * limit: int, | ||
| * has_next: bool, | ||
| * has_previous: bool, | ||
| * next_page: ?int, | ||
| * previous_page: ?int, | ||
| * count: int | ||
| * } | ||
| * } | ||
| */ | ||
| public function toArray(): array | ||
| { | ||
| return [ | ||
| 'data' => $this->data, | ||
| 'pagination' => [ | ||
| 'current_page' => $this->currentPage, | ||
| 'items_per_page' => $this->itemsPerPage, | ||
| 'offset' => $this->offset, | ||
| 'limit' => $this->limit, | ||
| 'has_next' => $this->hasNext, | ||
| 'has_previous' => $this->hasPrevious, | ||
| 'next_page' => $this->nextPage, | ||
| 'previous_page' => $this->previousPage, | ||
| 'count' => $this->count, | ||
| ], | ||
| ]; | ||
| } | ||
|
|
||
| /** | ||
| * @return array{ | ||
| * data: array<T>, | ||
| * pagination: array{ | ||
| * current_page: int, | ||
| * items_per_page: int, | ||
| * offset: int, | ||
| * limit: int, | ||
| * has_next: bool, | ||
| * has_previous: bool, | ||
| * next_page: ?int, | ||
| * previous_page: ?int, | ||
| * count: int | ||
| * } | ||
| * } | ||
| */ | ||
| public function jsonSerialize(): array | ||
| { | ||
| return $this->toArray(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| <?php | ||
|
|
||
| namespace Tempest\Support\Paginator; | ||
|
|
||
| use Tempest\Support\Paginator\Exceptions\ArgumentWasInvalid; | ||
|
|
||
| final class SimplePaginator | ||
| { | ||
| public function __construct( | ||
| private(set) int $itemsPerPage = 20, | ||
| private(set) int $currentPage = 1, | ||
| ) { | ||
| if ($this->itemsPerPage <= 0) { | ||
| throw new ArgumentWasInvalid('Items per page must be positive'); | ||
| } | ||
|
|
||
| if ($this->currentPage <= 0) { | ||
| throw new ArgumentWasInvalid('Current page must be positive'); | ||
| } | ||
|
Comment on lines
+13
to
+19
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's also create named, past tense exceptions for these, see other exceptions in the codebase. The old paginator follows the currently implemented patterns, but I'd consider it legacy and move towards named exceptions. Only fix these exceptions for now, if you wish, you can open a followup PR for the original paginator exceptions. Though I'd wait for @brendt's approval with this. |
||
| } | ||
|
|
||
| public int $offset { | ||
| get => ($this->currentPage - 1) * $this->itemsPerPage; | ||
| } | ||
|
|
||
| /** | ||
| * One additional item is requested to determine whether | ||
| * the next page exists. | ||
| */ | ||
| public int $limit { | ||
| get => $this->itemsPerPage + 1; | ||
| } | ||
|
|
||
| public bool $hasPrevious { | ||
| get => $this->currentPage > 1; | ||
| } | ||
|
|
||
| public ?int $previousPage { | ||
| get => $this->hasPrevious ? $this->currentPage - 1 : null; | ||
| } | ||
|
|
||
| public function withPage(int $page): self | ||
| { | ||
| return new self( | ||
| itemsPerPage: $this->itemsPerPage, | ||
| currentPage: $page, | ||
| ); | ||
| } | ||
|
|
||
| public function withItemsPerPage(int $itemsPerPage): self | ||
| { | ||
| return new self( | ||
| itemsPerPage: $itemsPerPage, | ||
| currentPage: $this->currentPage, | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Creates simple paginated data with the provided items. | ||
| * | ||
| * Any items beyond the configured page size are used to determine | ||
| * whether the next page exists and are omitted from the result. | ||
| * | ||
| * @template T | ||
| * @param array<T> $data | ||
| * @return SimplePaginatedData<T> | ||
| */ | ||
| public function paginate(array $data): SimplePaginatedData | ||
| { | ||
| $hasNext = count($data) > $this->itemsPerPage; | ||
| $data = array_slice($data, 0, $this->itemsPerPage); | ||
|
|
||
| return new SimplePaginatedData( | ||
| data: $data, | ||
| currentPage: $this->currentPage, | ||
| itemsPerPage: $this->itemsPerPage, | ||
| offset: $this->offset, | ||
| limit: $this->itemsPerPage, | ||
| hasNext: $hasNext, | ||
| hasPrevious: $this->hasPrevious, | ||
| nextPage: $hasNext ? $this->currentPage + 1 : null, | ||
| previousPage: $this->previousPage, | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Creates simple paginated data from a callable that fetches data. | ||
| * | ||
| * @template T | ||
| * @param callable(int $limit, int $offset): array<T> $callback | ||
| * @return SimplePaginatedData<T> | ||
| */ | ||
| public function paginateWith(callable $callback): SimplePaginatedData | ||
| { | ||
| return $this->paginate( | ||
| $callback($this->limit, $this->offset), | ||
| ); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's set an upper-bound as well. Though edge-case, a big enough argument (e.g.
PHP_INT_MAX) can cause an overflow, coercing the type to float and causing aTypeError.