Completed
Pull Request — master (#242)
by
unknown
12:44
created

Pagerfanta::getPageNumberForItemAtPosition()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

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