Issues (590)

src/Query/Pagination/Walker.php (2 issues)

1
<?php
2
3
namespace Bdf\Prime\Query\Pagination;
4
5
use BadMethodCallException;
6
use Bdf\Prime\Connection\ConnectionInterface;
7
use Bdf\Prime\Exception\PrimeException;
8
use Bdf\Prime\PrimeSerializable;
9
use Bdf\Prime\Query\Contract\Limitable;
10
use Bdf\Prime\Query\Contract\Orderable;
11
use Bdf\Prime\Query\Contract\Paginable;
12
use Bdf\Prime\Query\Contract\ReadOperation;
13
use Bdf\Prime\Query\Pagination\WalkStrategy\PaginationWalkStrategy;
14
use Bdf\Prime\Query\Pagination\WalkStrategy\WalkCursor;
15
use Bdf\Prime\Query\Pagination\WalkStrategy\WalkStrategyInterface;
16
use Bdf\Prime\Query\ReadCommandInterface;
17
use Iterator;
18
use LogicException;
19
20
/**
21
 * Query Walker
22
 *
23
 * Permet de parcourir des collections contenant de gros volume d'entités.
24
 * Le parcourt se fait par paquet d'entités définis par la limit de la query
25
 * Une fois la limite atteinte, la classe lance la requête suivante
26
 *
27
 * Attention, le walker ne gère pas les objects collection
28
 *
29
 * @template R as array|object
30
 *
31
 * @implements PaginatorInterface<R>
32
 * @implements Iterator<array-key, R>
33
 */
