Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
84.72% |
61 / 72 |
|
40.00% |
2 / 5 |
CRAP | |
0.00% |
0 / 1 |
Pagination | |
84.72% |
61 / 72 |
|
40.00% |
2 / 5 |
27.23 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
5 | |||
getPageNumbers | |
96.00% |
24 / 25 |
|
0.00% |
0 / 1 |
7 | |||
getShortShortcuts | |
54.55% |
6 / 11 |
|
0.00% |
0 / 1 |
5.50 | |||
getLongShortcuts | |
54.55% |
6 / 11 |
|
0.00% |
0 / 1 |
3.85 | |||
getShortcuts | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
6 |
1 | <?php |
2 | |
3 | declare(strict_types=1); |
4 | |
5 | namespace PeServer\Core\Mvc; |
6 | |
7 | use PeServer\Core\Collection\Arr; |
8 | use PeServer\Core\Mvc\PageShortcut; |
9 | use PeServer\Core\Mvc\PageShortcutKind; |
10 | use PeServer\Core\Throws\ArgumentException; |
11 | |
12 | /** |
13 | * ページャ。 |
14 | * |
15 | * @immutable |
16 | */ |
17 | class Pagination |
18 | { |
19 | #region define |
20 | |
21 | /** |
22 | * ページ番号基点。 |
23 | */ |
24 | public const FIRST_PAGE_NUMBER = 1; |
25 | |
26 | private const SHORTCUT_HEAD = 0; |
27 | private const SHORTCUT_TAIL = 1; |
28 | |
29 | #endregion |
30 | |
31 | #region variable |
32 | |
33 | /** |
34 | * 全ショートカット数。 |
35 | * |
36 | * 全てなので `$shortcutMaxCount` を超過する。 |
37 | * |
38 | * @var int |
39 | */ |
40 | public int $shortcutTotalItemCount; |
41 | |
42 | #endregion |
43 | |
44 | /** |
45 | * 生成。 |
46 | * |
47 | * @param int $currentPageNumber 現在ページ番号(1基点) |
48 | * @phpstan-param positive-int $currentPageNumber |
49 | * @param int $itemCountInPage ページ内アイテムの表示件数。 |
50 | * @phpstan-param positive-int $itemCountInPage |
51 | * @param int $totalItemCount アイテム全件数。 |
52 | * @phpstan-param non-negative-int $totalItemCount |
53 | * @param bool $shortJump 直近(前後)へのリンク表示。 |
54 | * @param bool $longJump 全件数(最初と最後)へのリンク表示。 |
55 | * @param int $shortcutMaxCount ショートカットリンク表示数。 |
56 | * @phpstan-param non-negative-int $shortcutMaxCount ショートカットリンク表示数。 |
57 | */ |
58 | public function __construct( |
59 | public int $currentPageNumber, |
60 | public int $itemCountInPage, |
61 | public int $totalItemCount, |
62 | public bool $shortJump = true, |
63 | public bool $longJump = true, |
64 | private int $shortcutMaxCount = 5 |
65 | ) { |
66 | if ($itemCountInPage < 0) { //@phpstan-ignore-line [DOCTYPE] |
67 | throw new ArgumentException('$itemCountInPage'); |
68 | } |
69 | |
70 | if (!$totalItemCount) { |
71 | $this->currentPageNumber = self::FIRST_PAGE_NUMBER; |
72 | $this->shortcutTotalItemCount = 0; |
73 | } else { |
74 | $this->shortcutTotalItemCount = (int)ceil($this->totalItemCount / $this->itemCountInPage); |
75 | if ($this->shortcutTotalItemCount <= $this->currentPageNumber) { |
76 | $this->currentPageNumber = $this->shortcutTotalItemCount; //@phpstan-ignore-line [DOCTYPE] |
77 | } elseif (!$this->currentPageNumber) { //@phpstan-ignore-line [DOCTYPE] |
78 | $this->currentPageNumber = self::FIRST_PAGE_NUMBER; |
79 | } |
80 | } |
81 | } |
82 | |
83 | #region function |
84 | |
85 | /** |
86 | * 通常ショートカットのみを取得。 |
87 | * |
88 | * TODO: 偶数処理が💩 |
89 | * |
90 | * @return PageShortcut[] |
91 | */ |
92 | public function getPageNumbers(): array |
93 | { |
94 | if ($this->shortcutTotalItemCount <= 0) { |
95 | return []; |
96 | } |
97 | |
98 | /** @phpstan-var positive-int[] */ |
99 | $pageNumbers = []; |
100 | |
101 | if ($this->shortcutTotalItemCount <= $this->shortcutMaxCount) { |
102 | // ショートカット全件がショートカット設定数以下は全件を指定する |
103 | $pageNumbers = Arr::range(self::FIRST_PAGE_NUMBER, $this->shortcutTotalItemCount); |
104 | } else { |
105 | $beginWidth = (int)($this->shortcutMaxCount / 2); |
106 | $endWidth = $this->shortcutMaxCount - $beginWidth; |
107 | if ($this->currentPageNumber - $beginWidth < 1) { |
108 | $pageNumbers = Arr::range(1, $this->shortcutMaxCount); |
109 | } elseif ($this->shortcutTotalItemCount - $endWidth < $this->currentPageNumber) { |
110 | $pageNumbers = Arr::range($this->shortcutTotalItemCount - $this->shortcutMaxCount + 1, $this->shortcutMaxCount); |
111 | } else { |
112 | $beginPageNumber = $this->currentPageNumber - (int)($this->shortcutMaxCount / 2); |
113 | if ($this->currentPageNumber < $beginPageNumber) { |
114 | $beginPageNumber -= (int)($this->shortcutMaxCount / 2); |
115 | } |
116 | |
117 | $pageNumbers = Arr::range($beginPageNumber, $this->shortcutMaxCount); |
118 | } |
119 | } |
120 | |
121 | |
122 | /** @var PageShortcut[] */ |
123 | $shortcuts = []; |
124 | foreach ($pageNumbers as $pageNumber) { |
125 | /** @phpstan-var positive-int $pageNumber */ |
126 | $item = new PageShortcut( |
127 | $pageNumber, |
128 | $this->currentPageNumber == $pageNumber, |
129 | true, |
130 | PageShortcutKind::Normal, |
131 | ); |
132 | |
133 | $shortcuts[] = $item; |
134 | } |
135 | |
136 | return $shortcuts; |
137 | } |
138 | |
139 | /** |
140 | * Undocumented function |
141 | * |
142 | * @param PageShortcut[] $pageShortcuts |
143 | * @return PageShortcut[]|null |
144 | */ |
145 | private function getShortShortcuts(array $pageShortcuts): ?array |
146 | { |
147 | if (!$this->shortJump) { |
148 | return null; |
149 | } |
150 | |
151 | if ($this->shortcutTotalItemCount === 0 || empty($pageShortcuts)) { |
152 | return [ |
153 | self::SHORTCUT_HEAD => new PageShortcut(PHP_INT_MIN, false, false, PageShortcutKind::Short), //@phpstan-ignore-line このページ番号は使用しない |
154 | self::SHORTCUT_TAIL => new PageShortcut(PHP_INT_MAX, false, false, PageShortcutKind::Short), // このページ番号は使用しない |
155 | ]; |
156 | } |
157 | |
158 | return [ |
159 | self::SHORTCUT_HEAD => new PageShortcut($this->currentPageNumber - 1, false, $this->currentPageNumber !== 1, PageShortcutKind::Short), //@phpstan-ignore-line 状況次第でこのページ番号は使用しない |
160 | self::SHORTCUT_TAIL => new PageShortcut($this->currentPageNumber + 1, false, $this->currentPageNumber !== $this->shortcutTotalItemCount, PageShortcutKind::Short), //状況次第でこのページ番号は使用しない |
161 | ]; |
162 | } |
163 | |
164 | /** |
165 | * Undocumented function |
166 | * |
167 | * @return PageShortcut[]|null |
168 | */ |
169 | private function getLongShortcuts(): ?array |
170 | { |
171 | if (!$this->longJump) { |
172 | return null; |
173 | } |
174 | |
175 | if ($this->shortcutTotalItemCount === 0) { |
176 | return [ |
177 | self::SHORTCUT_HEAD => new PageShortcut(PHP_INT_MIN, false, false, PageShortcutKind::Long), //@phpstan-ignore-line このページ番号は使用しない |
178 | self::SHORTCUT_TAIL => new PageShortcut(PHP_INT_MAX, false, false, PageShortcutKind::Long), // このページ番号は使用しない |
179 | ]; |
180 | } |
181 | |
182 | return [ |
183 | self::SHORTCUT_HEAD => new PageShortcut(1, false, $this->currentPageNumber !== 1, PageShortcutKind::Long), |
184 | self::SHORTCUT_TAIL => new PageShortcut($this->shortcutTotalItemCount, false, $this->currentPageNumber !== $this->shortcutTotalItemCount, PageShortcutKind::Long), //@phpstan-ignore-line |
185 | ]; |
186 | } |
187 | |
188 | /** |
189 | * ページャのあれこれを返す。 |
190 | * |
191 | * View側で回す想定。 |
192 | * |
193 | * @return PageShortcut[] |
194 | */ |
195 | public function getShortcuts(): array |
196 | { |
197 | /** @var PageShortcut[] */ |
198 | $result = []; |
199 | |
200 | $numbers = $this->getPageNumbers(); |
201 | |
202 | $long = $this->getLongShortcuts(); |
203 | $short = $this->getShortShortcuts($numbers); |
204 | |
205 | if ($long !== null) { |
206 | $result[] = $long[self::SHORTCUT_HEAD]; |
207 | } |
208 | if ($short !== null) { |
209 | $result[] = $short[self::SHORTCUT_HEAD]; |
210 | } |
211 | |
212 | foreach ($numbers as $number) { |
213 | $result[] = $number; |
214 | } |
215 | //$result += $numbers; |
216 | |
217 | if ($short !== null) { |
218 | $result[] = $short[self::SHORTCUT_TAIL]; |
219 | } |
220 | if ($long !== null) { |
221 | $result[] = $long[self::SHORTCUT_TAIL]; |
222 | } |
223 | |
224 | return $result; |
225 | } |
226 | |
227 | #endregion |
228 | } |