Completed
Pull Request — master (#276)
by
unknown
02:59
created

Pagerfanta::getOffset()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
/*
4
 * This file is part of the Pagerfanta package.
5
 *
6
 * (c) Pablo Díez <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Pagerfanta;
13
14
use OutOfBoundsException;
15
use Pagerfanta\Adapter\AdapterInterface;
16
use Pagerfanta\Exception\LogicException;
17
use Pagerfanta\Exception\NotBooleanException;
18
use Pagerfanta\Exception\NotIntegerException;
19
use Pagerfanta\Exception\NotIntegerMaxPerPageException;
20
use Pagerfanta\Exception\LessThan1MaxPerPageException;
21
use Pagerfanta\Exception\NotIntegerCurrentPageException;
22
use Pagerfanta\Exception\LessThan1CurrentPageException;
23
use Pagerfanta\Exception\OutOfRangeCurrentPageException;
24
25
/**
26
 * Represents a paginator.
27
 *
28
 * @author Pablo Díez <[email protected]>
29
 */
30
class Pagerfanta implements \Countable, \IteratorAggregate, \JsonSerializable, PagerfantaInterface
0 ignored issues
show
Deprecated Code introduced by
The interface Pagerfanta\PagerfantaInterface has been deprecated.

This class, trait or interface has been deprecated.

Loading history...
31
{
32
    private $adapter;
33
    private $allowOutOfRangePages;
34
    private $normalizeOutOfRangePages;
35
    private $maxPerPage;
36
    private $currentPage;
37
    private $nbResults;
38
    private $currentPageResults;
39
    private $offset = 0;
40
41
    /**
42
     * @param AdapterInterface $adapter An adapter.
43
     */
44 144
    public function __construct(AdapterInterface $adapter)
45
    {
46 144
        $this->adapter = $adapter;
47 144
        $this->allowOutOfRangePages = false;
48 144
        $this->normalizeOutOfRangePages = false;
49 144
        $this->maxPerPage = 10;
50 144
        $this->currentPage = 1;
51 144
    }
52
53
    /**
54
     * Returns the adapter.
55
     *
56
     * @return AdapterInterface The adapter.
57
     */
58 88
    public function getAdapter()
59
    {
60 88
        return $this->adapter;
61
    }
62
63
    /**
64
     * Sets whether or not allow out of range pages.
65
     *
66
     * @param boolean $value
67
     *
68
     * @return self
69
     */
70 10
    public function setAllowOutOfRangePages($value)
71
    {
72 10
        $this->allowOutOfRangePages = $this->filterBoolean($value);
73
74 7
        return $this;
75
    }
76
77
    /**
78
     * Returns whether or not allow out of range pages.
79
     *
80
     * @return boolean
81
     */
82 89
    public function getAllowOutOfRangePages()
83
    {
84 89
        return $this->allowOutOfRangePages;
85
    }
86
87
    /**
88
     * Sets whether or not normalize out of range pages.
89
     *
90
     * @param boolean $value
91
     *
92
     * @return self
93
     */
94 8
    public function setNormalizeOutOfRangePages($value)
95
    {
96 8
        $this->normalizeOutOfRangePages = $this->filterBoolean($value);
97
98 5
        return $this;
99
    }
100
101
    /**
102
     * Returns whether or not normalize out of range pages.
103
     *
104
     * @return boolean
105
     */
106 6
    public function getNormalizeOutOfRangePages()
107
    {
108 6
        return $this->normalizeOutOfRangePages;
109
    }
110
111 16
    private function filterBoolean($value)
112
    {
113 16
        if (!is_bool($value)) {
114 6
            throw new NotBooleanException();
115
        }
116
117 10
        return $value;
118
    }
119
120
    /**
121
     * Sets the max per page.
122
     *
123
     * Tries to convert from string and float.
124
     *
125
     * @param integer $maxPerPage
126
     *
127
     * @return self
128
     *
129
     * @throws NotIntegerMaxPerPageException If the max per page is not an integer even converting.
130
     * @throws LessThan1MaxPerPageException  If the max per page is less than 1.
131
     */
132 54
    public function setMaxPerPage($maxPerPage)
133
    {
134 54
        $this->maxPerPage = $this->filterMaxPerPage($maxPerPage);
135 48
        $this->resetForMaxPerPageChange();
136
137 48
        return $this;
138
    }
139
140 54
    private function filterMaxPerPage($maxPerPage)
141
    {
142 54
        $maxPerPage = $this->toInteger($maxPerPage);
143 54
        $this->checkMaxPerPage($maxPerPage);
144
145 48
        return $maxPerPage;
146
    }
147
148 54
    private function checkMaxPerPage($maxPerPage)
149
    {
150 54
        if (!is_int($maxPerPage)) {
151 4
            throw new NotIntegerMaxPerPageException();
152
        }
153
154 50
        if ($maxPerPage < 1) {
155 2
            throw new LessThan1MaxPerPageException();
156
        }
157 48
    }
158
159 48
    private function resetForMaxPerPageChange()
160
    {
161 48
        $this->currentPageResults = null;
162 48
        $this->nbResults = null;
163 48
    }
164
165
    /**
166
     * Returns the max per page.
167
     *
168
     * @return integer
169
     */
170 97
    public function getMaxPerPage()
171
    {
172 97
        return $this->maxPerPage;
173
    }
174
175
    /**
176
     * Sets the current page.
177
     *
178
     * Tries to convert from string and float.
179
     *
180
     * @param integer $currentPage
181
     *
182
     * @return self
183
     *
184
     * @throws NotIntegerCurrentPageException If the current page is not an integer even converting.
185
     * @throws LessThan1CurrentPageException  If the current page is less than 1.
186
     * @throws OutOfRangeCurrentPageException If It is not allowed out of range pages and they are not normalized.
187
     */
188 92
    public function setCurrentPage($currentPage)
189
    {
190 92
        $this->useDeprecatedCurrentPageBooleanArguments(func_get_args());
191
192 92
        $this->currentPage = $this->filterCurrentPage($currentPage);
193 85
        $this->resetForCurrentPageChange();
194
195 85
        return $this;
196
    }
197
198 92
    private function useDeprecatedCurrentPageBooleanArguments($arguments)
199
    {
200 92
        $this->useDeprecatedCurrentPageAllowOutOfRangePagesBooleanArgument($arguments);
201 92
        $this->useDeprecatedCurrentPageNormalizeOutOfRangePagesBooleanArgument($arguments);
202 92
    }
203
204 92
    private function useDeprecatedCurrentPageAllowOutOfRangePagesBooleanArgument($arguments)
205
    {
206 92
        $index = 1;
207 92
        $method = 'setAllowOutOfRangePages';
208
209 92
        $this->useDeprecatedBooleanArgument($arguments, $index, $method);
210 92
    }
211
212 92
    private function useDeprecatedCurrentPageNormalizeOutOfRangePagesBooleanArgument($arguments)
213
    {
214 92
        $index = 2;
215 92
        $method = 'setNormalizeOutOfRangePages';
216
217 92
        $this->useDeprecatedBooleanArgument($arguments, $index, $method);
218 92
    }
219
220 92
    private function useDeprecatedBooleanArgument($arguments, $index, $method)
221
    {
222 92
        if (isset($arguments[$index])) {
223 2
            $this->$method($arguments[$index]);
224
        }
225 92
    }
226
227 92
    private function filterCurrentPage($currentPage)
228
    {
229 92
        $currentPage = $this->toInteger($currentPage);
230 92
        $this->checkCurrentPage($currentPage);
231 86
        $currentPage = $this->filterOutOfRangeCurrentPage($currentPage);
232
233 85
        return $currentPage;
234
    }
235
236 92
    private function checkCurrentPage($currentPage)
237
    {
238 92
        if (!is_int($currentPage)) {
239 4
            throw new NotIntegerCurrentPageException();
240
        }
241
242 88
        if ($currentPage < 1) {
243 2
            throw new LessThan1CurrentPageException();
244
        }
245 86
    }
246
247 86
    private function filterOutOfRangeCurrentPage($currentPage)
248
    {
249 86
        if ($this->notAllowedCurrentPageOutOfRange($currentPage)) {
250 3
            return $this->normalizeOutOfRangeCurrentPage($currentPage);
251
        }
252
253 83
        return $currentPage;
254
    }
255
256 86
    private function notAllowedCurrentPageOutOfRange($currentPage)
257
    {
258 86
        return !$this->getAllowOutOfRangePages() &&
259 86
               $this->currentPageOutOfRange($currentPage);
260
    }
261
262 84
    private function currentPageOutOfRange($currentPage)
263
    {
264 84
        return $currentPage > 1 && $currentPage > $this->getNbPages();
265
    }
266
267
    /**
268
     * @param int $currentPage
269
     *
270
     * @return int
271
     *
272
     * @throws OutOfRangeCurrentPageException If the page should not be normalized
273
     */
274 3
    private function normalizeOutOfRangeCurrentPage($currentPage)
275
    {
276 3
        if ($this->getNormalizeOutOfRangePages()) {
277 2
            return $this->getNbPages();
278
        }
279
280 1
        throw new OutOfRangeCurrentPageException(sprintf('Page "%d" does not exist. The currentPage must be inferior to "%d"', $currentPage, $this->getNbPages()));
281
    }
282
283 85
    private function resetForCurrentPageChange()
284
    {
285 85
        $this->currentPageResults = null;
286 85
    }
287
288
    /**
289
     * Returns the current page.
290
     *
291
     * @return integer
292
     */
293 82
    public function getCurrentPage()
294
    {
295 82
        return $this->currentPage;
296
    }
297
298
    /**
299
     * Returns the results for the current page.
300
     *
301
     * @return array|\Traversable
302
     */
303 18
    public function getCurrentPageResults()
304
    {
305 18
        if ($this->notCachedCurrentPageResults()) {
306 18
            $this->currentPageResults = $this->getCurrentPageResultsFromAdapter();
307
        }
308
309 18
        return $this->currentPageResults;
310
    }
311
312 18
    private function notCachedCurrentPageResults()
313
    {
314 18
        return $this->currentPageResults === null;
315
    }
316
317 18
    private function getCurrentPageResultsFromAdapter()
318
    {
319 18
        $offset = $this->calculateOffsetForCurrentPageResults();
320 18
        $length = $this->getMaxPerPage();
321
322 18
        return $this->adapter->getSlice($offset, $length);
323
    }
324
325 19
    private function calculateOffsetForCurrentPageResults()
326
    {
327 19
        return (($this->getCurrentPage() - 1) * $this->getMaxPerPage()) + $this->getOffset();
328
    }
329
330
    /**
331
     * Calculates the current page offset start
332
     *
333
     * @return int
334
     */
335 2
    public function getCurrentPageOffsetStart()
336
    {
337 2
        return $this->getNbResults() ?
338 1
               $this->calculateOffsetForCurrentPageResults() + 1 :
339 2
               0;
340
    }
341
342
    /**
343
     * Calculates the current page offset end
344
     *
345
     * @return int
346
     */
347 2
    public function getCurrentPageOffsetEnd()
348
    {
349 2
        return $this->hasNextPage() ?
350 1
               $this->getCurrentPage() * $this->getMaxPerPage() :
351 2
               $this->getNbResults();
352
    }
353
354
    /**
355
     * Returns the number of results.
356
     *
357
     * @return integer
358
     */
359 87
    public function getNbResults()
360
    {
361 87
        if ($this->notCachedNbResults()) {
362 87
            $this->nbResults = $this->getAdapter()->getNbResults();
363
        }
364
365 87
        return $this->nbResults;
366
    }
367
368 87
    private function notCachedNbResults()
369
    {
370 87
        return $this->nbResults === null;
371
    }
372
373
    /**
374
     * Returns the number of pages.
375
     *
376
     * @return integer
377
     */
378 77
    public function getNbPages()
379
    {
380 77
        $nbPages = $this->calculateNbPages();
381
382 77
        if ($nbPages == 0) {
383 1
            return $this->minimumNbPages();
384
        }
385
386 76
        return $nbPages;
387
    }
388
389 77
    private function calculateNbPages()
390
    {
391 77
        return (int) ceil($this->getNbResults() / $this->getMaxPerPage());
392
    }
393
394 1
    private function minimumNbPages()
395
    {
396 1
        return 1;
397
    }
398
399
    /**
400
     * Returns if the number of results is higher than the max per page.
401
     *
402
     * @return boolean
403
     */
404 3
    public function haveToPaginate()
405
    {
406 3
        return $this->getNbResults() > $this->maxPerPage;
407
    }
408
409
    /**
410
     * Returns whether there is previous page or not.
411
     *
412
     * @return boolean
413
     */
414 56
    public function hasPreviousPage()
415
    {
416 56
        return $this->currentPage > 1;
417
    }
418
419
    /**
420
     * Returns the previous page.
421
     *
422
     * @return integer
423
     *
424
     * @throws LogicException If there is no previous page.
425
     */
426 43
    public function getPreviousPage()
427
    {
428 43
        if (!$this->hasPreviousPage()) {
429 1
            throw new LogicException('There is no previous page.');
430
        }
431
432 42
        return $this->currentPage - 1;
433
    }
434
435
    /**
436
     * Returns whether there is next page or not.
437
     *
438
     * @return boolean
439
     */
440 58
    public function hasNextPage()
441
    {
442 58
        return $this->currentPage < $this->getNbPages();
443
    }
444
445
    /**
446
     * Returns the next page.
447
     *
448
     * @return integer
449
     *
450
     * @throws LogicException If there is no next page.
451
     */
452 49
    public function getNextPage()
453
    {
454 49
        if (!$this->hasNextPage()) {
455 1
            throw new LogicException('There is no next page.');
456
        }
457
458 48
        return $this->currentPage + 1;
459
    }
460
461
    /**
462
     * Implements the \Countable interface.
463
     *
464
     * @return integer The number of results.
465
     */
466 1
    public function count()
467
    {
468 1
        return $this->getNbResults();
469
    }
470
471
    /**
472
     * Implements the \IteratorAggregate interface.
473
     *
474
     * @return \ArrayIterator instance with the current results.
475
     */
476 3
    public function getIterator()
477
    {
478 3
        $results = $this->getCurrentPageResults();
479
480 3
        if ($results instanceof \Iterator) {
481 1
            return $results;
482
        }
483
484 2
        if ($results instanceof \IteratorAggregate) {
485 1
            return $results->getIterator();
486
        }
487
488 1
        return new \ArrayIterator($results);
489
    }
490
491
    /**
492
     * Implements the \JsonSerializable interface.
493
     *
494
     * @return array current page results
495
     */
496 4
    public function jsonSerialize()
497
    {
498 4
        $results = $this->getCurrentPageResults();
499 4
        if ($results instanceof \Traversable) {
500 2
            return iterator_to_array($results);
501
        }
502
        
503 2
        return $results;
504
    }
505
506 114
    private function toInteger($value)
507
    {
508 114
        if ($this->needsToIntegerConversion($value)) {
509 6
            return (int) $value;
510
        }
511
512 111
        return $value;
513
    }
514
515 114
    private function needsToIntegerConversion($value)
516
    {
517 114
        return (is_string($value) || is_float($value)) && (int) $value == $value;
518
    }
519
520
    /**
521
     * Get page number of the item at specified position (1-based index)
522
     *
523
     * @param integer $position
524
     *
525
     * @return integer
526
     */
527 3
    public function getPageNumberForItemAtPosition($position)
528
    {
529 3
        if (!is_int($position)) {
530 1
            throw new NotIntegerException();
531
        }
532
533 2
        if ($this->getNbResults() < $position) {
534 1
            throw new OutOfBoundsException(sprintf(
535 1
                'Item requested at position %d, but there are only %d items.',
536 1
                $position,
537 1
                $this->getNbResults()
538
            ));
539
        }
540
541 1
        return (int) ceil($position/$this->getMaxPerPage());
542
    }
543
544
    /**
545
     * @return integer
546
     */
547 19
    public function getOffset()
548
    {
549 19
        return $this->offset;
550
    }
551
552
    /**
553
     * @param integer $offset
554
     *
555
     * @return Pagerfanta
556
     */
557 5
    public function setOffset($offset)
558
    {
559 5
        $this->offset = $offset;
560
561 5
        return $this;
562
    }
563
}
564