34
class Walker extends PrimeSerializable implements Iterator, PaginatorInterface
35
{
36
    public const DEFAULT_PAGE  = 1;
37
    public const DEFAULT_LIMIT = 150;
38
39
    /**
40
     * First page
41
     *
42
     * @var int
43
     */
44
    protected $startPage;
45
46
    /**
47
     * The current offset
48
     *
49
     * @var int|null
50
     */
51
    protected $offset;
52
53
    /**
54
     * @var R[]
55
     */
56
    private $collection = [];
57
58
    /**
59
     * @var WalkStrategyInterface<R>
60
     */
61
    private $strategy;
62
63
    /**
64
     * @var WalkCursor<R>
65
     */
66
    private $cursor;
67
68
    /**
69
     * @var ReadCommandInterface<ConnectionInterface, R>
70
     */
71
    private $query;
72
73
    /**
74
     * @var int
75
     */
76
    private $page;
77
78
    /**
79
     * @var int
80
     */
81
    private $maxRows;
82
83
    /**
84
     * Create a query walker
85
     *
86
     * @param ReadCommandInterface<ConnectionInterface, R> $query
87
     * @param int            $maxRows
88
     * @param int            $page
89
     */
90 30
    public function __construct(ReadCommandInterface $query, $maxRows = null, $page = null)
91
    {
92 30
        $this->query = $query;
93 30
        $this->page = 0;
94 30
        $this->maxRows = $maxRows ?: self::DEFAULT_LIMIT;
95 30
        $this->startPage = $page ?: self::DEFAULT_PAGE;
96
    }
97
98
    /**
99
     * Change the walk strategy
100
     *
101
     * @param WalkStrategyInterface<R> $strategy
102
     *
103
     * @return $this
104
     */
105 24
    public function setStrategy(WalkStrategyInterface $strategy): self
106
    {
107 24
        if ($this->cursor !== null) {
108
            throw new LogicException('Cannot change walk strategy during walk');
109
        }
110
111 24
        $this->strategy = $strategy;
112
113 24
        return $this;
114
    }
115
116
    /**
117
     * Get the current active walk strategy
118
     *
119
     * @return WalkStrategyInterface<R>
120
     */
121 27
    public function getStrategy(): WalkStrategyInterface
122
    {
123 27
        if ($this->strategy) {
124 20
            return $this->strategy;
125
        }
126
127
        /**
128
         * @var WalkStrategyInterface<R>
129
         * @psalm-suppress InvalidPropertyAssignmentValue
130
         */
131 8
        return $this->strategy = new PaginationWalkStrategy();
132
    }
133
134
    /**
135
     * Load the first page of collection
136
     *
137
     * @throws PrimeException
138
     *
139
     * @return void
140
     */
141
    #[ReadOperation]
142 26
    public function load(): void
143
    {
144 26
        $this->page = $this->startPage;
145 26
        $this->cursor = $this->getStrategy()->initialize($this->query, $this->maxRows, $this->page);
146 23
        $this->loadCollection();
147
    }
148
149
    /**
150
     * @return ReadCommandInterface<ConnectionInterface, R>
151
     */
152 2
    public function query(): ReadCommandInterface
153
    {
154 2
        if ($this->cursor) {
155 1
            return $this->cursor->query;
156
        }
157
158 1
        return $this->query;
159
    }
160
161
    /**
162
     * {@inheritdoc}
163
     */
164
    public function collection()
165
    {
166
        return $this->collection;
167
    }
168
169
    /**
170
     * {@inheritdoc}
171
     */
172 5
    public function size()
173
    {
174 5
        if (!$this->query instanceof Paginable) {
175
            throw new BadMethodCallException(__METHOD__.' should be called with a Paginable query');
176
        }
177
178 5
        return $this->query->paginationCount();
179
    }
180
181
    /**
182
     * {@inheritdoc}
183
     */
184
    public function order($attribute = null)
185
    {
186
        $query = $this->query();
187
188
        if (!$query instanceof Orderable) {
189
            return $attribute ? null : [];
0 ignored issues
show
Bug Best Practice introduced by
The expression return $attribute ? null : array() also could return the type array which is incompatible with the return type mandated by Bdf\Prime\Query\Paginati...natorInterface::order() of Bdf\Prime\Query\Contract\Orderable.
Loading history...
190
        }
191
192
        $orders = $query->getOrders();
193
194
        if ($attribute === null) {
195
            return $orders;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $orders returns the type array which is incompatible with the return type mandated by Bdf\Prime\Query\Paginati...natorInterface::order() of Bdf\Prime\Query\Contract\Orderable.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
196
        }
197
198
        return $orders[$attribute] ?? null;
199
    }
200
201
    /**
202
     * {@inheritdoc}
203
     */
204 3
    public function limit(): ?int
205
    {
206 3
        if ($this->cursor->query instanceof Limitable) {
207 3
            return $this->cursor->query->getLimit();
208
        }
209
210
        return 0;
211
    }
212
213
    /**
214
     * {@inheritdoc}
215
     */
216 1
    public function offset(): ?int
217
    {
218 1
        if ($this->cursor->query instanceof Limitable) {
219 1
            return $this->cursor->query->getOffset();
220
        }
221
222
        return 0;
223
    }
224
225
    /**
226
     * {@inheritdoc}
227
     */
228 3
    public function page()
229
    {
230 3
        return $this->page;
231
    }
232
233
    /**
234
     * {@inheritdoc}
235
     */
236 3
    public function pageMaxRows()
237
    {
238 3
        return $this->maxRows;
239
    }
240
241
    /**
242
     * {@inheritdoc}
243
     */
244 23
    protected function loadCollection(): void
245
    {
246 23
        $this->cursor = $this->strategy->next($this->cursor);
247 23
        $this->collection = $this->cursor->entities;
248
249
        // Test if the collection has numerical keys.
250
        // We have to add the offset to the numerical key.
251 23
        if (isset($this->collection[0])) {
252 19
            $this->offset = ($this->page - $this->startPage) * $this->maxRows;
253
        } else {
254 20
            $this->offset = null;
255
        }
256
    }
257
258
    /**
259
     * SPL - Iterator
260
     *
261
     * {@inheritdoc}
262
     */
263
    #[\ReturnTypeWillChange]
264 19
    public function current()
265
    {
266 19
        return current($this->collection);
267
    }
268
269
    /**
270
     * SPL - Iterator
271
     *
272
     * {@inheritdoc}
273
     */
274
    #[\ReturnTypeWillChange]
275 14
    public function key()
276
    {
277 14
        if ($this->offset !== null) {
278
            /** @var array<int, mixed> $this->collection */
279 11
            return $this->offset + key($this->collection);
280
        }
281
282 3
        return key($this->collection);
283
    }
284
285
    /**
286
     * SPL - Iterator
287
     *
288
     * {@inheritdoc}
289
     *
290
     * @throws PrimeException
291
     */
292
    #[ReadOperation]
293 19
    public function next(): void
294
    {
295 19
        if (false === next($this->collection)) {
296 19
            $this->page++;
297 19
            $this->loadCollection();
298
        }
299
    }
300
301
    /**
302
     * SPL - Iterator
303
     *
304
     * {@inheritdoc}
305
     */
306 19
    public function valid(): bool
307
    {
308 19
        return false !== current($this->collection);
309
    }
310
311
    /**
312
     * SPL - Iterator
313
     *
314
     * {@inheritdoc}
315
     *
316
     * @throws PrimeException
317
     */
318
    #[ReadOperation]
319 22
    public function rewind(): void
320
    {
321 22
        if (($this->page == $this->startPage) && count($this->collection)) {
322
            reset($this->collection);
323
        } else {
324 22
            $this->load();
325
        }
326
    }
327
328
    /**
329
     * {@inheritdoc}
330
     */
331 3
    public function count(): int
332
    {
333 3
        return count($this->collection);
334
    }
335
336
    /**
337
     * {@inheritdoc}
338
     */
339
    public function pushAll(array $items)
340
    {
341
        throw new BadMethodCallException('Collection methods are not supported by the Walker');
342
    }
343
344
    /**
345
     * {@inheritdoc}
346
     */
347
    public function push($item)
348
    {
349
        throw new BadMethodCallException('Collection methods are not supported by the Walker');
350
    }
351
352
    /**
353
     * {@inheritdoc}
354
     */
355
    public function put($key, $item)
356
    {
357
        throw new BadMethodCallException('Collection methods are not supported by the Walker');
358
    }
359
360
    /**
361
     * {@inheritdoc}
362
     */
363
    public function all()
364
    {
365
        throw new BadMethodCallException('Collection methods are not supported by the Walker');
366
    }
367
368
    /**
369
     * {@inheritdoc}
370
     */
371
    public function get($key, $default = null)
372
    {
373
        throw new BadMethodCallException('Collection methods are not supported by the Walker');
374
    }
375
376
    /**
377
     * {@inheritdoc}
378
     */
379
    public function has($key)
380
    {
381
        throw new BadMethodCallException('Collection methods are not supported by the Walker');
382
    }
383
384
    /**
385
     * {@inheritdoc}
386
     */
387
    public function remove($key)
388
    {
389
        throw new BadMethodCallException('Collection methods are not supported by the Walker');
390
    }
391
392
    /**
393
     * {@inheritdoc}
394
     */
395
    public function clear()
396
    {
397
        throw new BadMethodCallException('Collection methods are not supported by the Walker');
398
    }
399
400
    /**
401
     * {@inheritdoc}
402
     */
403
    public function keys()
404
    {
405
        throw new BadMethodCallException('Collection methods are not supported by the Walker');
406
    }
407
408
    /**
409
     * {@inheritdoc}
410
     */
411
    public function isEmpty()
412
    {
413
        throw new BadMethodCallException('Collection methods are not supported by the Walker');
414
    }
415
416
    /**
417
     * {@inheritdoc}
418
     */
419 1
    public function map($callback)
420
    {
421 1
        throw new BadMethodCallException('Collection methods are not supported by the Walker');
422
    }
423
424
    /**
425
     * {@inheritdoc}
426
     */
427
    public function filter($callback = null)
428
    {
429
        throw new BadMethodCallException('Collection methods are not supported by the Walker');
430
    }
431
432
    /**
433
     * {@inheritdoc}
434
     */
435
    public function groupBy($groupBy, $mode = self::GROUPBY)
436
    {
437
        throw new BadMethodCallException('Collection methods are not supported by the Walker');
438
    }
439
440
    /**
441
     * {@inheritdoc}
442
     */
443
    public function contains($element)
444
    {
445
        throw new BadMethodCallException('Collection methods are not supported by the Walker');
446
    }
447
448
    /**
449
     * {@inheritdoc}
450
     */
451
    public function indexOf($value, $strict = false)
452
    {
453
        throw new BadMethodCallException('Collection methods are not supported by the Walker');
454
    }
455
456
    /**
457
     * {@inheritdoc}
458
     */
459
    public function merge($items)
460
    {
461
        throw new BadMethodCallException('Collection methods are not supported by the Walker');
462
    }
463
464
    /**
465
     * {@inheritdoc}
466
     */
467
    public function sort(callable $callback = null)
468
    {
469
        throw new BadMethodCallException('Collection methods are not supported by the Walker');
470
    }
471
472
    /**
473
     * {@inheritdoc}
474
     */
475
    public function offsetExists($offset): bool
476
    {
477
        throw new BadMethodCallException('Collection methods are not supported by the Walker');
478
    }
479
480
    /**
481
     * {@inheritdoc}
482
     */
483
    #[\ReturnTypeWillChange]
484
    public function offsetGet($offset)
485
    {
486
        throw new BadMethodCallException('Collection methods are not supported by the Walker');
487
    }
488
489
    /**
490
     * {@inheritdoc}
491
     */
492
    public function offsetSet($offset, $value): void
493
    {
494
        throw new BadMethodCallException('Collection methods are not supported by the Walker');
495
    }
496
497
    /**
498
     * {@inheritdoc}
499
     */
500
    public function offsetUnset($offset): void
501
    {
502
        throw new BadMethodCallException('Collection methods are not supported by the Walker');
503
    }
504
}
505