• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

visavi / motor-orm / 33188900344

28 Aug 2026 04:12PM UTC coverage: 98.627% (-0.02%) from 98.649%
33188900344

push

github

visavi
Поправил примеры

1149 of 1165 relevant lines covered (98.63%)

33.46 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.23
/src/PagedCollection.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace MotorORM;
6

7
use Closure;
8
use InvalidArgumentException;
9

10
/**
11
 * A collection that knows it is one page of a longer read
12
 *
13
 * It holds the rows of that page and is walked over like any other collection,
14
 * and on top of that it knows where the page stands among the rest. Where the
15
 * page number came from is none of its business
16
 *
17
 * @license Code and contributions have MIT License
18
 * @link    https://visavi.net
19
 * @author  Alexander Grigorev <admin@visavi.net>
20
 */
21
abstract class PagedCollection extends Collection
22
{
23
    /** Rows on a page */
24
    protected(set) int $limit;
25

26
    /** The page being shown */
27
    protected(set) int $page;
28

29
    /** Rows to skip to reach the page */
30
    protected(set) int $offset;
31

32
    /**
33
     * Name of the page parameter, in the urls that are built and in the
34
     * request the current page is read from
35
     *
36
     * The page has to be known before a page of rows exists, so the name of
37
     * the parameter carrying it cannot belong to that page either
38
     */
39
    private static string $pageName = 'page';
40

41
    /**
42
     * Where the current page comes from when a query is not told it
43
     *
44
     * A library that reads csv knows nothing about requests, so nothing is
45
     * read from the environment: until an application says where the page
46
     * comes from, the first one is meant
47
     */
48
    private static ?Closure $pageResolver = null;
49

50
    /** Path the links point at, relative to the current one when it is null */
51
    protected(set) ?string $path = null;
52

53
    /** Query parameters every link carries along */
54
    protected(set) array $appends = [];
55

56
    /**
57
     * @param array $items the rows of the page
58
     * @param int   $limit rows on a page
59
     * @param int   $page  the page to show
60
     */
61
    protected function __construct(array $items, int $limit, int $page)
55✔
62
    {
63
        if ($limit < 1) {
55✔
64
            throw new InvalidArgumentException(
2✔
65
                sprintf('%s() a page has to hold at least one row, %d given', static::class, $limit)
2✔
66
            );
2✔
67
        }
68

69
        $this->limit  = $limit;
53✔
70
        $this->page   = $this->clamp(max(1, $page));
53✔
71
        $this->offset = $this->page * $this->limit - $this->limit;
53✔
72

73
        parent::__construct($items);
53✔
74
    }
75

76
    /**
77
     * The entries of the navigation
78
     *
79
     * The rows of the page are the collection itself, these are the links
80
     * under it
81
     *
82
     * @return Page[]
83
     */
84
    abstract public function pages(): array;
85

86
    /**
87
     * Whether there is anything after this page
88
     *
89
     * @return bool
90
     */
91
    abstract public function hasMorePages(): bool;
92

93
    /**
94
     * Rows on the page being shown
95
     *
96
     * @return int
97
     */
98
    abstract protected function rowsOnPage(): int;
99

100
    /**
101
     * Get current page
102
     *
103
     * @return int
104
     */
105
    public function currentPage(): int
11✔
106
    {
107
        return $this->page;
11✔
108
    }
109

110
    /**
111
     * Rows on a page
112
     *
113
     * @return int
114
     */
115
    public function perPage(): int
4✔
116
    {
117
        return $this->limit;
4✔
118
    }
119

120
    /**
121
     * Number of the first row of the page among all the rows
122
     *
123
     * @return int|null null when the page holds nothing
124
     */
125
    public function firstItem(): ?int
11✔
126
    {
127
        return $this->rowsOnPage() ? $this->offset + 1 : null;
11✔
128
    }
129

130
    /**
131
     * Number of the last row of the page among all the rows
132
     *
133
     * @return int|null null when the page holds nothing
134
     */
135
    public function lastItem(): ?int
10✔
136
    {
137
        return $this->rowsOnPage() ? $this->offset + $this->rowsOnPage() : null;
10✔
138
    }
139

140
    /**
141
     * Whether the first page is the one being shown
142
     *
143
     * @return bool
144
     */
145
    public function onFirstPage(): bool
12✔
146
    {
147
        return $this->page === 1;
12✔
148
    }
149

150
    /**
151
     * Whether the last page is the one being shown
152
     *
153
     * @return bool
154
     */
155
    public function onLastPage(): bool
7✔
156
    {
157
        return ! $this->hasMorePages();
7✔
158
    }
159

160
    /**
161
     * Whether there is more than one page
162
     *
163
     * @return bool
164
     */
165
    public function hasPages(): bool
3✔
166
    {
167
        return $this->hasMorePages() || ! $this->onFirstPage();
3✔
168
    }
169

170
    /**
171
     * Url of a page
172
     *
173
     * @param int $page
174
     *
175
     * @return string
176
     */
177
    public function url(int $page): string
1✔
178
    {
179
        return $this->buildUrl(max(1, $page));
1✔
180
    }
181

182
    /**
183
     * Name the page parameter
184
     *
185
     * @param string $name
186
     *
187
     * @return void
188
     */
189
    public static function setPageName(string $name): void
53✔
190
    {
191
        self::$pageName = $name;
53✔
192
    }
193

194
    /**
195
     * Name of the page parameter
196
     *
197
     * @return string
198
     */
199
    public static function pageName(): string
×
200
    {
201
        return self::$pageName;
×
202
    }
203

204
    /**
205
     * Say where the current page comes from
206
     *
207
     * The source is the application's to name — the query string of a request,
208
     * a PSR-7 request, a router, a console argument. Until it does, or a query
209
     * is told the page outright with page(), the first page is meant
210
     *
211
     * @param Closure|null $resolver called with the name of the page parameter
212
     *
213
     * @return void
214
     */
215
    public static function resolvePageUsing(?Closure $resolver): void
53✔
216
    {
217
        self::$pageResolver = $resolver;
53✔
218
    }
219

220
    /**
221
     * The page being asked for
222
     *
223
     * @return int never below the first page
224
     */
225
    public static function resolveCurrentPage(): int
14✔
226
    {
227
        if (! self::$pageResolver) {
14✔
228
            return 1;
7✔
229
        }
230

231
        $page = (self::$pageResolver)(self::$pageName);
7✔
232

233
        /* A page that is no number is no page, the first one is meant */
234
        return is_numeric($page) ? max(1, (int) $page) : 1;
7✔
235
    }
236

237
    /**
238
     * Point the links at a path
239
     *
240
     * @param string $path
241
     *
242
     * @return $this
243
     */
244
    public function withPath(string $path): static
13✔
245
    {
246
        $this->path = $path;
13✔
247

248
        return $this;
13✔
249
    }
250

251
    /**
252
     * Carry query parameters along every link
253
     *
254
     * @param array $appends
255
     *
256
     * @return $this
257
     */
258
    public function appends(array $appends): static
6✔
259
    {
260
        $this->appends = $appends;
6✔
261

262
        return $this;
6✔
263
    }
264

265
    /**
266
     * Keep the page within the range the collection knows of
267
     *
268
     * @param int $page
269
     *
270
     * @return int
271
     */
272
    protected function clamp(int $page): int
17✔
273
    {
274
        return $page;
17✔
275
    }
276

277
    /**
278
     * Build a link to the given page
279
     *
280
     * @param int             $page
281
     * @param int|string|null $name what the view prints, the number by default
282
     *
283
     * @return Page
284
     */
285
    protected function link(int $page, int|string|null $name = null): Page
10✔
286
    {
287
        return Page::link($page, $this->buildUrl($page), $name ?? $page);
10✔
288
    }
289

290
    /**
291
     * Build url
292
     *
293
     * The first page is the path itself: "?page=1" points at what the path
294
     * already points at, and two urls for one page is one too many
295
     *
296
     * @param int $page
297
     *
298
     * @return string
299
     */
300
    protected function buildUrl(int $page): string
17✔
301
    {
302
        $query = http_build_query(
17✔
303
            $page > 1 ? [self::$pageName => $page] + $this->appends : $this->appends
17✔
304
        );
17✔
305

306
        if ($query === '') {
17✔
307
            /* An empty url would mean the current one, page parameter and all */
308
            return $this->path ?: '?';
6✔
309
        }
310

311
        return $this->path . '?' . $query;
15✔
312
    }
313
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc