Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 42
0.00% covered (danger)
0.00%
0 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
LabelBuilder
0.00% covered (danger)
0.00%
0 / 42
0.00% covered (danger)
0.00%
0 / 13
306
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
 setLabelText
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 setInputId
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setOptional
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setVisuallyHidden
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setIsLegend
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setDescription
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setDescriptionId
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 setDisabled
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setIconClass
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
 setId
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 build
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * LabelBuilder.php
4 *
5 * This file is part of the Codex design system, the official design system
6 * for Wikimedia projects. It provides the `Label` class, a builder for constructing
7 * label components using the Codex design system.
8 *
9 * A Label provides a descriptive title for a form input. Having labels is essential when filling out
10 * a form, since each field is associated with its label.
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 InvalidArgumentException;
23use Wikimedia\Codex\Component\Label;
24use Wikimedia\Codex\Renderer\LabelRenderer;
25
26/**
27 * LabelBuilder
28 *
29 * This class implements the builder pattern to construct instances of Label.
30 * It provides a fluent interface for setting various properties and building the
31 * final immutable object with predefined configurations and immutability.
32 *
33 * @category Builder
34 * @package  Codex\Builder
35 * @since    0.1.0
36 * @author   Doğu Abaris <abaris@null.net>
37 * @license  https://www.gnu.org/copyleft/gpl.html GPL-2.0-or-later
38 * @link     https://doc.wikimedia.org/codex/main/ Codex Documentation
39 */
40class LabelBuilder {
41
42    /**
43     * The text displayed inside the label.
44     */
45    protected string $labelText = '';
46
47    /**
48     * The ID of the input/control this label is associated with.
49     */
50    protected string $inputId = '';
51
52    /**
53     * Whether the associated input field is optional.
54     */
55    protected bool $optional = false;
56
57    /**
58     * Whether the label should be visually hidden but accessible to screen readers.
59     */
60    protected bool $visuallyHidden = false;
61
62    /**
63     * Whether the label should be rendered as a `<legend>` element, typically used within a `<fieldset>`.
64     */
65    protected bool $isLegend = false;
66
67    /**
68     * The description text that provides additional information about the input field.
69     */
70    protected string $description = '';
71
72    /**
73     * The ID of the description element, useful for the `aria-describedby` attribute.
74     */
75    protected ?string $descriptionId = null;
76
77    /**
78     * Whether the label is for a disabled field or input.
79     */
80    protected bool $disabled = false;
81
82    /**
83     * The CSS class for an icon displayed before the label text.
84     */
85    protected ?string $iconClass = null;
86
87    /**
88     * Additional HTML attributes for the label element.
89     */
90    protected array $attributes = [];
91
92    /**
93     * The ID attribute for the label.
94     */
95    protected ?string $id = null;
96
97    /**
98     * The renderer instance used to render the label.
99     */
100    protected LabelRenderer $renderer;
101
102    /**
103     * Constructor for the Label class.
104     *
105     * @param LabelRenderer $renderer The renderer to use for rendering the label.
106     */
107    public function __construct( LabelRenderer $renderer ) {
108        $this->renderer = $renderer;
109    }
110
111    /**
112     * Set the label text.
113     *
114     * This method specifies the text that will be displayed inside the label.
115     * The label text provides a descriptive title for the associated input field.
116     *
117     * @since 0.1.0
118     * @param string $labelText The text of the label.
119     * @return $this Returns the Label instance for method chaining.
120     */
121    public function setLabelText( string $labelText ): self {
122        if ( trim( $labelText ) === '' ) {
123            throw new InvalidArgumentException( "Label text cannot be empty." );
124        }
125        $this->labelText = $labelText;
126
127        return $this;
128    }
129
130    /**
131     * Set the ID of the input/control this label is associated with.
132     *
133     * This method sets the 'for' attribute of the label, linking it to an input element.
134     * This connection is important for accessibility and ensures that clicking the label focuses the input.
135     *
136     * Example usage:
137     *
138     *     $label->setInputId('username');
139     *
140     * @since 0.1.0
141     * @param string $inputId The ID of the input element.
142     * @return $this Returns the Label instance for method chaining.
143     */
144    public function setInputId( string $inputId ): self {
145        $this->inputId = $inputId;
146
147        return $this;
148    }
149
150    /**
151     * Set the optional flag.
152     *
153     * This method indicates whether the associated input field is optional.
154     * If true, an "(optional)" flag will be displayed next to the label text.
155     *
156     * Example usage:
157     *
158     *     $label->setOptionalFlag(true);
159     *
160     * @since 0.1.0
161     * @param bool $optional Whether the label is for an optional input.
162     * @return $this Returns the Label instance for method chaining.
163     */
164    public function setOptional( bool $optional ): self {
165        $this->optional = $optional;
166
167        return $this;
168    }
169
170    /**
171     * Set whether the label should be visually hidden.
172     *
173     * This method determines whether the label should be visually hidden while still being accessible to screen
174     * readers. Useful for forms where labels need to be read by assistive technologies but not displayed.
175     *
176     * Example usage:
177     *
178     *     $label->setVisuallyHidden(true);
179     *
180     * @since 0.1.0
181     * @param bool $visuallyHidden Whether the label should be visually hidden.
182     * @return $this Returns the Label instance for method chaining.
183     */
184    public function setVisuallyHidden( bool $visuallyHidden ): self {
185        $this->visuallyHidden = $visuallyHidden;
186
187        return $this;
188    }
189
190    /**
191     * Set whether this component should output a `<legend>` element.
192     *
193     * This method determines whether the label should be rendered as a `<legend>` element,
194     * typically used within a `<fieldset>` for grouping related inputs.
195     *
196     * Example usage:
197     *
198     *     $label->setIsLegend(true);
199     *
200     * @since 0.1.0
201     * @param bool $isLegend Whether to render the label as a `<legend>`.
202     * @return $this Returns the Label instance for method chaining.
203     */
204    public function setIsLegend( bool $isLegend ): self {
205        $this->isLegend = $isLegend;
206
207        return $this;
208    }
209
210    /**
211     * Set the description text for the label.
212     *
213     * This method adds a short description below the label, providing additional information about the input field.
214     * The description is linked to the input via the `aria-describedby` attribute for accessibility.
215     *
216     * Example usage:
217     *
218     *     $label->setDescriptionText('Please enter a valid email.');
219     *
220     * @since 0.1.0
221     * @param string $description The description text for the label.
222     * @return $this Returns the Label instance for method chaining.
223     */
224    public function setDescription( string $description ): self {
225        $this->description = $description;
226
227        return $this;
228    }
229
230    /**
231     * Set the ID of the description element.
232     *
233     * This method sets the ID attribute for the description element, which is useful for associating
234     * the description with an input via the `aria-describedby` attribute.
235     *
236     * Example usage:
237     *
238     *     $label->setDescriptionId('username-desc');
239     *
240     * @since 0.1.0
241     * @param string|null $descriptionId The ID for the description element.
242     * @return $this Returns the Label instance for method chaining.
243     */
244    public function setDescriptionId( ?string $descriptionId ): self {
245        $this->descriptionId = $descriptionId ?: null;
246
247        return $this;
248    }
249
250    /**
251     * Set whether the label is for a disabled field or input.
252     *
253     * This method marks the label as associated with a disabled input, applying the appropriate styles.
254     *
255     * Example usage:
256     *
257     *     $label->setDisabled(true);
258     *
259     * @since 0.1.0
260     * @param bool $disabled Whether the label is for a disabled input.
261     * @return $this Returns the Label instance for method chaining.
262     */
263    public function setDisabled( bool $disabled ): self {
264        $this->disabled = $disabled;
265
266        return $this;
267    }
268
269    /**
270     * Set an icon before the label text.
271     *
272     * This method allows for an icon to be displayed before the label text, specified by a CSS class.
273     * The icon enhances the visual appearance of the label.
274     *
275     * Example usage:
276     *
277     *     $label->setIcon('icon-class-name');
278     *
279     * @since 0.1.0
280     * @param string|null $iconClass The CSS class for the icon.
281     * @return $this Returns the Label instance for method chaining.
282     */
283    public function setIconClass( ?string $iconClass ): self {
284        $this->iconClass = $iconClass;
285
286        return $this;
287    }
288
289    /**
290     * Set additional HTML attributes for the label element.
291     *
292     * This method allows custom HTML attributes to be added to the label element, such as `id`, `class`, or `data-*`
293     * attributes. These attributes are automatically escaped to prevent XSS vulnerabilities.
294     *
295     * Example usage:
296     *
297     *     $label->setAttributes(['class' => 'custom-label-class', 'data-info' => 'additional-info']);
298     *
299     * @since 0.1.0
300     * @param array $attributes An associative array of HTML attributes.
301     * @return $this Returns the Label instance for method chaining.
302     */
303    public function setAttributes( array $attributes ): self {
304        foreach ( $attributes as $key => $value ) {
305            $this->attributes[$key] = $value;
306        }
307
308        return $this;
309    }
310
311    /**
312     * Set the label's HTML ID attribute.
313     *
314     * @since 0.1.0
315     * @param string $id The ID for the label element.
316     * @return $this
317     */
318    public function setId( string $id ): self {
319        $this->id = $id;
320
321        return $this;
322    }
323
324    /**
325     * Build and return the Label component object.
326     * This method constructs the immutable Label object with all the properties set via the builder.
327     *
328     * @since 0.1.0
329     * @return Label The constructed Label.
330     */
331    public function build(): Label {
332        if ( !$this->labelText ) {
333            throw new InvalidArgumentException( "The 'labelText' is required for Label." );
334        }
335
336        return new Label(
337            $this->labelText,
338            $this->inputId,
339            $this->optional,
340            $this->visuallyHidden,
341            $this->isLegend,
342            $this->description,
343            $this->descriptionId,
344            $this->disabled,
345            $this->iconClass,
346            $this->attributes,
347            $this->id,
348            $this->renderer,
349        );
350    }
351}