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

aplus-framework / pagination / 3769776998

pending completion
3769776998

push

github

Natan Felles
Update tests workflow

583 of 583 relevant lines covered (100.0%)

6.08 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 $this
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() : void
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
    }
301

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

594
    /**
595
     * Sanitize a number.
596
     *
597
     * @param mixed $number
598
     *
599
     * @return int
600
     */
601
    #[Pure]
602
    public static function sanitize(mixed $number) : int
603
    {
604
        if ( ! \is_numeric($number)) {
44✔
605
            return 1;
1✔
606
        }
607
        if ($number < 1) {
44✔
608
            return 1;
44✔
609
        }
610
        return $number >= \PHP_INT_MAX ? \PHP_INT_MAX : (int) $number;
44✔
611
    }
612
}
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