Passed
Push — master ( 899988...8dc315 )
by Alexander
02:32
created

OffsetPaginator   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 177
Duplicated Lines 0 %

Test Coverage

Coverage 84.93%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 58
dl 0
loc 177
ccs 62
cts 73
cp 0.8493
rs 10
c 1
b 0
f 0
wmc 30

18 Methods

Rating   Name   Duplication   Size   Complexity  
A isRequired() 0 3 1
A read() 0 12 3
A getPageSize() 0 3 1
A isOnFirstPage() 0 3 1
A getInternalTotalPages() 0 3 1
A getTotalItems() 0 3 1
A withPreviousPageToken() 0 3 1
A withPageSize() 0 9 2
A withNextPageToken() 0 3 1
A getNextPageToken() 0 3 2
A withCurrentPage() 0 9 2
A getOffset() 0 3 1
A getPreviousPageToken() 0 3 2
A __construct() 0 21 3
A getCurrentPage() 0 3 1
A getCurrentPageSize() 0 13 4
A isOnLastPage() 0 6 2
A getTotalPages() 0 3 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Data\Paginator;
6
7
use Yiisoft\Data\Reader\CountableDataInterface;
8
use Yiisoft\Data\Reader\OffsetableDataInterface;
9
use Yiisoft\Data\Reader\ReadableDataInterface;
10
11
/**
12
 * @template TKey as array-key
13
 * @template TValue
14
 *
15
 * @implements PaginatorInterface<TKey, TValue>
16
 */
17
final class OffsetPaginator implements PaginatorInterface
18
{
19
    /** @var CountableDataInterface|OffsetableDataInterface|ReadableDataInterface */
20
    private ReadableDataInterface $dataReader;
21
    private int $currentPage = 1;
22
    private int $pageSize = self::DEFAULT_PAGE_SIZE;
23
    /**
24
     * @psalm-var ReadableDataInterface<TKey, TValue>
25
     */
26
    private ?ReadableDataInterface $cachedReader = null;
27
28
    /**
29
     * @psalm-param ReadableDataInterface<TKey, TValue> $dataReader
30
     */
31 23
    public function __construct(ReadableDataInterface $dataReader)
32
    {
33 23
        if (!$dataReader instanceof OffsetableDataInterface) {
34 1
            throw new \InvalidArgumentException(
35 1
                sprintf(
36 1
                    'Data reader should implement %s in order to be used with offset paginator',
37 1
                    OffsetableDataInterface::class
38
                )
39
            );
40
        }
41
42 22
        if (!$dataReader instanceof CountableDataInterface) {
43 1
            throw new \InvalidArgumentException(
44 1
                sprintf(
45 1
                    'Data reader should implement %s in order to be used with offset paginator',
46 1
                    CountableDataInterface::class
47
                )
48
            );
49
        }
50
51 21
        $this->dataReader = $dataReader;
52 21
    }
53
54 3
    public function isRequired(): bool
55
    {
56 3
        return $this->getTotalPages() > 1;
57
    }
58
59 3
    public function getCurrentPage(): int
60
    {
61 3
        return $this->currentPage;
62
    }
63
64
    /**
65
     * @return $this
66
     *
67
     * @psalm-mutation-free
68
     */
69 12
    public function withCurrentPage(int $page): self
70
    {
71 12
        if ($page < 1) {
72 1
            throw new PaginatorException('Current page should be at least 1');
73
        }
74 11
        $new = clone $this;
75 11
        $new->currentPage = $page;
76 11
        $new->cachedReader = null;
77 11
        return $new;
78
    }
79
80
    /**
81
     * @return $this
82
     *
83
     * @psalm-mutation-free
84
     */
85 16
    public function withPageSize(int $size): self
86
    {
87 16
        if ($size < 1) {
88 1
            throw new PaginatorException('Page size should be at least 1');
89
        }
90 15
        $new = clone $this;
91 15
        $new->pageSize = $size;
92 15
        $new->cachedReader = null;
93 15
        return $new;
94
    }
95
96 4
    public function isOnFirstPage(): bool
97
    {
98 4
        return $this->currentPage === 1;
99
    }
100
101 4
    public function isOnLastPage(): bool
102
    {
103 4
        if ($this->currentPage > $this->getInternalTotalPages()) {
104 1
            throw new PaginatorException('Page not found');
105
        }
106 3
        return $this->currentPage === $this->getInternalTotalPages();
107
    }
108
109 14
    public function getTotalPages(): int
110
    {
111 14
        return (int) ceil($this->getTotalItems() / $this->pageSize);
112
    }
113
114 6
    public function getOffset(): int
115
    {
116 6
        return $this->pageSize * ($this->currentPage - 1);
117
    }
118
119
    /**
120
     * @psalm-return \Generator<TKey, TValue, mixed, void>
121
     */
122 5
    public function read(): iterable
123
    {
124 5
        if ($this->cachedReader !== null) {
125
            yield from $this->cachedReader->read();
126
            return;
127
        }
128 5
        if ($this->currentPage > $this->getInternalTotalPages()) {
129 1
            throw new PaginatorException('Page not found');
130
        }
131
        /** @var CountableDataInterface|OffsetableDataInterface|ReadableDataInterface $reader */
132 4
        $this->cachedReader = $this->dataReader->withLimit($this->pageSize)->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

132
        $this->cachedReader = $this->dataReader->withLimit($this->pageSize)->/** @scrutinizer ignore-call */ withOffset($this->getOffset());
Loading history...
133 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

133
        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...
134 4
    }
135
136
    public function getNextPageToken(): ?string
137
    {
138
        return $this->isOnLastPage() ? null : (string) ($this->currentPage + 1);
139
    }
140
141
    public function getPreviousPageToken(): ?string
142
    {
143
        return $this->isOnFirstPage() ? null : (string) ($this->currentPage - 1);
144
    }
145
146
    /**
147
     * @return $this
148
     *
149
     * @psalm-mutation-free
150
     */
151
    public function withNextPageToken(?string $token): self
152
    {
153
        return $this->withCurrentPage((int)$token);
154
    }
155
156
    /**
157
     * @return $this
158
     *
159
     * @psalm-mutation-free
160
     */
161
    public function withPreviousPageToken(?string $token): self
162
    {
163
        return $this->withCurrentPage((int)$token);
164
    }
165
166 4
    public function getCurrentPageSize(): int
167
    {
168 4
        $pages = $this->getInternalTotalPages();
169 4
        if ($pages === 1) {
170 2
            return $this->getTotalItems();
171
        }
172 2
        if ($this->currentPage === $pages) {
173 1
            return $this->getTotalItems() - $this->getOffset();
174
        }
175 1
        if ($this->currentPage > $pages) {
176
            throw new PaginatorException('Page not found');
177
        }
178 1
        return $this->pageSize;
179
    }
180
181 15
    public function getTotalItems(): int
182
    {
183 15
        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

183
        return $this->dataReader->/** @scrutinizer ignore-call */ count();
Loading history...
184
    }
185
186 1
    public function getPageSize(): int
187
    {
188 1
        return $this->pageSize;
189
    }
190
191 11
    private function getInternalTotalPages(): int
192
    {
193 11
        return max(1, $this->getTotalPages());
194
    }
195
}
196