Passed
Push — master ( 08d220...46108d )
by Alexander
01:28
created

KeysetPaginator::read()   B

Complexity

Conditions 7
Paths 17

Size

Total Lines 29
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 7.0796

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 16
c 1
b 0
f 0
dl 0
loc 29
ccs 15
cts 17
cp 0.8824
rs 8.8333
cc 7
nc 17
nop 0
crap 7.0796
1
<?php
2
declare(strict_types=1);
3
4
namespace Yiisoft\Data\Paginator;
5
6
use Yiisoft\Data\Reader\Filter\GreaterThan;
7
use Yiisoft\Data\Reader\Filter\LessThan;
8
use Yiisoft\Data\Reader\DataReaderInterface;
9
use Yiisoft\Data\Reader\FilterableDataInterface;
10
use Yiisoft\Data\Reader\SortableDataInterface;
11
12
/**
13
 * Keyset paginator
14
 *
15
 * - Equally fast for 1st and 1000nd page
16
 * - Total number of pages is not available
17
 * - Cannot get to specific page, only "next" and "previous"
18
 *
19
 * @link https://use-the-index-luke.com/no-offset
20
 */
21
class KeysetPaginator
22
{
23
    /**
24
     * @var FilterableDataInterface|DataReaderInterface|SortableDataInterface
25
     */
26
    private $dataReader;
27
    private $pageSize;
28
29
    private $lastValue;
30
    private $currentLastValue;
31
32 7
    public function __construct(DataReaderInterface $dataReader)
33
    {
34 7
        if (!$dataReader instanceof FilterableDataInterface) {
35 1
            throw new \InvalidArgumentException('Data reader should implement FilterableDataInterface in order to be used with keyset paginator');
36
        }
37
38 6
        if (!$dataReader instanceof SortableDataInterface) {
39 1
            throw new \InvalidArgumentException('Data reader should implement SortableDataInterface in order to be used with keyset paginator');
40
        }
41
42 5
        if ($dataReader->getSort() === null) {
43 1
            throw new \RuntimeException('Data sorting should be configured in order to work with keyset pagination');
44
        }
45
46 4
        $this->dataReader = $dataReader;
47 4
    }
48
49 3
    public function read(): iterable
50
    {
51 3
        $this->currentLastValue = null;
52 3
        $dataReader = $this->dataReader->withLimit($this->pageSize);
0 ignored issues
show
Bug introduced by
The method withLimit() does not exist on Yiisoft\Data\Reader\FilterableDataInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\Data\Reader\FilterableDataInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

52
        /** @scrutinizer ignore-call */ 
53
        $dataReader = $this->dataReader->withLimit($this->pageSize);
Loading history...
Bug introduced by
The method withLimit() does not exist on Yiisoft\Data\Reader\SortableDataInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\Data\Reader\SortableDataInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

52
        /** @scrutinizer ignore-call */ 
53
        $dataReader = $this->dataReader->withLimit($this->pageSize);
Loading history...
53
54 3
        $order = $this->dataReader->getSort()->getOrder();
0 ignored issues
show
Bug introduced by
The method getSort() does not exist on Yiisoft\Data\Reader\DataReaderInterface. It seems like you code against a sub-type of Yiisoft\Data\Reader\DataReaderInterface such as Yiisoft\Data\Reader\IterableDataReader. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

54
        $order = $this->dataReader->/** @scrutinizer ignore-call */ getSort()->getOrder();
Loading history...
Bug introduced by
The method getSort() does not exist on Yiisoft\Data\Reader\FilterableDataInterface. It seems like you code against a sub-type of Yiisoft\Data\Reader\FilterableDataInterface such as Yiisoft\Data\Reader\IterableDataReader. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

54
        $order = $this->dataReader->/** @scrutinizer ignore-call */ getSort()->getOrder();
Loading history...
55
56 3
        if ($order === []) {
57 1
            throw new \RuntimeException('Data should be always sorted in order to work with keyset pagination');
58
        }
59
60
        // first order field is the field we are paging by
61 2
        foreach ($order as $field => $sorting) {
62 2
            break;
63
        }
64
65 2
        if (isset($this->lastValue)) {
66 1
            if ($sorting === 'asc') {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $sorting does not seem to be defined for all execution paths leading up to this point.
Loading history...
67 1
                $filter = new GreaterThan($field, $this->lastValue);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $field does not seem to be defined for all execution paths leading up to this point.
Loading history...
68
            } elseif ($sorting === 'desc') {
69
                $filter = new LessThan($field, $this->lastValue);
70
            }
71
72 1
            $dataReader = $dataReader->withFilter($filter);
0 ignored issues
show
Bug introduced by
The method withFilter() does not exist on Yiisoft\Data\Reader\SortableDataInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\Data\Reader\SortableDataInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

72
            /** @scrutinizer ignore-call */ 
73
            $dataReader = $dataReader->withFilter($filter);
Loading history...
Bug introduced by
The method withFilter() does not exist on Yiisoft\Data\Reader\DataReaderInterface. It seems like you code against a sub-type of Yiisoft\Data\Reader\DataReaderInterface such as anonymous//tests/Paginat...ysetPaginatorTest.php$1 or Yiisoft\Data\Reader\IterableDataReader. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

72
            /** @scrutinizer ignore-call */ 
73
            $dataReader = $dataReader->withFilter($filter);
Loading history...
Comprehensibility Best Practice introduced by
The variable $filter does not seem to be defined for all execution paths leading up to this point.
Loading history...
73
        }
74
75 2
        foreach($dataReader->read() as $item) {
76 2
            $this->currentLastValue = $item[$field];
77 2
            yield $item;
78
        }
79 2
    }
80
81 2
    public function withLast($value): self
82
    {
83 2
        $new = clone $this;
84 2
        $new->lastValue = $value;
85 2
        return $new;
86
    }
87
88 2
    public function getLastValue() {
89 2
        return $this->currentLastValue;
90
    }
91
92 4
    public function withPageSize(int $pageSize): self
93
    {
94 4
        if ($pageSize < 1) {
95 1
            throw new \InvalidArgumentException('Page size should be at least 1');
96
        }
97
98 3
        $new = clone $this;
99 3
        $new->pageSize = $pageSize;
100 3
        return $new;
101
    }
102
}
103