Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
66 / 66
100.00% covered (success)
100.00%
12 / 12
CRAP
100.00% covered (success)
100.00%
1 / 1
AbstractPaginator
100.00% covered (success)
100.00%
66 / 66
100.00% covered (success)
100.00%
12 / 12
27
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 setQueryKey
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setBookends
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 getTotal
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPerPage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRange
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getQueryKey
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCurrentPage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getNumberOfPages
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getBookend
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getBookends
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 calculateRange
100.00% covered (success)
100.00%
37 / 37
100.00% covered (success)
100.00%
1 / 1
9
1<?php
2declare(strict_types=1);
3/**
4 * Pop PHP Framework (https://www.popphp.org/)
5 *
6 * @link       https://github.com/popphp/popphp-framework
7 * @author     Nick Sagona, III <dev@noladev.com>
8 * @copyright  Copyright (c) 2009-2027 NOLA Interactive, LLC.
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\Paginator;
16
17/**
18 * Abstract paginator type class
19 *
20 * @category   Pop
21 * @package    Pop\Paginator
22 * @author     Nick Sagona, III <dev@noladev.com>
23 * @copyright  Copyright (c) 2009-2027 NOLA Interactive, LLC.
24 * @license    https://www.popphp.org/license     New BSD License
25 * @version    4.1.0
26 */
27abstract class AbstractPaginator implements PaginatorInterface
28{
29
30    /**
31     * Total number of items
32     * @var int
33     */
34    protected int $total = 0;
35
36    /**
37     * Number of items per page
38     * @var int
39     */
40    protected int $perPage = 10;
41
42    /**
43     * Range of pages per page
44     * @var int
45     */
46    protected int $range = 10;
47
48    /**
49     * Query key
50     * @var string
51     */
52    protected string $queryKey = 'page';
53
54    /**
55     * Current page property
56     * @var int
57     */
58    protected int $currentPage = 1;
59
60    /**
61     * Number of pages property
62     * @var ?int
63     */
64    protected ?int $numberOfPages = null;
65
66    /**
67     * Current page start index property
68     * @var ?int
69     */
70    protected ?int $start = null;
71
72    /**
73     * Current page end index property
74     * @var ?int
75     */
76    protected ?int $end = null;
77
78    /**
79     * Page bookends
80     * @var array
81     */
82    protected array $bookends = [
83        'start'    => '&laquo;',
84        'previous' => '&lsaquo;',
85        'next'     => '&rsaquo;',
86        'end'      => '&raquo;'
87    ];
88
89    /**
90     * Constructor
91     *
92     * Instantiate the paginator object
93     *
94     * @param  int $total
95     * @param  int $perPage
96     * @param  int $range
97     * @throws Exception
98     */
99    public function __construct(int $total, int $perPage = 10, int $range = 10)
100    {
101        if ($total < 0) {
102            throw new Exception('Error: The total value must be zero or greater.');
103        }
104        if ($perPage < 1) {
105            throw new Exception('Error: The per page value must be greater than zero.');
106        }
107        if ($range < 1) {
108            throw new Exception('Error: The range value must be greater than zero.');
109        }
110
111        $this->total   = $total;
112        $this->perPage = $perPage;
113        $this->range   = $range;
114
115        $this->calculateRange($this->currentPage);
116    }
117
118    /**
119     * Set the query key
120     *
121     * @param  string $key
122     * @return AbstractPaginator
123     */
124    public function setQueryKey(string $key): AbstractPaginator
125    {
126        $this->queryKey = $key;
127        return $this;
128    }
129
130    /**
131     * Set the bookends
132     *
133     * @param  array $bookends
134     * @return AbstractPaginator
135     */
136    public function setBookends(array $bookends): AbstractPaginator
137    {
138        if (array_key_exists('start', $bookends)) {
139            $this->bookends['start'] = $bookends['start'];
140        }
141        if (array_key_exists('previous', $bookends)) {
142            $this->bookends['previous'] = $bookends['previous'];
143        }
144        if (array_key_exists('next', $bookends)) {
145            $this->bookends['next'] = $bookends['next'];
146        }
147        if (array_key_exists('end', $bookends)) {
148            $this->bookends['end'] = $bookends['end'];
149        }
150
151        return $this;
152    }
153
154    /**
155     * Get the content items total
156     *
157     * @return int
158     */
159    public function getTotal(): int
160    {
161        return $this->total;
162    }
163
164    /**
165     * Get the per page
166     *
167     * @return int
168     */
169    public function getPerPage(): int
170    {
171        return $this->perPage;
172    }
173
174    /**
175     * Get the page range
176     *
177     * @return int
178     */
179    public function getRange(): int
180    {
181        return $this->range;
182    }
183
184    /**
185     * Get the query key
186     *
187     * @return string
188     */
189    public function getQueryKey(): string
190    {
191        return $this->queryKey;
192    }
193
194    /**
195     * Get the current page
196     *
197     * @return int
198     */
199    public function getCurrentPage(): int
200    {
201        return $this->currentPage;
202    }
203
204    /**
205     * Get the number of pages
206     *
207     * @return int
208     */
209    public function getNumberOfPages(): int
210    {
211        return $this->numberOfPages;
212    }
213
214    /**
215     * Get a bookend
216     *
217     * @param  string $key
218     * @return string|null
219     */
220    public function getBookend(string $key): string|null
221    {
222        return $this->bookends[$key] ?? null;
223    }
224
225    /**
226     * Get the bookends
227     *
228     * @return array
229     */
230    public function getBookends(): array
231    {
232        return $this->bookends;
233    }
234
235    /**
236     * Calculate the page range
237     *
238     * @param  int $page
239     * @return array
240     */
241    public function calculateRange(int $page = 1): array
242    {
243        $this->currentPage = $page;
244
245        // Calculate the number of pages based on the remainder.
246        $remainder = $this->total % $this->perPage;
247        $this->numberOfPages = ($remainder != 0) ? ((int)floor($this->total / $this->perPage) + 1) :
248            (int)floor($this->total / $this->perPage);
249
250        // Calculate the start index.
251        $this->start = ($page * $this->perPage) - $this->perPage;
252
253        // Calculate the end index.
254        if (($page == $this->numberOfPages) && ($remainder == 0)) {
255            $this->end = $this->start + $this->perPage;
256        } else if ($page == $this->numberOfPages) {
257            $this->end = (($page * $this->perPage) - ($this->perPage - $remainder));
258        } else {
259            $this->end = ($page * $this->perPage);
260        }
261
262        // Calculate if out of range.
263        if ($this->start >= $this->total) {
264            $this->start = 0;
265            $this->end   = $this->perPage;
266        }
267
268        // Check and calculate for any page ranges.
269        // If page is within the first range block.
270        if ($page <= $this->range) {
271            $range = [
272                'start' => 1,
273                'end'   => min($this->range, $this->numberOfPages),
274                'prev'  => false,
275                'next'  => ($this->numberOfPages > $this->range)
276            ];
277        } else {
278            // The first page number of the last range block, regardless of whether
279            // the number of pages divides evenly into the range.
280            $lastBlockStart = max(1, ($this->range * (int)floor(($this->numberOfPages - 1) / $this->range)) + 1);
281
282            // Else, if page is within the last range block.
283            if ($page >= $lastBlockStart) {
284                $range = [
285                    'start' => $lastBlockStart,
286                    'end'   => $this->numberOfPages,
287                    'prev'  => true,
288                    'next'  => false
289                ];
290            // Else, if page is within a middle range block.
291            } else {
292                $posInRange = (($page % $this->range) == 0) ? ($this->range - 1) : (($page % $this->range) - 1);
293                $linkStart = $page - $posInRange;
294                $range = [
295                    'start' => $linkStart,
296                    'end'   => $linkStart + ($this->range - 1),
297                    'prev'  => true,
298                    'next'  => true
299                ];
300            }
301        }
302
303        return $range;
304    }
305
306}