Completed
Push — master ( 3d4a40...992a8b )
by Alexander
25s queued 11s
created

KeysetPaginator::getLast()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 2
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 0
crap 1
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 $firstValue;
30
    private $lastValue;
31
32
    private $currentFirstValue;
33
    private $currentLastValue;
34
35 10
    public function __construct(DataReaderInterface $dataReader)
36
    {
37 10
        if (!$dataReader instanceof FilterableDataInterface) {
38 1
            throw new \InvalidArgumentException('Data reader should implement FilterableDataInterface in order to be used with keyset paginator');
39
        }
40
41 9
        if (!$dataReader instanceof SortableDataInterface) {
42 1
            throw new \InvalidArgumentException('Data reader should implement SortableDataInterface in order to be used with keyset paginator');
43
        }
44
45 8
        if ($dataReader->getSort() === null) {
46 1
            throw new \RuntimeException('Data sorting should be configured in order to work with keyset pagination');
47
        }
48
49 7
        $this->dataReader = $dataReader;
50 7
    }
51
52 6
    public function read(): iterable
53
    {
54 6
        $this->currentLastValue = null;
55 6
        $this->currentFirstValue = null;
56
57 6
        $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

57
        /** @scrutinizer ignore-call */ 
58
        $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

57
        /** @scrutinizer ignore-call */ 
58
        $dataReader = $this->dataReader->withLimit($this->pageSize);
Loading history...
58
59 6
        $sort = $this->dataReader->getSort();
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

59
        /** @scrutinizer ignore-call */ 
60
        $sort = $this->dataReader->getSort();
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

59
        /** @scrutinizer ignore-call */ 
60
        $sort = $this->dataReader->getSort();
Loading history...
60 6
        $order = $sort->getOrder();
61
62 6
        if ($order === []) {
63 1
            throw new \RuntimeException('Data should be always sorted in order to work with keyset pagination');
64
        }
65
66 5
        $goingToPreviousPage = $this->firstValue !== null && $this->lastValue === null;
67 5
        $goingToNextPage = $this->firstValue === null && $this->lastValue !== null;
68
69 5
        if ($goingToPreviousPage) {
70
            // reverse sorting
71 2
            foreach ($order as &$sorting) {
72 2
                $sorting = $sorting === 'asc' ? 'desc' : 'asc';
73
            }
74 2
            unset($sorting);
75 2
            $dataReader = $dataReader->withSort($sort->withOrder($order));
0 ignored issues
show
Bug introduced by
The method withSort() 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

75
            /** @scrutinizer ignore-call */ 
76
            $dataReader = $dataReader->withSort($sort->withOrder($order));
Loading history...
Bug introduced by
The method withSort() 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

75
            /** @scrutinizer ignore-call */ 
76
            $dataReader = $dataReader->withSort($sort->withOrder($order));
Loading history...
76
        }
77
78
        // first order field is the field we are paging by
79 5
        $field = null;
80 5
        $sorting = null;
81 5
        foreach ($order as $field => $sorting) {
82 5
            break;
83
        }
84
85 5
        if ($goingToPreviousPage || $goingToNextPage) {
86 4
            $value = $goingToPreviousPage ? $this->firstValue : $this->lastValue;
87
88 4
            $filter = null;
89 4
            if ($sorting === 'asc') {
90 3
                $filter = new GreaterThan($field, $value);
91 2
            } elseif ($sorting === 'desc') {
92 2
                $filter = new LessThan($field, $value);
93
            }
94
95 4
            $dataReader = $dataReader->withFilter($filter);
0 ignored issues
show
Bug introduced by
It seems like $filter can also be of type null; however, parameter $filter of Yiisoft\Data\Reader\Filt...Interface::withFilter() does only seem to accept Yiisoft\Data\Reader\Filter\FilterInterface, maybe add an additional type check? ( Ignorable by Annotation )

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

95
            $dataReader = $dataReader->withFilter(/** @scrutinizer ignore-type */ $filter);
Loading history...
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

95
            /** @scrutinizer ignore-call */ 
96
            $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

95
            /** @scrutinizer ignore-call */ 
96
            $dataReader = $dataReader->withFilter($filter);
Loading history...
96
        }
97
98 5
        $data = [];
99 5
        foreach ($dataReader->read() as $item) {
100 5
            $this->currentLastValue = $item[$field];
101 5
            if ($this->currentFirstValue === null) {
102 5
                $this->currentFirstValue = $item[$field];
103
            }
104 5
            $data[] = $item;
105
        }
106
107 5
        if ($goingToPreviousPage) {
108 2
            [$this->currentFirstValue, $this->currentLastValue] = [$this->currentLastValue, $this->currentFirstValue];
109 2
            return array_reverse($data);
110
        }
111
112 4
        return $data;
113
    }
114
115 2
    public function withFirst($value): self
116
    {
117 2
        $new = clone $this;
118 2
        $new->firstValue = $value;
119 2
        return $new;
120
    }
121
122 4
    public function withLast($value): self
123
    {
124 4
        $new = clone $this;
125 4
        $new->lastValue = $value;
126 4
        return $new;
127
    }
128
129 2
    public function getFirst() {
130 2
        return $this->currentFirstValue;
131
    }
132
133 5
    public function getLast() {
134 5
        return $this->currentLastValue;
135
    }
136
137 7
    public function withPageSize(int $pageSize): self
138
    {
139 7
        if ($pageSize < 1) {
140 1
            throw new \InvalidArgumentException('Page size should be at least 1');
141
        }
142
143 6
        $new = clone $this;
144 6
        $new->pageSize = $pageSize;
145 6
        return $new;
146
    }
147
}
148