Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
78 / 78
100.00% covered (success)
100.00%
46 / 46
CRAP
100.00% covered (success)
100.00%
1 / 1
AbstractElement
100.00% covered (success)
100.00%
78 / 78
100.00% covered (success)
100.00%
46 / 46
61
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setName
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setValue
n/a
0 / 0
n/a
0 / 0
0
 resetValue
n/a
0 / 0
n/a
0 / 0
0
 setLabel
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setHint
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setPrepend
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setAppend
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setLabelAttribute
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setLabelAttributes
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setHintAttribute
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setHintAttributes
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setRequired
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 setRequiredMessage
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setDisabled
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setReadonly
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setErrorPre
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 isErrorPre
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setValidators
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 clearErrors
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getType
n/a
0 / 0
n/a
0 / 0
0
 getValue
n/a
0 / 0
n/a
0 / 0
0
 getLabel
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasLabel
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getHint
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasHint
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPrepend
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasPrepend
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAppend
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasAppend
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getLabelAttributes
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasLabelAttributes
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getHintAttributes
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasHintAttributes
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getValidators
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasValidators
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isRequired
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasRequiredMessage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRequiredMessage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isDisabled
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isReadonly
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isButton
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
4
 getErrors
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasErrors
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addValidator
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 addValidators
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 validateValue
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
5
 validateCallable
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 validate
n/a
0 / 0
n/a
0 / 0
0
 __toString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2declare(strict_types=1);
