Passed
Push — master ( 989b30...0f788a )
by Caen
03:17 queued 13s
created

Paginator::next()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 0
dl 0
loc 11
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Hyde\Framework\Features;
6
7
use function collect;
8
use Hyde\Hyde;
9
use Hyde\Support\Models\Route;
10
use Illuminate\Contracts\Support\Arrayable;
11
use Illuminate\Support\Collection;
12
use InvalidArgumentException;
13
use function range;
14
use function sprintf;
15
16
/**
17
 * @see \Hyde\Framework\Testing\Feature\PaginatorTest
18
 */
19
class Paginator
20
{
21
    protected Collection $paginatedItems;
22
23
    protected int $pageSize = 25;
24
    protected int $currentPage = 1;
25
26
    /**
27
     * Optionally provide a route basename to be used in generating the pagination links.
28
     */
29
    protected string $routeBasename;
30
31
    public function __construct(Arrayable|array $items = [], int $pageSize = 25, int $currentPageNumber = null, string $paginationRouteBasename = null)
32
    {
33
        $this->pageSize = $pageSize;
34
35
        $this->generate(collect($items));
0 ignored issues
show
Bug introduced by
$items of type array is incompatible with the type Illuminate\Contracts\Support\Arrayable expected by parameter $value of collect(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

35
        $this->generate(collect(/** @scrutinizer ignore-type */ $items));
Loading history...
36
37
        if ($currentPageNumber) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $currentPageNumber of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
38
            $this->setCurrentPage($currentPageNumber);
39
        }
40
41
        if ($paginationRouteBasename) {
42
            $this->routeBasename = $paginationRouteBasename;
43
        }
44
    }
45
46
    protected function generate(Collection $items): void
47
    {
48
        $this->paginatedItems = $items->chunk($this->perPage());
49
    }
50
51
    /** Set the current page number. */
52
    public function setCurrentPage(int $currentPage): Paginator
53
    {
54
        $this->validateCurrentPageValue($currentPage);
55
56
        $this->currentPage = $currentPage;
57
58
        return $this;
59
    }
60
61
    /** Get the current page number (which is used as a cursor). */
62
    public function currentPage(): int
63
    {
64
        return $this->currentPage;
65
    }
66
67
    /** Get the paginated collection */
68
    public function getPaginatedItems(): Collection
69
    {
70
        return $this->paginatedItems;
71
    }
72
73
    public function getItemsForPage(): Collection
74
    {
75
        return $this->paginatedItems->get($this->currentPage - 1);
76
    }
77
78
    public function getPageLinks(): array
79
    {
80
        $array = [];
81
        $pageRange = range(1, $this->totalPages());
82
        if (isset($this->routeBasename)) {
83
            foreach ($pageRange as $number) {
84
                $array[$number] = Route::get("$this->routeBasename/page-$number") ?? Hyde::formatLink("$this->routeBasename/page-$number");
85
            }
86
        } else {
87
            foreach ($pageRange as $number) {
88
                $array[$number] = Hyde::formatLink("page-$number.html");
89
            }
90
        }
91
92
        return $array;
93
    }
94
95
    /** The number of items to be shown per page. */
96
    public function perPage(): int
97
    {
98
        return $this->pageSize;
99
    }
100
101
    /** Get the total number of pages. */
102
    public function totalPages(): int
103
    {
104
        return $this->paginatedItems->count();
105
    }
106
107
    /** Determine if there are enough items to split into multiple pages. */
108
    public function hasMultiplePages(): bool
109
    {
110
        return $this->totalPages() > 1;
111
    }
112
113
    /** Get the page number of the last available page. */
114
    public function lastPage(): int
115
    {
116
        return $this->totalPages();
117
    }
118
119
    /** Determine if there are fewer items after the cursor in the data store. */
120
    public function canNavigateBack(): bool
121
    {
122
        return $this->currentPage > $this->firstPage();
123
    }
124
125
    /** Determine if there are more items after the cursor in the data store. */
126
    public function canNavigateForward(): bool
127
    {
128
        return $this->currentPage < $this->lastPage();
129
    }
130
131
    public function previousPageNumber(): false|int
132
    {
133
        if (! $this->canNavigateBack()) {
134
            return false;
135
        }
136
137
        return $this->currentPage - 1;
138
    }
139
140
    public function nextPageNumber(): false|int
141
    {
142
        if (! $this->canNavigateForward()) {
143
            return false;
144
        }
145
146
        return $this->currentPage + 1;
147
    }
148
149
    public function previous(): false|string|Route
150
    {
151
        if (! $this->canNavigateBack()) {
152
            return false;
153
        }
154
155
        if (! isset($this->routeBasename)) {
156
            return $this->formatLink(-1);
157
        }
158
159
        return $this->getRoute(-1);
160
    }
161
162
    public function next(): false|string|Route
163
    {
164
        if (! $this->canNavigateForward()) {
165
            return false;
166
        }
167
168
        if (! isset($this->routeBasename)) {
169
            return $this->formatLink(+1);
170
        }
171
172
        return $this->getRoute(+1);
173
    }
174
175
    public function firstItemNumberOnPage(): int
176
    {
177
        return (($this->currentPage - 1) * $this->perPage()) + 1;
178
    }
179
180
    protected function validateCurrentPageValue(int $currentPage): void
181
    {
182
        if ($currentPage < $this->firstPage()) {
183
            throw new InvalidArgumentException('Current page number must be greater than 0.');
184
        }
185
186
        if ($currentPage > $this->lastPage()) {
187
            throw new InvalidArgumentException('Current page number must be less than or equal to the last page number.');
188
        }
189
    }
190
191
    protected function formatPageName(int $offset): string
192
    {
193
        return sprintf('page-%d', $this->currentPage + $offset);
194
    }
195
196
    protected function formatLink(int $offset): string
197
    {
198
        return Hyde::formatLink("{$this->formatPageName($offset)}.html");
199
    }
200
201
    protected function getRoute(int $offset): Route|string
202
    {
203
        return Route::get("$this->routeBasename/{$this->formatPageName($offset)}") ?? Hyde::formatLink("$this->routeBasename/{$this->formatPageName($offset)}");
204
    }
205
206
    protected function firstPage(): int
207
    {
208
        return 1;
209
    }
210
}
211