Passed
Push — master ( 0c0121...d6a1fd )
by Alexander
10:21 queued 08:08
created

OffsetPaginator::getPageSize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Data\Paginator;
6
7
use Generator;
8
use InvalidArgumentException;
9
use Yiisoft\Data\Reader\CountableDataInterface;
10
use Yiisoft\Data\Reader\OffsetableDataInterface;
11
use Yiisoft\Data\Reader\ReadableDataInterface;
12
use Yiisoft\Data\Reader\Sort;
13
use Yiisoft\Data\Reader\SortableDataInterface;
14
15
use function ceil;
16
use function max;
17
use function sprintf;
18
19
/**
20
 * @psalm-template DataReaderType = ReadableDataInterface<TKey, TValue>&OffsetableDataInterface&CountableDataInterface
21
 *
22
 * @template TKey as array-key
23
 * @template TValue
24
 *
25
 * @implements PaginatorInterface<TKey, TValue>
26
 */
27
final class OffsetPaginator implements PaginatorInterface
28
{
29
    private int $currentPage = 1;
30
    private int $pageSize = self::DEFAULT_PAGE_SIZE;
31
32
    /**
33
     * @psalm-var DataReaderType
34
     */
35
    private ReadableDataInterface $dataReader;
36
37
    /**
38
     * @psalm-var DataReaderType|null
39
     */
40
    private ?ReadableDataInterface $cachedReader = null;
41
42
    /**
43
     * @psalm-param DataReaderType $dataReader
44
     */
45 30
    public function __construct(ReadableDataInterface $dataReader)
46
    {
47 30
        if (!$dataReader instanceof OffsetableDataInterface) {
48 1
            throw new InvalidArgumentException(sprintf(
49
                'Data reader should implement "%s" in order to be used with offset paginator.',
50
                OffsetableDataInterface::class,
51
            ));
52
        }
53
54 29
        if (!$dataReader instanceof CountableDataInterface) {
55 1
            throw new InvalidArgumentException(sprintf(
56
                'Data reader should implement "%s" in order to be used with offset paginator.',
57
                CountableDataInterface::class,
58
            ));
59
        }
60
61 28
        $this->dataReader = $dataReader;
62
    }
63
64 2
    public function withNextPageToken(?string $token): self
65
    {
66 2
        return $this->withCurrentPage((int) $token);
67
    }
68
69 2
    public function withPreviousPageToken(?string $token): self
70
    {
71 2
        return $this->withCurrentPage((int) $token);
72
    }
73
74 20
    public function withPageSize(int $pageSize): self
75
    {
76 20
        if ($pageSize < 1) {
77 1
            throw new PaginatorException('Page size should be at least 1.');
78
        }
79
80 19
        $new = clone $this;
81 19
        $new->pageSize = $pageSize;
82 19
        $new->cachedReader = null;
83 19
        return $new;
84
    }
85
86 17
    public function withCurrentPage(int $page): self
87
    {
88 17
        if ($page < 1) {
89 1
            throw new PaginatorException('Current page should be at least 1.');
90
        }
91
92 16
        $new = clone $this;
93 16
        $new->currentPage = $page;
94 16
        $new->cachedReader = null;
95 16
        return $new;
96
    }
97
98 1
    public function getNextPageToken(): ?string
99
    {
100 1
        return $this->isOnLastPage() ? null : (string) ($this->currentPage + 1);
101
    }
102
103 1
    public function getPreviousPageToken(): ?string
104
    {
105 1
        return $this->isOnFirstPage() ? null : (string) ($this->currentPage - 1);
106
    }
107
108 1
    public function getPageSize(): int
109
    {
110 1
        return $this->pageSize;
111
    }
112
113 3
    public function getCurrentPage(): int
114
    {
115 3
        return $this->currentPage;
116
    }
117
118 6
    public function getCurrentPageSize(): int
119
    {
120 6
        $pages = $this->getInternalTotalPages();
121
122 6
        if ($pages === 1) {
123 3
            return $this->getTotalItems();
124
        }
125
126 3
        if ($this->currentPage === $pages) {
127 1
            return $this->getTotalItems() - $this->getOffset();
128
        }
129
130 2
        if ($this->currentPage > $pages) {
131 1
            throw new PaginatorException('Page not found.');
132
        }
133
134 1
        return $this->pageSize;
135
    }
136
137 6
    public function getOffset(): int
138
    {
139 6
        return $this->pageSize * ($this->currentPage - 1);
140
    }
141
142 19
    public function getTotalItems(): int
143
    {
144
        /** @psalm-var CountableDataInterface $this->dataReader */
145 19
        return $this->dataReader->count();
0 ignored issues
show
Bug introduced by
The method count() does not exist on Yiisoft\Data\Reader\ReadableDataInterface. It seems like you code against a sub-type of said class. However, the method does not exist in anonymous//tests/Paginat...ysetPaginatorTest.php$2 or anonymous//tests/Paginat...ysetPaginatorTest.php$1. Are you sure you never get one of those? ( Ignorable by Annotation )

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

145
        return $this->dataReader->/** @scrutinizer ignore-call */ count();
Loading history...
146
    }
147
148 18
    public function getTotalPages(): int
149
    {
150 18
        return (int) ceil($this->getTotalItems() / $this->pageSize);
151
    }
152
153 1
    public function getSort(): ?Sort
154
    {
155 1
        return $this->dataReader instanceof SortableDataInterface ? $this->dataReader->getSort() : null;
0 ignored issues
show
Bug introduced by
The method getSort() does not exist on Yiisoft\Data\Reader\ReadableDataInterface. It seems like you code against a sub-type of Yiisoft\Data\Reader\ReadableDataInterface such as anonymous//tests/Paginat...ysetPaginatorTest.php$2 or Yiisoft\Data\Reader\DataReaderInterface. ( Ignorable by Annotation )

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

155
        return $this->dataReader instanceof SortableDataInterface ? $this->dataReader->/** @scrutinizer ignore-call */ getSort() : null;
Loading history...
156
    }
157
158
    /**
159
     * @psalm-return Generator<TKey, TValue, mixed, void>
160
     * @psalm-suppress MixedAssignment, MixedMethodCall, MixedReturnTypeCoercion
161
     */
162 5
    public function read(): iterable
163
    {
164 5
        if ($this->cachedReader !== null) {
165
            yield from $this->cachedReader->read();
166
            return;
167
        }
168
169 5
        if ($this->currentPage > $this->getInternalTotalPages()) {
170 1
            throw new PaginatorException('Page not found.');
171
        }
172
173 4
        $this->cachedReader = $this->dataReader
174 4
            ->withLimit($this->pageSize)
175 4
            ->withOffset($this->getOffset());
0 ignored issues
show
Bug introduced by
The method withOffset() does not exist on Yiisoft\Data\Reader\ReadableDataInterface. It seems like you code against a sub-type of Yiisoft\Data\Reader\ReadableDataInterface such as anonymous//tests/Paginat...fsetPaginatorTest.php$1 or Yiisoft\Data\Reader\DataReaderInterface. ( Ignorable by Annotation )

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

175
            ->/** @scrutinizer ignore-call */ withOffset($this->getOffset());
Loading history...
176 4
        yield from $this->cachedReader->read();
0 ignored issues
show
Bug introduced by
The method read() does not exist on null. ( Ignorable by Annotation )

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

176
        yield from $this->cachedReader->/** @scrutinizer ignore-call */ read();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
177
    }
178
179 5
    public function isOnFirstPage(): bool
180
    {
181 5
        return $this->currentPage === 1;
182
    }
183
184 5
    public function isOnLastPage(): bool
185
    {
186 5
        if ($this->currentPage > $this->getInternalTotalPages()) {
187 1
            throw new PaginatorException('Page not found.');
188
        }
189
190 4
        return $this->currentPage === $this->getInternalTotalPages();
191
    }
192
193 3
    public function isRequired(): bool
194
    {
195 3
        return $this->getTotalPages() > 1;
196
    }
197
198 14
    private function getInternalTotalPages(): int
199
    {
200 14
        return max(1, $this->getTotalPages());
201
    }
202
}
203