Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 57
0.00% covered (danger)
0.00%
0 / 19
CRAP
0.00% covered (danger)
0.00%
0 / 1
TableBuilder
0.00% covered (danger)
0.00%
0 / 57
0.00% covered (danger)
0.00%
0 / 19
506
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
 setCaption
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setHideCaption
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setColumns
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setData
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setUseRowHeaders
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setShowVerticalBorders
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setSort
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setPaginate
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setTotalRows
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setPaginationPosition
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setAttributes
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 setPager
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setFooter
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setHeaderContent
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setCurrentSortColumn
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setCurrentSortDirection
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 build
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * TableBuilder.php
4 *
5 * This file is part of the Codex design system, the official design system
6 * for Wikimedia projects. It provides the `Table` class, a builder for constructing
7 * table components using the Codex design system.
8 *
9 * Tables are used to arrange data in rows and columns, facilitating the comparison,
10 * analysis, and management of information.
11 *
12 * @category Builder
13 * @package  Codex\Builder
14 * @since    0.1.0
15 * @author   Doğu Abaris <abaris@null.net>
16 * @license  https://www.gnu.org/copyleft/gpl.html GPL-2.0-or-later
17 * @link     https://doc.wikimedia.org/codex/main/ Codex Documentation
18 */
19
20namespace Wikimedia\Codex\Builder;
21
22use Wikimedia\Codex\Component\HtmlSnippet;
23use Wikimedia\Codex\Component\Pager;
24use Wikimedia\Codex\Component\Table;
25use Wikimedia\Codex\Renderer\TableRenderer;
26
27/**
28 * TableBuilder
29 *
30 * This class implements the builder pattern to construct instances of Table.
31 * It provides a fluent interface for setting various properties and building the
32 * final immutable object with predefined configurations and immutability.
33 *
34 * @category Builder
35 * @package  Codex\Builder
36 * @since    0.1.0
37 * @author   Doğu Abaris <abaris@null.net>
38 * @license  https://www.gnu.org/copyleft/gpl.html GPL-2.0-or-later
39 * @link     https://doc.wikimedia.org/codex/main/ Codex Documentation
40 */
41class TableBuilder {
42
43    /**
44     * Sort direction for ascending order.
45     */
46    public const SORT_ASCENDING = 'asc';
47
48    /**
49     * Sort direction for descending order.
50     */
51    public const SORT_DESCENDING = 'desc';
52
53    /**
54     * The ID for the table.
55     */
56    protected string $id = '';
57
58    /**
59     * Caption for the table.
60     */
61    protected string $caption = '';
62
63    /**
64     * Flag to hide or show the caption.
65     */
66    protected bool $hideCaption = false;
67
68    /**
69     * Content for the table header.
70     */
71    protected string|HtmlSnippet|null $headerContent = null;
72
73    /**
74     * Array of columns in the table.
75     */
76    protected array $columns = [];
77
78    /**
79     * Array of data rows in the table.
80     */
81    protected array $data = [];
82
83    /**
84     * Flag to use row headers.
85     */
86    protected bool $useRowHeaders = false;
87
88    /**
89     * Sorting configuration.
90     */
91    protected array $sort = [];
92
93    /**
94     * Currently sorted column.
95     */
96    protected ?string $currentSortColumn = null;
97
98    /**
99     * Current sort direction.
100     */
101    protected string $currentSortDirection = self::SORT_ASCENDING;
102
103    /**
104     * Flag to show vertical borders.
105     */
106    protected bool $showVerticalBorders = false;
107
108    /**
109     * Array of additional attributes.
110     */
111    protected array $attributes = [];
112
113    /**
114     * Flag to enable pagination.
115     */
116    protected bool $paginate = false;
117
118    /**
119     * Total number of rows for pagination.
120     */
121    protected int $totalRows = 0;
122
123    /**
124     * Position of the pagination controls ('top', 'bottom', or 'both').
125     */
126    protected string $paginationPosition = 'bottom';
127
128    /**
129     * Pager object for handling pagination.
130     */
131    protected ?Pager $pager = null;
132
133    /**
134     * Content for the table footer.
135     */
136    protected string|HtmlSnippet|null $footer = null;
137
138    /**
139     * The renderer instance used to render the table.
140     */
141    protected TableRenderer $renderer;
142
143    /**
144     * Constructor for the TableBuilder class.
145     *
146     * @param TableRenderer $renderer The renderer to use for rendering the table.
147     */
148    public function __construct( TableRenderer $renderer ) {
149        $this->renderer = $renderer;
150    }
151
152    /**
153     * Set the Table HTML ID attribute.
154     *
155     * @since 0.1.0
156     * @param string $id The ID for the Table element.
157     * @return $this
158     */
159    public function setId( string $id ): self {
160        $this->id = $id;
161
162        return $this;
163    }
164
165    /**
166     * Set the caption for the table.
167     *
168     * The caption provides a description of the table's contents and purpose. It is essential for accessibility
169     * as it helps screen readers convey the context of the table to users. To visually hide the caption while
170     * keeping it accessible, use the `setHideCaption()` method.
171     *
172     * Example usage:
173     *
174     *     $table->setCaption('Article List');
175     *
176     * @since 0.1.0
177     * @param string $caption The caption text to be displayed above the table.
178     * @return $this Returns the Table instance for method chaining.
179     */
180    public function setCaption( string $caption ): self {
181        $this->caption = $caption;
182
183        return $this;
184    }
185
186    /**
187     * Set whether to hide the caption.
188     *
189     * If set to true, the caption will be visually hidden but still accessible to screen readers.
190     *
191     * @since 0.1.0
192     * @param bool $hideCaption Indicates if the caption should be visually hidden.
193     * @return $this Returns the Table instance for method chaining.
194     */
195    public function setHideCaption( bool $hideCaption ): self {
196        $this->hideCaption = $hideCaption;
197
198        return $this;
199    }
200
201    /**
202     * Set the columns for the table.
203     *
204     * Each column is defined by an associative array with attributes such as 'id', 'label', 'sortable', etc.
205     * The 'label' can be a string (plain text) or an HtmlSnippet object (raw HTML).
206     *
207     * Example usage:
208     *
209     *     $table->setColumns([
210     *         ['id' => 'title', 'label' => 'Title', 'sortable' => true],
211     *         ['id' => 'creation_date', 'label' => 'Creation Date', 'sortable' => false]
212     *     ]);
213     *
214     * @since 0.1.0
215     * @param array $columns An array of columns, where each column is an associative array containing column
216     *                       attributes.
217     * @return $this Returns the Table instance for method chaining.
218     */
219    public function setColumns( array $columns ): self {
220        $this->columns = $columns;
221
222        return $this;
223    }
224
225    /**
226     * Set the data for the table.
227     *
228     * The data array should correspond to the columns defined. Each row is an associative array where keys match
229     * column IDs. The values can be strings (plain text) or HtmlSnippet objects (raw HTML).
230     *
231     * Example usage:
232     *
233     *     $table->setData([
234     *         ['title' => 'Mercury', 'creation_date' => '2024-01-01'],
235     *         ['title' => 'Venus', 'creation_date' => '2024-01-02'],
236     *     ]);
237     *
238     * @since 0.1.0
239     * @param array $data An array of data to be displayed in the table, where each row is an associative array with
240     *                    keys matching column IDs.
241     * @return $this Returns the Table instance for method chaining.
242     */
243    public function setData( array $data ): self {
244        $this->data = $data;
245
246        return $this;
247    }
248
249    /**
250     * Set whether to use row headers.
251     *
252     * If enabled, the first column of the table will be treated as row headers. This is useful for accessibility
253     * and to provide additional context for each row.
254     *
255     * @since 0.1.0
256     * @param bool $useRowHeaders Indicates if row headers should be used.
257     * @return $this Returns the Table instance for method chaining.
258     */
259    public function setUseRowHeaders( bool $useRowHeaders ): self {
260        $this->useRowHeaders = $useRowHeaders;
261
262        return $this;
263    }
264
265    /**
266     * Set whether to show vertical borders between columns.
267     *
268     * Vertical borders can help distinguish between columns, especially in tables with many columns.
269     *
270     * @since 0.1.0
271     * @param bool $showVerticalBorders Indicates if vertical borders should be displayed between columns.
272     * @return $this Returns the Table instance for method chaining.
273     */
274    public function setShowVerticalBorders( bool $showVerticalBorders ): self {
275        $this->showVerticalBorders = $showVerticalBorders;
276
277        return $this;
278    }
279
280    /**
281     * Set the sort order for the table.
282     *
283     * This method defines the initial sort order for the table. The array should contain
284     * column IDs as keys and sort directions ('asc' or 'desc') as values.
285     *
286     * Example usage:
287     *
288     *     $table->setSort([
289     *         'column1' => 'asc',
290     *         'column2' => 'desc'
291     *     ]);
292     *
293     * @since 0.1.0
294     * @param array $sort An associative array of column IDs and their respective sort directions ('asc' or 'desc').
295     * @return $this Returns the Table instance for method chaining.
296     */
297    public function setSort( array $sort ): self {
298        $this->sort = $sort;
299
300        return $this;
301    }
302
303    /**
304     * Set whether the table should be paginated.
305     *
306     * If enabled, pagination controls will be added to the table, allowing users to navigate through multiple pages of
307     * data.
308     *
309     * @since 0.1.0
310     * @param bool $paginate Indicates if the table should be paginated.
311     * @return $this Returns the Table instance for method chaining.
312     */
313    public function setPaginate( bool $paginate ): self {
314        $this->paginate = $paginate;
315
316        return $this;
317    }
318
319    /**
320     * Set the total number of rows in the table.
321     *
322     * This value is used in conjunction with pagination to calculate the total number of pages and to display the
323     * current range of rows.
324     *
325     * @since 0.1.0
326     * @param int $totalRows The total number of rows in the table.
327     * @return $this Returns the Table instance for method chaining.
328     */
329    public function setTotalRows( int $totalRows ): self {
330        $this->totalRows = $totalRows;
331
332        return $this;
333    }
334
335    /**
336     * Set the position of the pagination controls.
337     *
338     * The pagination controls can be displayed at the top, bottom, or both top and bottom of the table.
339     *
340     * @since 0.1.0
341     * @param string $paginationPosition The position of the pagination controls ('top', 'bottom', 'both').
342     * @return $this Returns the Table instance for method chaining.
343     */
344    public function setPaginationPosition( string $paginationPosition ): self {
345        $this->paginationPosition = $paginationPosition;
346
347        return $this;
348    }
349
350    /**
351     * Set additional HTML attributes for the table element.
352     *
353     * This method allows custom HTML attributes to be added to the `<table>` element, such as `id`, `class`,
354     * or `data-*` attributes. These attributes are automatically escaped to prevent XSS vulnerabilities.
355     *
356     * Example usage:
357     *
358     *     $table->setAttributes(['class' => 'custom-table-class', 'data-info' => 'additional-info']);
359     *
360     * @since 0.1.0
361     * @param array $attributes An associative array of HTML attributes to be added to the `<table>` element.
362     *
363     * @return $this Returns the Table instance for method chaining.
364     */
365    public function setAttributes( array $attributes ): self {
366        foreach ( $attributes as $key => $value ) {
367            $this->attributes[$key] = $value;
368        }
369        return $this;
370    }
371
372    /**
373     * Set the Pager instance for the table.
374     *
375     * The Pager instance provides pagination controls for the table. If set, pagination controls will be rendered
376     * according to the settings.
377     *
378     * @since 0.1.0
379     * @param Pager $pager The Pager instance.
380     * @return $this Returns the Table instance for method chaining.
381     */
382    public function setPager( Pager $pager ): self {
383        $this->pager = $pager;
384
385        return $this;
386    }
387
388    /**
389     * Set the footer content for the table.
390     *
391     * The footer is an optional section that can contain additional information or actions related to the table.
392     *
393     * @since 0.1.0
394     * @param string|HtmlSnippet $footer The footer content.
395     * @return $this Returns the Table instance for method chaining.
396     */
397    public function setFooter( string|HtmlSnippet $footer ): self {
398        $this->footer = $footer;
399
400        return $this;
401    }
402
403    /**
404     * Set the header content for the table.
405     *
406     * This method allows custom content to be added to the table's header, such as actions or additional text.
407     *
408     * Example usage:
409     *
410     *     $table->setHeaderContent('Custom Actions');
411     *
412     * @since 0.1.0
413     * @param string|HtmlSnippet $headerContent The content to be displayed in the table header.
414     * @return $this Returns the Table instance for method chaining.
415     */
416    public function setHeaderContent( string|HtmlSnippet $headerContent ): self {
417        $this->headerContent = $headerContent;
418
419        return $this;
420    }
421
422    /**
423     * Set the current sort column.
424     *
425     * This method specifies which column is currently being used for sorting the table data.
426     * The column with this ID will be marked as sorted in the table header.
427     *
428     * Example usage:
429     *
430     *     $table->setCurrentSortColumn('title');
431     *
432     * @since 0.1.0
433     * @param string $currentSortColumn The ID of the column used for sorting.
434     *
435     * @return $this Returns the Table instance for method chaining.
436     */
437    public function setCurrentSortColumn( string $currentSortColumn ): self {
438        $this->currentSortColumn = $currentSortColumn;
439
440        return $this;
441    }
442
443    /**
444     * Set the current sort direction.
445     *
446     * This method specifies the direction for sorting the table data. Acceptable values are 'asc' for ascending
447     * and 'desc' for descending. The method validates these values to ensure they are correct.
448     *
449     * Example usage:
450     *
451     *     $table->setCurrentSortDirection('asc');
452     *
453     * @since 0.1.0
454     * @param string $currentSortDirection The sort direction ('asc' or 'desc').
455     *
456     * @return $this Returns the Table instance for method chaining.
457     */
458    public function setCurrentSortDirection( string $currentSortDirection ): self {
459        if ( $currentSortDirection === self::SORT_ASCENDING || $currentSortDirection === self::SORT_DESCENDING ) {
460            $this->currentSortDirection = $currentSortDirection;
461        }
462
463        return $this;
464    }
465
466    /**
467     * Build and return the Table component object.
468     * This method constructs the immutable Table object with all the properties set via the builder.
469     *
470     * @since 0.1.0
471     * @return Table The constructed Table.
472     */
473    public function build(): Table {
474        return new Table(
475            $this->id,
476            $this->caption,
477            $this->hideCaption,
478            $this->columns,
479            $this->data,
480            $this->useRowHeaders,
481            $this->headerContent,
482            $this->sort,
483            $this->currentSortColumn,
484            $this->currentSortDirection,
485            $this->showVerticalBorders,
486            $this->attributes,
487            $this->paginate,
488            $this->totalRows,
489            $this->paginationPosition,
490            $this->pager,
491            $this->footer,
492            $this->renderer
493        );
494    }
495}