Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
53 / 53
100.00% covered (success)
100.00%
23 / 23
CRAP
100.00% covered (success)
100.00%
1 / 1
Paginator
100.00% covered (success)
100.00%
53 / 53
100.00% covered (success)
100.00%
23 / 23
36
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 generate
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setCurrentPage
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 currentPage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPaginatedItems
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getItemsForPage
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getPageLinks
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 perPage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 totalPages
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasMultiplePages
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 lastPage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 canNavigateBack
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 canNavigateForward
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 previousPageNumber
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 nextPageNumber
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 previous
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 next
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 firstItemNumberOnPage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 validateCurrentPageValue
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 formatPageName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 formatLink
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRoute
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 firstPage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace Hyde\Support;
6
7use Hyde\Hyde;
8use InvalidArgumentException;
9use Hyde\Support\Models\Route;
10use Hyde\Foundation\Facades\Routes;
11use Illuminate\Support\Collection;
12use Illuminate\Contracts\Support\Arrayable;
13
14use function collect;
15use function sprintf;
16use function range;
17
18class Paginator
19{
20    protected Collection $paginatedItems;
21
22    protected int $pageSize = 25;
23    protected int $currentPage = 1;
24
25    /**
26     * Optionally provide a route basename to be used in generating the pagination links.
27     */
28    protected string $routeBasename;
29
30    public function __construct(Arrayable|array $items = [], int $pageSize = 25, int $currentPageNumber = null, string $paginationRouteBasename = null)
31    {
32        $this->pageSize = $pageSize;
33
34        $this->generate(collect($items));
35
36        if ($currentPageNumber) {
37            $this->setCurrentPage($currentPageNumber);
38        }
39
40        if ($paginationRouteBasename) {
41            $this->routeBasename = $paginationRouteBasename;
42        }
43    }
44
45    protected function generate(Collection $items): void
46    {
47        $this->paginatedItems = $items->chunk($this->perPage());
48    }
49
50    /** Set the current page number. */
51    public function setCurrentPage(int $currentPage): Paginator
52    {
53        $this->validateCurrentPageValue($currentPage);
54
55        $this->currentPage = $currentPage;
56
57        return $this;
58    }
59
60    /** Get the current page number (which is used as a cursor). */
61    public function currentPage(): int
62    {
63        return $this->currentPage;
64    }
65
66    /** Get the paginated collection */
67    public function getPaginatedItems(): Collection
68    {
69        return $this->paginatedItems;
70    }
71
72    public function getItemsForPage(): Collection
73    {
74        /** @var Collection $paginated */
75        $paginated = $this->paginatedItems->get($this->currentPage - 1);
76
77        return $paginated;
78    }
79
80    public function getPageLinks(): array
81    {
82        $array = [];
83
84        $pageRange = range(1, $this->totalPages());
85
86        if (isset($this->routeBasename)) {
87            foreach ($pageRange as $number) {
88                $array[$number] = Routes::get("$this->routeBasename/page-$number") ?? Hyde::formatLink("$this->routeBasename/page-$number");
89            }
90        } else {
91            foreach ($pageRange as $number) {
92                $array[$number] = Hyde::formatLink("page-$number.html");
93            }
94        }
95
96        return $array;
97    }
98
99    /** The number of items to be shown per page. */
100    public function perPage(): int
101    {
102        return $this->pageSize;
103    }
104
105    /** Get the total number of pages. */
106    public function totalPages(): int
107    {
108        return $this->paginatedItems->count();
109    }
110
111    /** Determine if there are enough items to split into multiple pages. */
112    public function hasMultiplePages(): bool
113    {
114        return $this->totalPages() > 1;
115    }
116
117    /** Get the page number of the last available page. */
118    public function lastPage(): int
119    {
120        return $this->totalPages();
121    }
122
123    /** Determine if there are fewer items after the cursor in the data store. */
124    public function canNavigateBack(): bool
125    {
126        return $this->currentPage > $this->firstPage();
127    }
128
129    /** Determine if there are more items after the cursor in the data store. */
130    public function canNavigateForward(): bool
131    {
132        return $this->currentPage < $this->lastPage();
133    }
134
135    public function previousPageNumber(): false|int
136    {
137        if (! $this->canNavigateBack()) {
138            return false;
139        }
140
141        return $this->currentPage - 1;
142    }
143
144    public function nextPageNumber(): false|int
145    {
146        if (! $this->canNavigateForward()) {
147            return false;
148        }
149
150        return $this->currentPage + 1;
151    }
152
153    public function previous(): false|string|Route
154    {
155        if (! $this->canNavigateBack()) {
156            return false;
157        }
158
159        if (! isset($this->routeBasename)) {
160            return $this->formatLink(-1);
161        }
162
163        return $this->getRoute(-1);
164    }
165
166    public function next(): false|string|Route
167    {
168        if (! $this->canNavigateForward()) {
169            return false;
170        }
171
172        if (! isset($this->routeBasename)) {
173            return $this->formatLink(+1);
174        }
175
176        return $this->getRoute(+1);
177    }
178
179    public function firstItemNumberOnPage(): int
180    {
181        return (($this->currentPage - 1) * $this->perPage()) + 1;
182    }
183
184    protected function validateCurrentPageValue(int $currentPage): void
185    {
186        if ($currentPage < $this->firstPage()) {
187            throw new InvalidArgumentException('Current page number must be greater than 0.');
188        }
189
190        if ($currentPage > $this->lastPage()) {
191            throw new InvalidArgumentException('Current page number must be less than or equal to the last page number.');
192        }
193    }
194
195    protected function formatPageName(int $offset): string
196    {
197        return sprintf('page-%d', $this->currentPage + $offset);
198    }
199
200    protected function formatLink(int $offset): string
201    {
202        return Hyde::formatLink("{$this->formatPageName($offset)}.html");
203    }
204
205    protected function getRoute(int $offset): Route|string
206    {
207        return Routes::get("$this->routeBasename/{$this->formatPageName($offset)}") ?? Hyde::formatLink("$this->routeBasename/{$this->formatPageName($offset)}");
208    }
209
210    protected function firstPage(): int
211    {
212        return 1;
213    }
214}