3/**
4 * Pop PHP Framework (https://www.popphp.org/)
5 *
6 * @link       https://github.com/popphp/popphp-framework
7 * @author     Nick Sagona, III <nick@popphp.org>
8 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\Form\Element;
16
17use Pop\Dom\Child;
18use Pop\Validator;
19use Pop\Form\ValidatorEvaluationTrait;
20
21/**
22 * Abstract form element class
23 *
24 * @category   Pop
25 * @package    Pop\Form
26 * @author     Nick Sagona, III <nick@popphp.org>
27 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
28 * @license    https://www.popphp.org/license     New BSD License
29 * @version    5.0.0
30 */
31abstract class AbstractElement extends Child implements ElementInterface
32{
33
34    use ValidatorEvaluationTrait;
35
36    /**
37     * Element name
38     * @var ?string
39     */
40    protected ?string $name = null;
41
42    /**
43     * Form element label
44     * @var ?string
45     */
46    protected ?string $label = null;
47
48    /**
49     * Form element hint
50     * @var ?string
51     */
52    protected ?string $hint = null;
53
54    /**
55     * Form element label attributes
56     * @var array
57     */
58    protected array $labelAttributes = [];
59
60    /**
61     * Form element hint attributes
62     * @var array
63     */
64    protected array $hintAttributes = [];
65
66    /**
67     * Form element prepend contents
68     * @var ?string
69     */
70    protected ?string $prepend = null;
71
72    /**
73     * Form element append contents
74     * @var ?string
75     */
76    protected ?string $append = null;
77
78    /**
79     * Form element required property
80     * @var bool
81     */
82    protected bool $required = false;
83
84    /**
85     * Form element required message
86     * @var ?string
87     */
88    protected ?string $requiredMessage = null;
89
90    /**
91     * Form element disabled property
92     * @var bool
93     */
94    protected bool $disabled = false;
95
96    /**
97     * Form element readonly property
98     * @var bool
99     */
100    protected bool $readonly = false;
101
102    /**
103     * Form element validators
104     * @var array
105     */
106    protected array $validators = [];
107
108    /**
109     * Form element error display position
110     * @var bool
111     */
112    protected bool $errorPre = false;
113
114    /**
115     * Form element errors
116     * @var array
117     */
118    protected array $errors = [];
119
120    /**
121     * Constructor
122     *
123     * Instantiate the form element object
124     *
125     * @param  string  $name
126     * @param  ?string $value
127     * @param  array   $options
128     */
129    public function __construct(string $name, ?string $value = null, array $options = [])
130    {
131        parent::__construct($name, $value, $options);
132    }
133
134    /**
135     * Set the name of the form element object
136     *
137     * @param  string $name
138     * @return AbstractElement
139     */
140    public function setName(string $name): AbstractElement
141    {
142        $this->name = $name;
143        return $this;
144    }
145
146    /**
147     * Set the value of the form element
148     *
149     * @param  mixed $value
150     * @return AbstractElement
151     */
152    abstract public function setValue(mixed $value = null): AbstractElement;
153
154    /**
155     * Reset the value of the form element
156     *
157     * @return AbstractElement
158     */
159    abstract public function resetValue(): AbstractElement;
160
161    /**
162     * Set the label of the form element object
163     *
164     * @param  string $label
165     * @return AbstractElement
166     */
167    public function setLabel(string $label): AbstractElement
168    {
169        $this->label = $label;
170        return $this;
171    }
172
173    /**
174     * Set the hint of the form element object
175     *
176     * @param  string $hint
177     * @return AbstractElement
178     */
179    public function setHint(string $hint): AbstractElement
180    {
181        $this->hint = $hint;
182        return $this;
183    }
184
185    /**
186     * Set the prepend contents of the form element object
187     *
188     * @param  string $prepend
189     * @return AbstractElement
190     */
191    public function setPrepend(string $prepend): AbstractElement
192    {
193        $this->prepend = $prepend;
194        return $this;
195    }
196
197    /**
198     * Set the append contents of the form element object
199     *
200     * @param  string $append
201     * @return AbstractElement
202     */
203    public function setAppend(string $append): AbstractElement
204    {
205        $this->append = $append;
206        return $this;
207    }
208
209    /**
210     * Set an attribute of the label of the form element object
211     *
212     * @param  string $a
213     * @param  string $v
214     * @return AbstractElement
215     */
216    public function setLabelAttribute(string $a, string $v): AbstractElement
217    {
218        $this->labelAttributes[$a] = $v;
219        return $this;
220    }
221
222    /**
223     * Set the attributes of the label of the form element object
224     *
225     * @param  array $attribs
226     * @return AbstractElement
227     */
228    public function setLabelAttributes(array $attribs): AbstractElement
229    {
230        foreach ($attribs as $a => $v) {
231            $this->setLabelAttribute($a, $v);
232        }
233        return $this;
234    }
235
236    /**
237     * Set an attribute of the hint of the form element object
238     *
239     * @param  string $a
240     * @param  string $v
241     * @return AbstractElement
242     */
243    public function setHintAttribute(string $a, string $v): AbstractElement
244    {
245        $this->hintAttributes[$a] = $v;
246        return $this;
247    }
248
249    /**
250     * Set the attributes of the hint of the form element object
251     *
252     * @param  array $attribs
253     * @return AbstractElement
254     */
255    public function setHintAttributes(array $attribs): AbstractElement
256    {
257        foreach ($attribs as $a => $v) {
258            $this->setHintAttribute($a, $v);
259        }
260        return $this;
261    }
262
263    /**
264     * Set whether the form element is required
265     *
266     * @param  bool    $required
267     * @param  ?string $requiredMessage
268     * @return AbstractElement
269     */
270    public function setRequired(bool $required, ?string $requiredMessage = 'This field is required.'): AbstractElement
271    {
272        $this->required = $required;
273
274        if (!empty($requiredMessage)) {
275            $this->setRequiredMessage($requiredMessage);
276        }
277
278        return $this;
279    }
280
281    /**
282     * Set the form element is required message
283     *
284     * @param  string $requiredMessage
285     * @return AbstractElement
286     */
287    public function setRequiredMessage(string $requiredMessage): AbstractElement
288    {
289        $this->requiredMessage = $requiredMessage;
290        return $this;
291    }
292
293    /**
294     * Set whether the form element is disabled
295     *
296     * @param  bool $disabled
297     * @return AbstractElement
298     */
299    public function setDisabled(bool $disabled): AbstractElement
300    {
301        $this->disabled = $disabled;
302        return $this;
303    }
304
305    /**
306     * Set whether the form element is readonly
307     *
308     * @param  bool $readonly
309     * @return AbstractElement
310     */
311    public function setReadonly(bool $readonly): AbstractElement
312    {
313        $this->readonly = $readonly;
314        return $this;
315    }
316
317    /**
318     * Set error pre-display
319     *
320     * @param  bool $pre
321     * @return AbstractElement
322     */
323    public function setErrorPre(bool $pre): AbstractElement
324    {
325        $this->errorPre = $pre;
326        return $this;
327    }
328
329    /**
330     * Determine if error to display before the element
331     *
332     * @return bool
333     */
334    public function isErrorPre(): bool
335    {
336        return $this->errorPre;
337    }
338
339    /**
340     * Set validators
341     *
342     * @param  array $validators
343     * @return AbstractElement
344     */
345    public function setValidators(array $validators = []): AbstractElement
346    {
347        $this->validators = $validators;
348        return $this;
349    }
350
351    /**
352     * Clear errors
353     *
354     * @return AbstractElement
355     */
356    public function clearErrors(): AbstractElement
357    {
358        $this->errors = [];
359        return $this;
360    }
361
362    /**
363     * Get form element object name
364     *
365     * @return ?string
366     */
367    public function getName(): ?string
368    {
369        return $this->name;
370    }
371
372    /**
373     * Get form element object type
374     *
375     * @return ?string
376     */
377    abstract public function getType() : ?string;
378
379    /**
380     * Get form element value
381     *
382     * @return mixed
383     */
384    abstract public function getValue(): mixed;
385
386    /**
387     * Get form element object label
388     *
389     * @return ?string
390     */
391    public function getLabel(): ?string
392    {
393        return $this->label;
394    }
395
396    /**
397     * Determine if form element has a label
398     *
399     * @return bool
400     */
401    public function hasLabel(): bool
402    {
403        return !empty($this->label);
404    }
405
406    /**
407     * Get form element object hint
408     *
409     * @return ?string
410     */
411    public function getHint(): ?string
412    {
413        return $this->hint;
414    }
415
416    /**
417     * Determine if form element has a hint
418     *
419     * @return bool
420     */
421    public function hasHint(): bool
422    {
423        return !empty($this->hint);
424    }
425
426    /**
427     * Get form element object prepend contents
428     *
429     * @return ?string
430     */
431    public function getPrepend(): ?string
432    {
433        return $this->prepend;
434    }
435
436    /**
437     * Determine if form element has prepend content
438     *
439     * @return bool
440     */
441    public function hasPrepend(): bool
442    {
443        return !empty($this->prepend);
444    }
445
446    /**
447     * Get form element object append contents
448     *
449     * @return ?string
450     */
451    public function getAppend(): ?string
452    {
453        return $this->append;
454    }
455
456    /**
457     * Determine if form element has append content
458     *
459     * @return bool
460     */
461    public function hasAppend(): bool
462    {
463        return !empty($this->append);
464    }
465
466    /**
467     * Get the attributes of the form element object label
468     *
469     * @return array
470     */
471    public function getLabelAttributes(): array
472    {
473        return $this->labelAttributes;
474    }
475
476    /**
477     * Determine if form element has label attributes
478     *
479     * @return bool
480     */
481    public function hasLabelAttributes(): bool
482    {
483        return !empty($this->labelAttributes);
484    }
485
486    /**
487     * Get the attributes of the form element object hint
488     *
489     * @return array
490     */
491    public function getHintAttributes(): array
492    {
493        return $this->hintAttributes;
494    }
495
496    /**
497     * Determine if form element has hint attributes
498     *
499     * @return bool
500     */
501    public function hasHintAttributes(): bool
502    {
503        return !empty($this->hintAttributes);
504    }
505
506    /**
507     * Get validators
508     *
509     * @return array
510     */
511    public function getValidators(): array
512    {
513        return $this->validators;
514    }
515
516    /**
517     * Determine if form element has validators
518     *
519     * @return bool
520     */
521    public function hasValidators(): bool
522    {
523        return !empty($this->validators);
524    }
525
526    /**
527     * Get whether the form element object is required
528     *
529     * @return bool
530     */
531    public function isRequired(): bool
532    {
533        return $this->required;
534    }
535
536    /**
537     * Does the form element object have a required message
538     *
539     * @return bool
540     */
541    public function hasRequiredMessage(): bool
542    {
543        return !empty($this->requiredMessage);
544    }
545
546    /**
547     * Get the form element object required message
548     *
549     * @return ?string
550     */
551    public function getRequiredMessage(): ?string
552    {
553        return $this->requiredMessage;
554    }
555
556    /**
557     * Get whether the form element object is disabled
558     *
559     * @return bool
560     */
561    public function isDisabled(): bool
562    {
563        return $this->disabled;
564    }
565
566    /**
567     * Get whether the form element object is readonly
568     *
569     * @return bool
570     */
571    public function isReadonly(): bool
572    {
573        return $this->readonly;
574    }
575
576    /**
577     * Get whether the form element object is a button
578     *
579     * @return bool
580     */
581    public function isButton(): bool
582    {
583        return (($this instanceof Button) || ($this instanceof Input\Button) ||
584            ($this instanceof Input\Submit) || ($this instanceof Input\Reset));
585    }
586
587    /**
588     * Get form element object errors
589     *
590     * @return array
591     */
592    public function getErrors(): array
593    {
594        return $this->errors;
595    }
596
597    /**
598     * Get if form element object has errors
599     *
600     * @return bool
601     */
602    public function hasErrors(): bool
603    {
604        return (count($this->errors) > 0);
605    }
606
607    /**
608     * Add a validator the form element
609     *
610     * @param  mixed $validator
611     * @throws Exception
612     * @return AbstractElement
613     */
614    public function addValidator(mixed $validator): AbstractElement
615    {
616        if (!($validator instanceof \Pop\Validator\AbstractValidator) && !is_callable($validator)) {
617            throw new Exception('Error: The validator must be an instance of Pop\Validator\AbstractValidator or a callable object.');
618        }
619        $this->validators[] = $validator;
620        return $this;
621    }
622
623    /**
624     * Add multiple validators the form element
625     *
626     * @param  array $validators
627     * @throws Exception
628     * @return AbstractElement
629     */
630    public function addValidators(array $validators): AbstractElement
631    {
632        foreach ($validators as $validator) {
633            $this->addValidator($validator);
634        }
635        return $this;
636    }
637
638    /**
639     * Validate the value
640     *
641     * @param  mixed $value
642     * @param  array $formValues
643     * @return void
644     */
645    public function validateValue(mixed $value, array $formValues = []): void
646    {
647        // Check field validators
648        if (count($this->validators) > 0) {
649            foreach ($this->validators as $validator) {
650                foreach ($this->evaluateValidator($validator, $value, $formValues) as $message) {
651                    if (!in_array($message, $this->errors)) {
652                        $this->errors[] = $message;
653                    }
654                }
655            }
656        }
657    }
658
659    /**
660     * Validate the value by callable
661     *
662     * @param  callable $validator
663     * @param  mixed    $value
664     * @param  array    $formValues
665     * @return void
666     */
667    public function validateCallable(callable $validator, mixed $value, array $formValues = []): void
668    {
669        foreach ($this->evaluateValidator($validator, $value, $formValues) as $message) {
670            if (!in_array($message, $this->errors)) {
671                $this->errors[] = $message;
672            }
673        }
674    }
675
676    /**
677     * Validate the form element object
678     *
679     * @param  array $formValues
680     * @return bool
681     */
682    abstract public function validate(array $formValues = []): bool;
683
684    /**
685     * Print form element
686     *
687     * @return string
688     */
689    public function __toString(): string
690    {
691        return $this->render();
692    }
693
694}