Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 54
0.00% covered (danger)
0.00%
0 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
PagerBuilder
0.00% covered (danger)
0.00%
0 / 54
0.00% covered (danger)
0.00%
0 / 15
380
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setId
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setTotalPages
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setTotalResults
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setLimit
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 setCurrentOffset
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setFirstOffset
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setPrevOffset
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setNextOffset
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setLastOffset
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setOrdinals
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 setPosition
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 setPaginationSizeOptions
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 setPaginationSizeDefault
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 build
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * PagerBuilder.php
4 *
5 * This file is part of the Codex design system, the official design system
6 * for Wikimedia projects. It provides the `Pager` class, a builder for constructing
7 * pagination controls using the Codex design system.
8 *
9 * Pagers are used to navigate through pages of data, facilitating user interaction with large datasets.
10 *
11 * @category Builder
12 * @package  Codex\Builder
13 * @since    0.1.0
14 * @author   Doğu Abaris <abaris@null.net>
15 * @license  https://www.gnu.org/copyleft/gpl.html GPL-2.0-or-later
16 * @link     https://doc.wikimedia.org/codex/main/ Codex Documentation
17 */
18
19namespace Wikimedia\Codex\Builder;
20
21use InvalidArgumentException;
22use Wikimedia\Codex\Component\Pager;
23use Wikimedia\Codex\Renderer\PagerRenderer;
24
25/**
26 * PagerBuilder
27 *
28 * This class implements the builder pattern to construct instances of Pager.
29 * It provides a fluent interface for setting various properties and building the
30 * final immutable object with predefined configurations and immutability.
31 *
32 * @category Builder
33 * @package  Codex\Builder
34 * @since    0.1.0
35 * @author   Doğu Abaris <abaris@null.net>
36 * @license  https://www.gnu.org/copyleft/gpl.html GPL-2.0-or-later
37 * @link     https://doc.wikimedia.org/codex/main/ Codex Documentation
38 */
39class PagerBuilder {
40
41    /**
42     * Valid positions for pagination controls ('top', 'bottom', or 'both').
43     */
44    private const TABLE_PAGINATION_POSITIONS = [
45        'top',
46        'bottom',
47        'both',
48    ];
49
50    /**
51     * Action for the first page.
52     */
53    public const ACTION_FIRST = 'first';
54
55    /**
56     * Action for the previous page.
57     */
58    public const ACTION_PREVIOUS = 'previous';
59
60    /**
61     * Action for the next page.
62     */
63    public const ACTION_NEXT = 'next';
64
65    /**
66     * Action for the last page.
67     */
68    public const ACTION_LAST = 'last';
69
70    /**
71     * The ID for the pager.
72     */
73    protected string $id = '';
74
75    /**
76     * Available options for the number of results displayed per page.
77     */
78    protected array $paginationSizeOptions = [
79        5,
80        10,
81        25,
82        50,
83        100,
84    ];
85
86    /**
87     * Default pagination size.
88     */
89    protected int $paginationSizeDefault = 10;
90
91    /**
92     * Total number of pages in the dataset.
93     */
94    protected int $totalPages = 1;
95
96    /**
97     * Total number of results in the dataset.
98     */
99    protected int $totalResults = 0;
100
101    /**
102     * Position of the pagination controls ('top', 'bottom', or 'both').
103     */
104    protected string $position = 'bottom';
105
106    /**
107     * Array of additional attributes for the pager.
108     */
109    protected array $attributes = [];
110
111    /**
112     * Number of results to display per page.
113     */
114    protected int $limit = 10;
115
116    /**
117     * Offset of the current page.
118     */
119    protected ?int $currentOffset = null;
120
121    /**
122     * Offset for the next page.
123     */
124    protected ?int $nextOffset = null;
125
126    /**
127     * Offset for the previous page.
128     */
129    protected ?int $prevOffset = null;
130
131    /**
132     * Offset for the first page.
133     */
134    protected ?int $firstOffset = 0;
135
136    /**
137     * Offset for the last page.
138     */
139    protected ?int $lastOffset = null;
140
141    /**
142     * Start ordinal for the current page.
143     */
144    protected int $startOrdinal = 1;
145
146    /**
147     * End ordinal for the current page.
148     */
149    protected int $endOrdinal = 1;
150
151    /**
152     * The renderer instance used to render the pager.
153     */
154    protected PagerRenderer $renderer;
155
156    /**
157     * Constructor for the PagerBuilder class.
158     *
159     * @param PagerRenderer $renderer The renderer to use for rendering the pager.
160     */
161    public function __construct( PagerRenderer $renderer ) {
162        $this->renderer = $renderer;
163    }
164
165    /**
166     * Set the Pager's HTML ID attribute.
167     *
168     * @since 0.1.0
169     * @param string $id The ID for the Pager element.
170     * @return $this
171     */
172    public function setId( string $id ): self {
173        $this->id = $id;
174
175        return $this;
176    }
177
178    /**
179     * Set the total number of pages.
180     *
181     * The total number of pages available based on the dataset.
182     *
183     * @since 0.1.0
184     * @param int $totalPages The total number of pages.
185     * @return $this Returns the Pager instance for method chaining.
186     */
187    public function setTotalPages( int $totalPages ): self {
188        $this->totalPages = $totalPages;
189
190        return $this;
191    }
192
193    /**
194     * Set the total number of results.
195     *
196     * The total number of results in the dataset.
197     *
198     * @since 0.1.0
199     * @param int $totalResults The total number of results.
200     * @return $this Returns the Pager instance for method chaining.
201     */
202    public function setTotalResults( int $totalResults ): self {
203        $this->totalResults = $totalResults;
204
205        return $this;
206    }
207
208    /**
209     * Set the limit for the pager.
210     *
211     * The number of results to be displayed per page. The limit must be at least 1.
212     *
213     * @since 0.1.0
214     * @param int $limit The number of results per page.
215     * @return $this Returns the Pager instance for method chaining.
216     */
217    public function setLimit( int $limit ): self {
218        if ( $limit < 1 ) {
219            throw new InvalidArgumentException( 'The limit must be at least 1.' );
220        }
221
222        $this->limit = $limit;
223
224        return $this;
225    }
226
227    /**
228     * Set the current offset for the pager.
229     *
230     * This method sets the current offset, typically a timestamp or unique
231     * identifier, for cursor-based pagination. The offset represents the
232     * position in the dataset from which to start fetching the next page
233     * of results.
234     *
235     * Example usage:
236     *
237     *     $pager->setCurrentOffset('20240918135942');
238     *
239     * @since 0.1.0
240     * @param ?int $currentOffset The offset value (usually a timestamp).
241     * @return $this Returns the Pager instance for method chaining.
242     */
243    public function setCurrentOffset( ?int $currentOffset ): self {
244        $this->currentOffset = $currentOffset;
245
246        return $this;
247    }
248
249    /**
250     * Set the offset for the first page.
251     *
252     * This method sets the offset for the first page in cursor-based
253     * pagination. It usually represents the earliest timestamp in the
254     * dataset.
255     *
256     * Example usage:
257     *
258     *     $pager->setFirstOffset('20240918135942');
259     *
260     * @since 0.1.0
261     * @param ?int $firstOffset The offset for the first page.
262     * @return $this Returns the Pager instance for method chaining.
263     */
264    public function setFirstOffset( ?int $firstOffset ): self {
265        $this->firstOffset = $firstOffset;
266
267        return $this;
268    }
269
270    /**
271     * Set the offset for the previous page.
272     *
273     * This method sets the offset for the previous page in cursor-based
274     * pagination. The offset is typically the timestamp of the first
275     * item in the current page.
276     *
277     * Example usage:
278     *
279     *     $pager->setPrevOffset('20240918135942');
280     *
281     * @since 0.1.0
282     * @param ?int $prevOffset The offset for the previous page.
283     * @return $this Returns the Pager instance for method chaining.
284     */
285    public function setPrevOffset( ?int $prevOffset ): self {
286        $this->prevOffset = $prevOffset;
287
288        return $this;
289    }
290
291    /**
292     * Set the offset for the next page.
293     *
294     * This method sets the offset for the next page in cursor-based
295     * pagination. It is typically the timestamp of the last item on the
296     * current page.
297     *
298     * Example usage:
299     *
300     *     $pager->setNextOffset('20240918135942');
301     *
302     * @since 0.1.0
303     * @param ?int $nextOffset The offset for the next page.
304     * @return $this Returns the Pager instance for method chaining.
305     */
306    public function setNextOffset( ?int $nextOffset ): self {
307        $this->nextOffset = $nextOffset;
308
309        return $this;
310    }
311
312    /**
313     * Set the offset for the last page.
314     *
315     * This method sets the offset for the last page in cursor-based
316     * pagination. It typically represents the timestamp of the last
317     * item in the dataset.
318     *
319     * Example usage:
320     *
321     *     $pager->setLastOffset('20240918135942');
322     *
323     * @since 0.1.0
324     * @param ?int $lastOffset The offset for the last page.
325     * @return $this Returns the Pager instance for method chaining.
326     */
327    public function setLastOffset( ?int $lastOffset ): self {
328        $this->lastOffset = $lastOffset;
329
330        return $this;
331    }
332
333    /**
334     * Set the start and end ordinals for the current page.
335     *
336     * This method defines the range of items (ordinals) displayed on the
337     * current page of results. The ordinals represent the 1-based index
338     * of the first and last items shown on the page.
339     *
340     * Ordinals are typically determined based on the current page number
341     * and the limit, which is the number of items per page. The `startOrdinal`
342     * specifies the index of the first item on the page, while `endOrdinal`
343     * specifies the index of the last item. This ensures accurate display
344     * of the current page's item range.
345     *
346     * **Tip**: When working with cursor-based pagination (e.g., based on
347     * timestamps), ordinals can be calculated by determining the position
348     * of the current offset within the dataset. By tracking the relative
349     * position of items using their timestamps, the starting and ending
350     * ordinal values for each page can be derived.
351     *
352     * Example usage:
353     *
354     *     $pager->setOrdinals(6, 10);
355     *
356     * @since 0.1.0
357     * @param int $startOrdinal The 1-based index of the first item displayed.
358     * @param int $endOrdinal The 1-based index of the last item displayed.
359     * @return $this Returns the Pager instance for method chaining.
360     */
361    public function setOrdinals( int $startOrdinal, int $endOrdinal ): self {
362        $this->startOrdinal = $startOrdinal;
363        $this->endOrdinal = $endOrdinal;
364
365        return $this;
366    }
367
368    /**
369     * Set the position for the pager.
370     *
371     * This method specifies where the pagination controls should appear.
372     * Valid positions are 'top', 'bottom', or 'both'.
373     *
374     * Example usage:
375     *
376     *     $pager->setPosition('top');
377     *
378     * @since 0.1.0
379     * @param string $position The position of the pagination controls ('top', 'bottom', or 'both').
380     * @return $this Returns the Pager instance for method chaining.
381     */
382    public function setPosition( string $position ): self {
383        if ( !in_array( $position, self::TABLE_PAGINATION_POSITIONS, true ) ) {
384            throw new InvalidArgumentException( "Invalid pagination position: $position" );
385        }
386        $this->position = $position;
387
388        return $this;
389    }
390
391    /**
392     * Set the pagination size options.
393     *
394     * This method defines the available options for the number of results displayed per page.
395     * Users can select from these options in a dropdown, and the selected value will control
396     * how many items are displayed on each page.
397     *
398     * Example usage:
399     *
400     *     $pager->setPaginationSizeOptions([10, 20, 50]);
401     *
402     * @since 0.1.0
403     * @param array $paginationSizeOptions The array of pagination size options.
404     * @return $this Returns the Pager instance for method chaining.
405     */
406    public function setPaginationSizeOptions( array $paginationSizeOptions ): self {
407        if ( !$paginationSizeOptions ) {
408            throw new InvalidArgumentException( 'Pagination size options cannot be empty.' );
409        }
410        $this->paginationSizeOptions = $paginationSizeOptions;
411
412        return $this;
413    }
414
415    /**
416     * Set the default pagination size.
417     *
418     * This method specifies the default number of rows displayed per page.
419     *
420     * @since 0.1.0
421     * @param int $paginationSizeDefault The default number of rows per page.
422     * @return $this Returns the Table instance for method chaining.
423     */
424    public function setPaginationSizeDefault( int $paginationSizeDefault ): self {
425        if ( !in_array( $paginationSizeDefault, $this->paginationSizeOptions, true ) ) {
426            throw new InvalidArgumentException( 'Default pagination size must be one of the pagination size options.' );
427        }
428        $this->paginationSizeDefault = $paginationSizeDefault;
429
430        return $this;
431    }
432
433    /**
434     * Build and return the Pager component object.
435     * This method constructs the immutable Pager object with all the properties set via the builder.
436     *
437     * @since 0.1.0
438     * @return Pager The constructed Pager.
439     */
440    public function build(): Pager {
441        return new Pager(
442            $this->id,
443            $this->paginationSizeOptions,
444            $this->paginationSizeDefault,
445            $this->totalPages,
446            $this->totalResults,
447            $this->position,
448            $this->attributes,
449            $this->limit,
450            $this->currentOffset,
451            $this->nextOffset,
452            $this->prevOffset,
453            $this->firstOffset,
454            $this->lastOffset,
455            $this->startOrdinal,
456            $this->endOrdinal,
457            $this->renderer
458        );
459    }
460}