• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

aplus-framework / pagination / 4999241113

pending completion
4999241113

push

github

Natan Felles
Add return types in user guide comments

585 of 585 relevant lines covered (100.0%)

6.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/src/Pager.php
1
<?php declare(strict_types=1);
2
/*
3
 * This file is part of Aplus Framework Pagination Library.
4
 *
5
 * (c) Natan Felles <natanfelles@gmail.com>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace Framework\Pagination;
11

12
use Framework\Helpers\Isolation;
13
use Framework\HTTP\URL;
14
use Framework\Language\Language;
15
use InvalidArgumentException;
16
use JetBrains\PhpStorm\ArrayShape;
17
use JetBrains\PhpStorm\Deprecated;
18
use JetBrains\PhpStorm\Pure;
19
use JsonSerializable;
20
use LogicException;
21
use Stringable;
22

23
/**
24
 * Class Pager.
25
 *
26
 * @package pagination
27
 */
28
class Pager implements JsonSerializable, Stringable
29
{
30
    protected int $currentPage;
31
    protected ?int $previousPage = null;
32
    protected ?int $nextPage = null;
33
    protected int $lastPage;
34
    protected int $itemsPerPage;
35
    protected int $totalItems;
36
    protected int $surround = 2;
37
    /**
38
     * @var array<string,string>
39
     */
40
    protected array $views = [
41
        // HTML Head
42
        'head' => __DIR__ . '/Views/head.php',
43
        // HTTP Header
44
        'header' => __DIR__ . '/Views/header.php',
45
        // HTML Previous and Next
46
        'pager' => __DIR__ . '/Views/pagination-short.php',
47
        // HTML Full
48
        'pagination' => __DIR__ . '/Views/pagination.php',
49
        'pagination-short' => __DIR__ . '/Views/pagination-short.php',
50
        // Bootstrap 5
51
        'bootstrap' => __DIR__ . '/Views/bootstrap.php',
52
        'bootstrap-short' => __DIR__ . '/Views/bootstrap-short.php',
53
        'bootstrap5' => __DIR__ . '/Views/bootstrap.php',
54
        'bootstrap5-short' => __DIR__ . '/Views/bootstrap-short.php',
55
        // Bulma 0
56
        'bulma' => __DIR__ . '/Views/bulma.php',
57
        'bulma-short' => __DIR__ . '/Views/bulma-short.php',
58
        // Foundation 6
59
        'foundation' => __DIR__ . '/Views/foundation.php',
60
        'foundation-short' => __DIR__ . '/Views/foundation-short.php',
61
        'foundation6' => __DIR__ . '/Views/foundation.php',
62
        'foundation6-short' => __DIR__ . '/Views/foundation-short.php',
63
        // Materialize 1
64
        'materialize' => __DIR__ . '/Views/materialize.php',
65
        'materialize-short' => __DIR__ . '/Views/materialize-short.php',
66
        'materialize1' => __DIR__ . '/Views/materialize.php',
67
        'materialize1-short' => __DIR__ . '/Views/materialize-short.php',
68
        // Primer 20
69
        'primer' => __DIR__ . '/Views/primer.php',
70
        'primer-short' => __DIR__ . '/Views/primer-short.php',
71
        'primer20' => __DIR__ . '/Views/primer.php',
72
        'primer20-short' => __DIR__ . '/Views/primer-short.php',
73
        // Semantic UI 2
74
        'semantic-ui' => __DIR__ . '/Views/semantic-ui.php',
75
        'semantic-ui-short' => __DIR__ . '/Views/semantic-ui-short.php',
76
        'semantic-ui2' => __DIR__ . '/Views/semantic-ui.php',
77
        'semantic-ui2-short' => __DIR__ . '/Views/semantic-ui-short.php',
78
        // Tailwind CSS 3
79
        'tailwind' => __DIR__ . '/Views/tailwind.php',
80
        'tailwind-short' => __DIR__ . '/Views/tailwind-short.php',
81
        'tailwind3' => __DIR__ . '/Views/tailwind.php',
82
        'tailwind3-short' => __DIR__ . '/Views/tailwind-short.php',
83
        // W3.CSS 4
84
        'w3' => __DIR__ . '/Views/w3.php',
85
        'w3-short' => __DIR__ . '/Views/w3-short.php',
86
        'w34' => __DIR__ . '/Views/w3.php',
87
        'w34-short' => __DIR__ . '/Views/w3-short.php',
88
    ];
89
    protected string $defaultView = 'pagination';
90
    protected URL $url;
91
    protected string $oldUrl;
92
    protected string $query = 'page';
93
    protected Language $language;
94

95
    /**
96
     * Pager constructor.
97
     *
98
     * @param int|string $currentPage
99
     * @param int|string $itemsPerPage
100
     * @param int $totalItems
101
     * @param Language|null $language Language instance
102
     * @param string|null $url
103
     */
104
    public function __construct(
105
        int | string $currentPage,
106
        int | string $itemsPerPage,
107
        int $totalItems,
108
        Language $language = null,
109
        string $url = null
110
    ) {
111
        if ($language) {
44✔
112
            $this->setLanguage($language);
1✔
113
        }
114
        $this->setCurrentPage(static::sanitize($currentPage))
44✔
115
            ->setItemsPerPage(static::sanitize($itemsPerPage))
44✔
116
            ->setTotalItems($totalItems);
44✔
117
        $lastPage = (int) \ceil($this->getTotalItems() / $this->getItemsPerPage());
44✔
118
        $this->setLastPage(static::sanitize($lastPage));
44✔
119
        if ($this->getCurrentPage() > 1) {
44✔
120
            if ($this->getCurrentPage() - 1 <= $this->getLastPage()) {
26✔
121
                $this->setPreviousPage($this->getCurrentPage() - 1);
24✔
122
            } elseif ($this->getLastPage() > 1) {
2✔
123
                $this->setPreviousPage($this->getLastPage());
2✔
124
            }
125
        }
126
        if ($this->getCurrentPage() < $this->getLastPage()) {
44✔
127
            $this->setNextPage($this->getCurrentPage() + 1);
44✔
128
        }
129
        isset($url) ? $this->setUrl($url) : $this->prepareUrl();
44✔
130
    }
131

132
    public function __toString() : string
133
    {
134
        return $this->render();
1✔
135
    }
136

137
    /**
138
     * @since 3.5
139
     *
140
     * @return int
141
     */
142
    public function getItemsPerPage() : int
143
    {
144
        return $this->itemsPerPage;
44✔
145
    }
146

147
    /**
148
     * @since 3.5
149
     *
150
     * @param int $itemsPerPage
151
     *
152
     * @return static
153
     */
154
    protected function setItemsPerPage(int $itemsPerPage) : static
155
    {
156
        $this->itemsPerPage = $itemsPerPage;
44✔
157
        return $this;
44✔
158
    }
159

160
    /**
161
     * @since 3.5
162
     *
163
     * @return int
164
     */
165
    public function getTotalItems() : int
166
    {
167
        return $this->totalItems;
44✔
168
    }
169

170
    /**
171
     * @since 3.5
172
     *
173
     * @param int $totalItems
174
     *
175
     * @return static
176
     */
177
    protected function setTotalItems(int $totalItems) : static
178
    {
179
        $this->totalItems = $totalItems;
44✔
180
        return $this;
44✔
181
    }
182

183
    /**
184
     * @param Language|null $language
185
     *
186
     * @return static
187
     */
188
    public function setLanguage(Language $language = null) : static
189
    {
190
        $this->language = $language ?? new Language();
27✔
191
        $this->language->addDirectory(__DIR__ . '/Languages');
27✔
192
        return $this;
27✔
193
    }
194

195
    /**
196
     * @return Language
197
     */
198
    public function getLanguage() : Language
199
    {
200
        if ( ! isset($this->language)) {
27✔
201
            $this->setLanguage();
27✔
202
        }
203
        return $this->language;
27✔
204
    }
205

206
    /**
207
     * @param string $name
208
     * @param string $filepath
209
     *
210
     * @return static
211
     */
212
    public function setView(string $name, string $filepath) : static
213
    {
214
        if ( ! \is_file($filepath)) {
2✔
215
            throw new InvalidArgumentException('Invalid Pager view filepath: ' . $filepath);
1✔
216
        }
217
        $this->views[$name] = $filepath;
1✔
218
        return $this;
1✔
219
    }
220

221
    /**
222
     * Get a view filepath.
223
     *
224
     * @param string $name The view name. Default names are: head, header, pager and pagination
225
     *
226
     * @return string
227
     */
228
    public function getView(string $name) : string
229
    {
230
        if (empty($this->views[$name])) {
28✔
231
            throw new InvalidArgumentException('Pager view not found: ' . $name);
1✔
232
        }
233
        return $this->views[$name];
27✔
234
    }
235

236
    /**
237
     * @return array<string,string>
238
     */
239
    #[Pure]
240
    public function getViews() : array
241
    {
242
        return $this->views;
1✔
243
    }
244

245
    /**
246
     * @return int
247
     */
248
    #[Pure]
249
    public function getSurround() : int
250
    {
251
        return $this->surround;
12✔
252
    }
253

254
    /**
255
     * @param int $surround
256
     *
257
     * @return static
258
     */
259
    public function setSurround(int $surround) : static
260
    {
261
        $this->surround = $surround < 0 ? 0 : $surround;
2✔
262
        return $this;
2✔
263
    }
264

265
    /**
266
     * @param string $query
267
     *
268
     * @return static
269
     */
270
    public function setQuery(string $query = 'page') : static
271
    {
272
        $this->query = $query;
1✔
273
        return $this;
1✔
274
    }
275

276
    #[Pure]
277
    public function getQuery() : string
278
    {
279
        return $this->query;
44✔
280
    }
281

282
    /**
283
     * @param array<int,string> $allowed
284
     *
285
     * @return static
286
     */
287
    public function setAllowedQueries(array $allowed) : static
288
    {
289
        $this->setUrl($this->oldUrl, $allowed);
1✔
290
        return $this;
1✔
291
    }
292

293
    protected function prepareUrl() : static
294
    {
295
        $scheme = ((isset($_SERVER['REQUEST_SCHEME']) && $_SERVER['REQUEST_SCHEME'] === 'https')
44✔
296
            || (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on'))
44✔
297
            ? 'https'
1✔
298
            : 'http';
44✔
299
        $this->setUrl($scheme . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
44✔
300
        return $this;
44✔
301
    }
302

303
    /**
304
     * @param string|URL $currentPageUrl
305
     * @param array<int,string> $allowedQueries
306
     *
307
     * @return static
308
     */
309
    public function setUrl(string | URL $currentPageUrl, array $allowedQueries = []) : static
310
    {
311
        $currentPageUrl = $currentPageUrl instanceof URL
44✔
312
            ? clone $currentPageUrl
1✔
313
            : new URL($currentPageUrl);
44✔
314
        $this->oldUrl = $currentPageUrl->toString();
44✔
315
        $allowedQueries[] = $this->getQuery();
44✔
316
        $currentPageUrl->setQuery($currentPageUrl->getQuery() ?? '', $allowedQueries);
44✔
317
        $this->url = $currentPageUrl;
44✔
318
        return $this;
44✔
319
    }
320

321
    public function getUrl() : URL
322
    {
323
        return $this->url;
2✔
324
    }
325

326
    public function getPageUrl(?int $page) : ?string
327
    {
328
        if ($page === null || $page === 0) {
33✔
329
            return null;
5✔
330
        }
331
        return $this->url->addQuery($this->getQuery(), $page)->toString();
33✔
332
    }
333

334
    public function getCurrentPage() : int
335
    {
336
        return $this->currentPage;
44✔
337
    }
338

339
    /**
340
     * @since 3.5
341
     *
342
     * @param int $currentPage
343
     *
344
     * @return static
345
     */
346
    protected function setCurrentPage(int $currentPage) : static
347
    {
348
        $this->currentPage = $currentPage;
44✔
349
        return $this;
44✔
350
    }
351

352
    public function getCurrentPageUrl() : string
353
    {
354
        return $this->getPageUrl($this->currentPage);
16✔
355
    }
356

357
    #[Pure]
358
    public function getFirstPage() : int
359
    {
360
        return 1;
18✔
361
    }
362

363
    public function getFirstPageUrl() : string
364
    {
365
        return $this->getPageUrl($this->getFirstPage());
17✔
366
    }
367

368
    #[Pure]
369
    public function getLastPage() : int
370
    {
371
        return $this->lastPage;
44✔
372
    }
373

374
    /**
375
     * @since 3.5
376
     *
377
     * @param int $lastPage
378
     *
379
     * @return static
380
     */
381
    protected function setLastPage(int $lastPage) : static
382
    {
383
        $this->lastPage = $lastPage;
44✔
384
        return $this;
44✔
385
    }
386

387
    public function getLastPageUrl() : string
388
    {
389
        return $this->getPageUrl($this->getLastPage());
19✔
390
    }
391

392
    #[Pure]
393
    public function getPreviousPage() : ?int
394
    {
395
        return $this->previousPage;
32✔
396
    }
397

398
    /**
399
     * @since 3.5
400
     *
401
     * @param int $previousPage
402
     *
403
     * @return static
404
     */
405
    protected function setPreviousPage(int $previousPage) : static
406
    {
407
        $this->previousPage = $previousPage;
26✔
408
        return $this;
26✔
409
    }
410

411
    public function getPreviousPageUrl() : ?string
412
    {
413
        return $this->getPageUrl($this->getPreviousPage());
27✔
414
    }
415

416
    #[Pure]
417
    public function getNextPage() : ?int
418
    {
419
        return $this->nextPage;
32✔
420
    }
421

422
    /**
423
     * @since 3.5
424
     *
425
     * @param int $nextPage
426
     *
427
     * @return static
428
     */
429
    protected function setNextPage(int $nextPage) : static
430
    {
431
        $this->nextPage = $nextPage;
44✔
432
        return $this;
44✔
433
    }
434

435
    public function getNextPageUrl() : ?string
436
    {
437
        return $this->getPageUrl($this->getNextPage());
29✔
438
    }
439

440
    /**
441
     * @return array<int,string>
442
     */
443
    public function getPreviousPagesUrls() : array
444
    {
445
        $urls = [];
16✔
446
        if ($this->currentPage > 1 && $this->currentPage <= $this->lastPage) {
16✔
447
            $range = \range($this->currentPage - $this->surround, $this->currentPage - 1);
13✔
448
            foreach ($range as $page) {
13✔
449
                if ($page < 1) {
13✔
450
                    continue;
1✔
451
                }
452
                $urls[$page] = $this->getPageUrl($page);
13✔
453
            }
454
        }
455
        return $urls;
16✔
456
    }
457

458
    /**
459
     * @return array<int,string>
460
     */
461
    public function getNextPagesUrls() : array
462
    {
463
        $urls = [];
15✔
464
        if ($this->currentPage < $this->lastPage) {
15✔
465
            $range = \range($this->currentPage + 1, $this->currentPage + $this->surround);
14✔
466
            foreach ($range as $page) {
14✔
467
                if ($page > $this->lastPage) {
14✔
468
                    break;
1✔
469
                }
470
                $urls[$page] = $this->getPageUrl($page);
14✔
471
            }
472
        }
473
        return $urls;
15✔
474
    }
475

476
    /**
477
     * @return array<string,int|null>
478
     */
479
    #[ArrayShape([
480
        'self' => 'int',
481
        'first' => 'int',
482
        'prev' => 'int|null',
483
        'next' => 'int|null',
484
        'last' => 'int',
485
    ])]
486
    #[Pure]
487
    public function get() : array
488
    {
489
        return [
1✔
490
            'self' => $this->getCurrentPage(),
1✔
491
            'first' => $this->getFirstPage(),
1✔
492
            'prev' => $this->getPreviousPage(),
1✔
493
            'next' => $this->getNextPage(),
1✔
494
            'last' => $this->getLastPage(),
1✔
495
        ];
1✔
496
    }
497

498
    /**
499
     * @return array<string,string|null>
500
     */
501
    #[ArrayShape([
502
        'self' => 'string',
503
        'first' => 'string',
504
        'prev' => 'string|null',
505
        'next' => 'string|null',
506
        'last' => 'string',
507
    ])]
