Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
84.72% covered (warning)
84.72%
61 / 72
40.00% covered (danger)
40.00%
2 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
Pagination
84.72% covered (warning)
84.72%
61 / 72
40.00% covered (danger)
40.00%
2 / 5
27.23
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
5
 getPageNumbers
96.00% covered (success)
96.00%
24 / 25
0.00% covered (danger)
0.00%
0 / 1
7
 getShortShortcuts
54.55% covered (warning)
54.55%
6 / 11
0.00% covered (danger)
0.00%
0 / 1
5.50
 getLongShortcuts
54.55% covered (warning)
54.55%
6 / 11
0.00% covered (danger)
0.00%
0 / 1
3.85
 getShortcuts
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
6
1<?php
2
3declare(strict_types=1);
4
5namespace PeServer\Core\Mvc;
6
7use PeServer\Core\Collection\Arr;
8use PeServer\Core\Mvc\PageShortcut;
9use PeServer\Core\Mvc\PageShortcutKind;
10use PeServer\Core\Throws\ArgumentException;
11
12/**
13 * ページャ。
14 *
15 * @immutable
16 */
17class 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}