1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* @author Gerard van Helden <[email protected]> |
4
|
|
|
* @copyright Zicht Online <http://zicht.nl> |
5
|
|
|
*/ |
6
|
|
|
namespace Zicht\Bundle\FrameworkExtraBundle\Pager; |
7
|
|
|
|
8
|
|
|
/** |
9
|
|
|
* Pager implementation to handle paging over a countable set of elements |
10
|
|
|
*/ |
11
|
|
|
class Pager implements \Iterator, \ArrayAccess, \Countable |
12
|
|
|
{ |
13
|
|
|
private $currentPage; |
14
|
|
|
private $total; |
15
|
|
|
private $numPages; |
16
|
|
|
private $offset; |
17
|
|
|
private $lengthOfRange; |
18
|
|
|
private $itemsPerPage; |
19
|
|
|
private $results; |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* Used for iterator implementation |
23
|
|
|
* |
24
|
|
|
* @var null |
25
|
|
|
*/ |
26
|
|
|
private $ptr = null; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* Constructs the pager with the given set of elements to page over, and the given amount of items per page. |
30
|
|
|
* |
31
|
|
|
* @param \Zicht\Bundle\FrameworkExtraBundle\Pager\Pageable $pagable |
32
|
|
|
* @param int $itemsPerPage |
33
|
|
|
*/ |
34
|
|
|
public function __construct(Pageable $pagable, $itemsPerPage) |
35
|
|
|
{ |
36
|
|
|
$this->currentPage = -1; |
37
|
|
|
$this->total = null; |
38
|
|
|
$this->numPages = -1; |
39
|
|
|
$this->offset = -1; |
40
|
|
|
$this->lengthOfRange = -1; |
41
|
|
|
$this->itemsPerPage = -1; |
42
|
|
|
$this->results = null; |
43
|
|
|
|
44
|
|
|
$this->results = $pagable; |
45
|
|
|
$this->setItemsPerPage($itemsPerPage); |
46
|
|
|
} |
47
|
|
|
|
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Sets the maximum number of items per page |
51
|
|
|
* |
52
|
|
|
* @param int $itemsPerPage |
53
|
|
|
* @return void |
54
|
|
|
* @throws \InvalidArgumentException if the number of items is not a valid non-negative integer |
55
|
|
|
*/ |
56
|
|
|
public function setItemsPerPage($itemsPerPage) |
57
|
|
|
{ |
58
|
|
|
if ($itemsPerPage <= 0) { |
59
|
|
|
throw new \InvalidArgumentException("Number of items per page must be positive integer"); |
60
|
|
|
} |
61
|
|
|
$this->itemsPerPage = $itemsPerPage; |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* Set the current page index, which is 0-index based. |
67
|
|
|
* (The first page is 0) |
68
|
|
|
* |
69
|
|
|
* If the page index format is invalid, an InvalidArgumentException is thrown. |
70
|
|
|
* If the page index is out of range, it is trimmed to the nearest logical value; e.g. -1 is interpreted as 0, |
71
|
|
|
* 15 is interpreted as 7 if the number of pages is 8. |
72
|
|
|
* |
73
|
|
|
* @param int $page |
74
|
|
|
* @return void |
75
|
|
|
* |
76
|
|
|
* @throws \InvalidArgumentException |
77
|
|
|
*/ |
78
|
|
|
public function setCurrentPage($page) |
79
|
|
|
{ |
80
|
|
|
if (is_null($this->total)) { |
81
|
|
|
$this->total = (int)$this->results->getTotal(); |
|
|
|
|
82
|
|
|
} |
83
|
|
|
if ((int)$page != $page) { |
84
|
|
|
throw new \InvalidArgumentException( |
85
|
|
|
"Invalid argument \$page, expected integer number, got " . gettype($page) |
86
|
|
|
); |
87
|
|
|
} |
88
|
|
|
$this->numPages = (int)ceil($this->total / $this->itemsPerPage); |
89
|
|
|
$this->currentPage = min(max(0, $this->getLast()), max($this->getFirst(), $page)); |
90
|
|
|
|
91
|
|
|
$this->offset = $this->itemsPerPage * $this->currentPage; |
92
|
|
|
$this->lengthOfRange = $this->itemsPerPage; |
93
|
|
|
|
94
|
|
|
if ($this->offset + $this->lengthOfRange > $this->total) { |
95
|
|
|
$this->lengthOfRange = max(0, $this->total - $this->offset); |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
$this->results->setRange($this->offset, $this->lengthOfRange); |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* Returns the first page index |
104
|
|
|
* |
105
|
|
|
* @return int |
106
|
|
|
*/ |
107
|
|
|
public function getFirst() |
108
|
|
|
{ |
109
|
|
|
return 0; |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* Returns the last page index |
115
|
|
|
* |
116
|
|
|
* @return int |
117
|
|
|
*/ |
118
|
|
|
public function getLast() |
119
|
|
|
{ |
120
|
|
|
return $this->numPages - 1; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* Returns the 1-indexed start of the displayed range, used for displaying in templates |
125
|
|
|
* |
126
|
|
|
* @return int |
127
|
|
|
*/ |
128
|
|
|
public function getRangeStart() |
129
|
|
|
{ |
130
|
|
|
return $this->offset + 1; |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* Returns the 1-indexed end of the displayed range, used for displaying in templates |
136
|
|
|
* |
137
|
|
|
* @return int |
138
|
|
|
*/ |
139
|
|
|
public function getRangeEnd() |
140
|
|
|
{ |
141
|
|
|
return $this->offset + $this->lengthOfRange; |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* Combines the getRangeStart() and getRangeEnd() in one array |
147
|
|
|
* |
148
|
|
|
* @return array |
149
|
|
|
*/ |
150
|
|
|
public function getRange() |
151
|
|
|
{ |
152
|
|
|
return array($this->getRangeStart(), $this->getRangeEnd()); |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* Returns the total of entire pageable set |
158
|
|
|
* |
159
|
|
|
* @return null |
160
|
|
|
*/ |
161
|
|
|
public function getItemTotal() |
162
|
|
|
{ |
163
|
|
|
return $this->total; |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* Returns whether the current page has a previous. This is only true for pages past the first. |
169
|
|
|
* |
170
|
|
|
* @return bool |
171
|
|
|
*/ |
172
|
|
|
public function hasPrevious() |
173
|
|
|
{ |
174
|
|
|
return $this->currentPage > $this->getFirst(); |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* Returns whether the current page has a next. This is only true for pages before the last |
180
|
|
|
* |
181
|
|
|
* @return bool |
182
|
|
|
*/ |
183
|
|
|
public function hasNext() |
184
|
|
|
{ |
185
|
|
|
return $this->offsetExists($this->currentPage + 1); |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* Returns a set of meta information on the current page. |
191
|
|
|
* See itemAt() for the available information |
192
|
|
|
* |
193
|
|
|
* @return array |
194
|
|
|
*/ |
195
|
|
|
public function getCurrent() |
196
|
|
|
{ |
197
|
|
|
return $this->itemAt($this->currentPage); |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* Returns the meta data for the next page, and 'null' if there is none |
203
|
|
|
* |
204
|
|
|
* @return array|null |
205
|
|
|
*/ |
206
|
|
|
public function getNext() |
207
|
|
|
{ |
208
|
|
|
if ($this->hasNext()) { |
209
|
|
|
return $this->itemAt($this->currentPage + 1); |
210
|
|
|
} |
211
|
|
|
return null; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* Returns the meta data for the previous page, and 'null' if there is none |
217
|
|
|
* |
218
|
|
|
* @return array|null |
219
|
|
|
*/ |
220
|
|
|
public function getPrevious() |
221
|
|
|
{ |
222
|
|
|
if ($this->hasPrevious()) { |
223
|
|
|
return $this->itemAt($this->currentPage - 1); |
224
|
|
|
} |
225
|
|
|
return null; |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
|
229
|
|
|
/** |
230
|
|
|
* Meta data helper function, returns the following meta data for each of the requested pages. |
231
|
|
|
* |
232
|
|
|
* - title: The displayable title for the current page (e.g. "1" for page 0) |
233
|
|
|
* - is_previous: Whether the page is the previous page |
234
|
|
|
* - is_current |
235
|
|
|
* - is_next: Whether the page is the next page |
236
|
|
|
* |
237
|
|
|
* @param int $i |
238
|
|
|
* @return array |
239
|
|
|
*/ |
240
|
|
|
private function itemAt($i) |
241
|
|
|
{ |
242
|
|
|
return array( |
243
|
|
|
'index' => $i, |
244
|
|
|
'title' => $i + 1, |
245
|
|
|
'is_previous' => $i == ($this->currentPage - 1), |
246
|
|
|
'is_current' => $i == $this->currentPage, |
247
|
|
|
'is_next' => $i == ($this->currentPage + 1) |
248
|
|
|
); |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
/** |
252
|
|
|
* Iterator::current() implementation |
253
|
|
|
* Returns the meta data for the current item in the iterator. |
254
|
|
|
* |
255
|
|
|
* @return array |
256
|
|
|
*/ |
257
|
|
|
public function current() |
258
|
|
|
{ |
259
|
|
|
return $this->itemAt($this->ptr); |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
|
263
|
|
|
/** |
264
|
|
|
* Iterator::next() implementation, advances the iterator one item. |
265
|
|
|
* |
266
|
|
|
* @return void |
267
|
|
|
*/ |
268
|
|
|
public function next() |
269
|
|
|
{ |
270
|
|
|
$this->ptr++; |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
|
274
|
|
|
/** |
275
|
|
|
* Returns the key of the current Iterator item, which is the page index. |
276
|
|
|
* |
277
|
|
|
* @return int |
278
|
|
|
*/ |
279
|
|
|
public function key() |
280
|
|
|
{ |
281
|
|
|
return $this->ptr; |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
|
285
|
|
|
/** |
286
|
|
|
* Iterator::valid() implementation, checks if the current iterator index is valid |
287
|
|
|
* |
288
|
|
|
* @return bool |
289
|
|
|
*/ |
290
|
|
|
public function valid() |
291
|
|
|
{ |
292
|
|
|
return $this->offsetExists($this->ptr); |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
|
296
|
|
|
/** |
297
|
|
|
* Iterator::rewind() implementation; rewinds the iterator to the start of the range |
298
|
|
|
* |
299
|
|
|
* @return void |
300
|
|
|
*/ |
301
|
|
|
public function rewind() |
302
|
|
|
{ |
303
|
|
|
$this->ptr = $this->getFirst(); |
|
|
|
|
304
|
|
|
} |
305
|
|
|
|
306
|
|
|
|
307
|
|
|
/** |
308
|
|
|
* ArrayAccess:offsetExists() implementation, checks if the given page index is valid. |
309
|
|
|
* |
310
|
|
|
* @param int $offset |
311
|
|
|
* @return bool |
312
|
|
|
*/ |
313
|
|
|
public function offsetExists($offset) |
314
|
|
|
{ |
315
|
|
|
return is_int($offset) && $offset >= $this->getFirst() && $offset <= $this->getLast(); |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
|
319
|
|
|
/** |
320
|
|
|
* ArrayAccess::offsetGet() implementation; returns the meta data for the given page index, and null |
321
|
|
|
* if it does not exist. |
322
|
|
|
* |
323
|
|
|
* @param int $offset |
324
|
|
|
* @return array |
325
|
|
|
*/ |
326
|
|
|
public function offsetGet($offset) |
327
|
|
|
{ |
328
|
|
|
if ($this->offsetExists($offset)) { |
329
|
|
|
return $this->itemAt($offset); |
330
|
|
|
} |
331
|
|
|
return null; |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
|
335
|
|
|
/** |
336
|
|
|
* ArrayAccess::offsetSet() implementation, throws an exception as the page set is read only |
337
|
|
|
* |
338
|
|
|
* @param int $offset |
339
|
|
|
* @param mixed $value |
340
|
|
|
* @return void |
341
|
|
|
* |
342
|
|
|
* @throws \BadMethodCallException |
343
|
|
|
*/ |
344
|
|
|
public function offsetSet($offset, $value) |
345
|
|
|
{ |
346
|
|
|
throw new \BadMethodCallException(__CLASS__ . ' is read only'); |
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
|
350
|
|
|
/** |
351
|
|
|
* ArrayAccess::offsetUnset() implementation, throws an exception as the page set is read only |
352
|
|
|
* |
353
|
|
|
* @param int $offset |
354
|
|
|
* @return void |
355
|
|
|
* |
356
|
|
|
* @throws \BadMethodCallException |
357
|
|
|
*/ |
358
|
|
|
public function offsetUnset($offset) |
359
|
|
|
{ |
360
|
|
|
throw new \BadMethodCallException(__CLASS__ . ' is read only'); |
361
|
|
|
} |
362
|
|
|
|
363
|
|
|
|
364
|
|
|
/** |
365
|
|
|
* Countable::count() implementation; Returns the number of pages in the page set. |
366
|
|
|
* |
367
|
|
|
* @return int |
368
|
|
|
*/ |
369
|
|
|
public function count() |
370
|
|
|
{ |
371
|
|
|
return $this->numPages; |
372
|
|
|
} |
373
|
|
|
|
374
|
|
|
/** |
375
|
|
|
* @return int |
376
|
|
|
*/ |
377
|
|
|
public function getItemsPerPage() |
378
|
|
|
{ |
379
|
|
|
return $this->itemsPerPage; |
380
|
|
|
} |
381
|
|
|
|
382
|
|
|
/** |
383
|
|
|
* With gaps |
384
|
|
|
* |
385
|
|
|
* @param int $surround |
386
|
|
|
* @return array |
387
|
|
|
*/ |
388
|
|
|
public function withGaps($surround = 2) |
389
|
|
|
{ |
390
|
|
|
$ret = []; |
391
|
|
|
$isPreviousGap = false; |
392
|
|
|
for ($i = 0; $i < $this->numPages; $i++) { |
393
|
|
|
if (($i >= $this->currentPage - $surround && $i <= $this->currentPage + $surround) |
394
|
|
|
|| ($i < $this->getFirst() + $surround) |
395
|
|
|
|| ($i > $this->getLast() - $surround) |
396
|
|
|
) { |
397
|
|
|
$ret[$i] = $this->itemAt($i); |
398
|
|
|
$isPreviousGap = false; |
399
|
|
|
} elseif (!$isPreviousGap) { |
400
|
|
|
$isPreviousGap = true; |
401
|
|
|
$ret[$i]= null; |
402
|
|
|
} |
403
|
|
|
} |
404
|
|
|
|
405
|
|
|
return $ret; |
406
|
|
|
} |
407
|
|
|
|
408
|
|
|
/** |
409
|
|
|
* @return int |
410
|
|
|
*/ |
411
|
|
|
public function getCurrentPageIndex() |
412
|
|
|
{ |
413
|
|
|
return $this->currentPage; |
414
|
|
|
} |
415
|
|
|
} |
416
|
|
|
|
This check looks for function or method calls that always return null and whose return value is used.
The method
getObject()
can return nothing but null, so it makes no sense to use the return value.The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.