508
    public function getWithUrl() : array
509
    {
510
        return [
2✔
511
            'self' => $this->getCurrentPageUrl(),
2✔
512
            'first' => $this->getFirstPageUrl(),
2✔
513
            'prev' => $this->getPreviousPageUrl(),
2✔
514
            'next' => $this->getNextPageUrl(),
2✔
515
            'last' => $this->getLastPageUrl(),
2✔
516
        ];
2✔
517
    }
518

519
    /**
520
     * @param string|null $view
521
     *
522
     * @return string
523
     */
524
    public function render(string $view = null) : string
525
    {
526
        $filename = $this->getView($view ?? $this->getDefaultView());
26✔
527
        \ob_start();
26✔
528
        Isolation::require($filename, ['pager' => $this]);
26✔
529
        return (string) \ob_get_clean();
26✔
530
    }
531

532
    public function renderShort() : string
533
    {
534
        $view = $this->getDefaultView();
1✔
535
        if ( ! \str_ends_with($view, '-short')) {
1✔
536
            $view .= '-short';
1✔
537
        }
538
        return $this->render($view);
1✔
539
    }
540

541
    public function setDefaultView(string $defaultView) : static
542
    {
543
        if ( ! \array_key_exists($defaultView, $this->views)) {
2✔
544
            throw new LogicException(
1✔
545
                'Default view "' . $defaultView . '" is not a valid value'
1✔
546
            );
1✔
547
        }
548
        $this->defaultView = $defaultView;
2✔
549
        return $this;
2✔
550
    }
551

552
    #[Pure]
553
    public function getDefaultView() : string
554
    {
555
        return $this->defaultView;
4✔
556
    }
557

558
    /**
559
     * @return array<string,string|null>
560
     */
561
    #[ArrayShape([
562
        'self' => 'string',
563
        'first' => 'string',
564
        'prev' => 'string|null',
565
        'next' => 'string|null',
566
        'last' => 'string',
567
    ])]
568
    public function jsonSerialize() : array
569
    {
570
        return $this->getWithUrl();
1✔
571
    }
572

573
    /**
574
     * @param int|string $number
575
     *
576
     * @return int
577
     *
578
     * @deprecated Use sanitize method
579
     *
580
     * @codeCoverageIgnore
581
     */
582
    #[Deprecated(
583
        reason: 'since version 3.1, use sanitize() instead',
584
        replacement: '%class%::sanitize(%parameter0%)'
585
    )]
586
    public static function sanitizePageNumber(int | string $number) : int
587
    {
588
        \trigger_error(
589
            'Method ' . __METHOD__ . ' is deprecated',
590
            \E_USER_DEPRECATED
591
        );
592
        $number = $number < 1 || ! \is_numeric($number) ? 1 : $number;
593
        return $number > \PHP_INT_MAX ? \PHP_INT_MAX : (int) $number;
594
    }
595

596
    /**
597
     * Sanitize a number.
598
     *
599
     * @param mixed $number
600
     *
601
     * @return int
602
     */
603
    #[Pure]
604
    public static function sanitize(mixed $number) : int
605
    {
606
        if ( ! \is_numeric($number)) {
44✔
607
            return 1;
1✔
608
        }
609
        if ($number < 1) {
44✔
610
            return 1;
44✔
611
        }
612
        return $number >= \PHP_INT_MAX ? \PHP_INT_MAX : (int) $number;
44✔
613
    }
614
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc