Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
265 / 265
100.00% covered (success)
100.00%
38 / 38
CRAP
100.00% covered (success)
100.00%
1 / 1
Fieldset
100.00% covered (success)
100.00%
265 / 265
100.00% covered (success)
100.00%
38 / 38
130
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 createFromConfig
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 setContainer
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setCurrent
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 createGroup
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 addField
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 addFields
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 insertFieldBefore
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 insertFieldAfter
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 count
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 toArray
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getLegend
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getContainer
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCurrent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasField
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 getField
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getFields
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFieldGroups
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAllFields
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getFieldValue
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 setLegend
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setFieldValue
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 setFieldValues
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getIterator
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 prepare
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
5
 prepareForView
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
10
 prepareFieldAccessibility
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
1 / 1
8
 prepareTable
100.00% covered (success)
100.00%
39 / 39
100.00% covered (success)
100.00%
1 / 1
16
 prepareElement
100.00% covered (success)
100.00%
31 / 31
100.00% covered (success)
100.00%
1 / 1
15
 prepareDl
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
1 / 1
15
 __set
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __get
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __isset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __unset
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 offsetExists
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 offsetGet
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 offsetSet
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 offsetUnset
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;
16
17use Pop\Dom\Child;
18use Pop\Form\Element\AbstractElement;
19use ArrayIterator;
20
21/**
22 * Form fieldset 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 */
31
32class Fieldset extends Child implements \ArrayAccess, \Countable, \IteratorAggregate
33{
34
35    /**
36     * Form field elements
37     * @var array
38     */
39    protected array $fields = [];
40
41    /**
42     * Current field group
43     * @var int
44     */
45    protected int $current = 0;
46
47    /**
48     * Fieldset legend
49     * @var ?string
50     */
51    protected ?string $legend = null;
52
53    /**
54     * Fieldset container (dl, table, div or p)
55     * @var string
56     */
57    protected string $container = 'dl';
58
59    /**
60     * Constructor
61     *
62     * Instantiate the form fieldset object
63     *
64     * @param  ?array $fields
65     * @param  ?string $container
66     */
67    public function __construct(?array $fields = null, ?string $container = 'dl')
68    {
69        parent::__construct('fieldset');
70        if ($container !== null) {
71            $this->setContainer($container);
72        }
73        if ($fields !== null) {
74            $this->addFields($fields);
75        }
76    }
77
78    /**
79     * Method to create form fieldset object and fields from config
80     *
81     * @param  array  $config
82     * @return Fieldset
83     */
84    public static function createFromConfig(array $config): Fieldset
85    {
86        $fields = [];
87
88        foreach ($config as $name => $field) {
89            $fields[$name] = Fields::create($name, $field);
90        }
91
92        return new static($fields);
93    }
94
95    /**
96     * Method to set container
97     *
98     * @param  string $container
99     * @return Fieldset
100     */
101    public function setContainer(string $container): Fieldset
102    {
103        $this->container = strtolower($container);
104        return $this;
105    }
106
107    /**
108     * Method to get current group index
109     *
110     * @param  int $i
111     * @return Fieldset
112     */
113    public function setCurrent(int $i): Fieldset
114    {
115        $this->current = (int)$i;
116        if (!isset($this->fields[$this->current])) {
117            $this->fields[$this->current] = [];
118        }
119        return $this;
120    }
121
122    /**
123     * Method to create new group
124     *
125     * @return Fieldset
126     */
127    public function createGroup(): Fieldset
128    {
129        $this->current++;
130        $this->fields[$this->current] = [];
131        return $this;
132    }
133
134    /**
135     * Method to add a form field
136     *
137     * @param  AbstractElement $field
138     * @return Fieldset
139     */
140    public function addField(AbstractElement $field): Fieldset
141    {
142        if (!isset($this->fields[$this->current])) {
143            $this->fields[$this->current] = [];
144        }
145        $this->fields[$this->current][$field->getName()] = $field;
146        return $this;
147    }
148
149    /**
150     * Method to add form fields
151     *
152     * @param  array $fields
153     * @return Fieldset
154     */
155    public function addFields(array $fields): Fieldset
156    {
157        foreach ($fields as $field) {
158            $this->addField($field);
159        }
160        return $this;
161    }
162
163    /**
164     * Method to insert a field before another one
165     *
166     * @param  string          $name
167     * @param  AbstractElement $field
168     * @return Fieldset
169     */
170    public function insertFieldBefore(string $name, AbstractElement $field): Fieldset
171    {
172        foreach ($this->fields as $i => $group) {
173            $fields = [];
174            foreach ($group as $key => $value) {
175                if ($key == $name) {
176                    $fields[$field->getName()] = $field;
177                    $fields[$key] = $value;
178                } else {
179                    $fields[$key] = $value;
180                }
181            }
182            $this->fields[$i] = $fields;
183        }
184
185        return $this;
186    }
187
188    /**
189     * Method to insert a field after another one
190     *
191     * @param  string          $name
192     * @param  AbstractElement $field
193     * @return Fieldset
194     */
195    public function insertFieldAfter(string $name, AbstractElement $field): Fieldset
196    {
197        foreach ($this->fields as $i => $group) {
198            $fields = [];
199            foreach ($group as $key => $value) {
200                if ($key == $name) {
201                    $fields[$key] = $value;
202                    $fields[$field->getName()] = $field;
203                } else {
204                    $fields[$key] = $value;
205                }
206            }
207            $this->fields[$i] = $fields;
208        }
209
210        return $this;
211    }
212
213    /**
214     * Method to get the count of elements in the form fieldset
215     *
216     * @return int
217     */
218    public function count(): int
219    {
220        $count = 0;
221        foreach ($this->fields as $group) {
222            $count += count($group);
223        }
224        return $count;
225    }
226
227    /**
228     * Method to get the field values as an array
229     *
230     * @return array
231     */
232    public function toArray(): array
233    {
234        $fieldValues = [];
235
236        foreach ($this->fields as $group) {
237            foreach ($group as $name => $field) {
238                $fieldValues[$name] = $field->getValue();
239            }
240        }
241
242        return $fieldValues;
243    }
244
245    /**
246     * Method to get fieldset legend
247     *
248     * @return string|null
249     */
250    public function getLegend(): string|null
251    {
252        return $this->legend;
253    }
254
255    /**
256     * Method to get container
257     *
258     * @return string
259     */
260    public function getContainer(): string
261    {
262        return $this->container;
263    }
264
265    /**
266     * Method to get current group index
267     *
268     * @return int
269     */
270    public function getCurrent(): int
271    {
272        return $this->current;
273    }
274
275    /**
276     * Method to determine if the fieldset has a field
277     *
278     * @param  string $name
279     * @return bool
280     */
281    public function hasField(string $name): bool
282    {
283        $result = false;
284        foreach ($this->fields as $key => $fields) {
285            if (isset($fields[$name])) {
286                $result = true;
287                break;
288            }
289        }
290        return $result;
291    }
292
293    /**
294     * Method to get a field element object
295     *
296     * @param  string $name
297     * @return AbstractElement
298     */
299    public function getField(string $name): AbstractElement
300    {
301        $result = null;
302        foreach ($this->fields as $key => $fields) {
303            if (isset($fields[$name])) {
304                $result = $fields[$name];
305            }
306        }
307        return $result;
308    }
309
310    /**
311     * Method to get field element objects in a group
312     *
313     * @param  int  $i
314     * @return array|null
315     */
316    public function getFields(int $i): array|null
317    {
318        return $this->fields[$i] ?? null;
319    }
320
321    /**
322     * Method to get all field element groups
323     *
324     * @return array
325     */
326    public function getFieldGroups(): array
327    {
328        return $this->fields;
329    }
330
331    /**
332     * Method to get all field elements
333     *
334     * @return array
335     */
336    public function getAllFields(): array
337    {
338        $fields = [];
339        foreach ($this->fields as $group) {
340            foreach ($group as $field) {
341                $fields[$field->getName()] = $field;
342            }
343        }
344        return $fields;
345    }
346
347    /**
348     * Method to get a field element value
349     *
350     * @param  string $name
351     * @return mixed
352     */
353    public function getFieldValue(string $name): mixed
354    {
355        $result = null;
356        foreach ($this->fields as $key => $fields) {
357            if (isset($fields[$name])) {
358                $result = $this->fields[$key][$name]->getValue();
359            }
360        }
361        return $result;
362    }
363
364    /**
365     * Method to set fieldset legend
366     *
367     * @param  string $legend
368     * @return Fieldset
369     */
370    public function setLegend(string $legend): Fieldset
371    {
372        $this->legend = $legend;
373        return $this;
374    }
375
376    /**
377     * Method to set a field element value
378     *
379     * @param  string $name
380     * @param  mixed  $value
381     * @return Fieldset
382     */
383    public function setFieldValue(string $name, mixed $value): Fieldset
384    {
385        foreach ($this->fields as $key => $fields) {
386            if (isset($fields[$name])) {
387                $this->fields[$key][$name]->setValue($value);
388            }
389        }
390        return $this;
391    }
392
393    /**
394     * Method to set field element values
395     *
396     * @param  array $values
397     * @return Fieldset
398     */
399    public function setFieldValues(array $values): Fieldset
400    {
401        foreach ($values as $name => $value) {
402            $this->setFieldValue($name, $value);
403        }
404        return $this;
405    }
406
407    /**
408     * Method to iterate over the form elements
409     *
410     * @return ArrayIterator
411     */
412    public function getIterator(): ArrayIterator
413    {
414        return new ArrayIterator($this->toArray());
415    }
416
417    /**
418     * Prepare fieldset object for rendering
419     *
420     * @return Fieldset
421     */
422    public function prepare(): Fieldset
423    {
424        if (!empty($this->legend)) {
425            $this->addChild(new Child('legend', $this->legend));
426        }
427
428        switch ($this->container) {
429            case 'table':
430                $this->prepareTable();
431                break;
432            case 'dl':
433                $this->prepareDl();
434                break;
435            default:
436                $this->prepareElement($this->container);
437        }
438
439        return $this;
440    }
441
442    /**
443     * Prepare fieldset elements for rendering with a view
444     *
445     * @return array
446     */
447    public function prepareForView(): array
448    {
449        $fields = [];
450
451        foreach ($this->fields as $groups) {
452            foreach ($groups as $field) {
453                [, $hint] = $this->prepareFieldAccessibility($field);
454
455                if ($field->hasLabel()) {
456                    $labelFor = $field->getName() . (($field->getNodeName() == 'fieldset') ? '1' : '');
457                    $label    = new Child('label', $field->getLabel());
458                    $label->setAttribute('for', $labelFor);
459                    if ($field->getLabelAttributes() !== null) {
460                        $label->setAttributes($field->getLabelAttributes());
461                    }
462                    if ($field->isRequired()) {
463                        if ($label->hasAttribute('class')) {
464                            $label->setAttribute('class', $label->getAttribute('class') . ' required');
465                        } else {
466                            $label->setAttribute('class', 'required');
467                        }
468                    }
469                    $fields[$field->getName() . '_label'] = $label->render();
470                }
471
472                if ($hint !== null) {
473                    $fields[$field->getName() . '_hint'] = $hint->render();
474                }
475
476                if ($field->hasErrors()) {
477                    $fields[$field->getName() . '_errors'] = $field->getErrors();
478                }
479
480                $fields[$field->getName()] = $field->render();
481            }
482        }
483
484        return $fields;
485    }
486
487    /**
488     * Build a field's error and hint child nodes, wiring aria-describedby/aria-invalid
489     * back onto the field itself so assistive tech announces validation state and hints.
490     *
491     * The error container and hint span get predictable ids (`{name}-error`/`{name}-hint`,
492     * with any `[]` stripped from the field name) so custom view templates can replicate
493     * the same association if they render the error/hint content themselves.
494     *
495     * @param  AbstractElement $field
496     * @return array
497     */
498    protected function prepareFieldAccessibility(AbstractElement $field): array
499    {
500        $baseId      = str_replace('[]', '', (string)$field->getName());
501        $isFieldset  = ($field->getNodeName() == 'fieldset');
502        $describedBy = [];
503        $hint        = null;
504        $errors      = null;
505
506        if ($field->hasHint()) {
507            $hintId = $baseId . '-hint';
508            $hint   = new Child('span', $field->getHint());
509            if ($field->hasHintAttributes()) {
510                $hint->setAttributes($field->getHintAttributes());
511            }
512            $hint->setAttribute('id', $hintId);
513            $describedBy[] = $hintId;
514        }
515
516        if ($field->hasErrors()) {
517            $errorId = $baseId . '-error';
518            $errors  = new Child('div');
519            $errors->setAttribute('class', 'error');
520            $errors->setAttribute('id', $errorId);
521            $errors->setAttribute('role', 'alert');
522            foreach ($field->getErrors() as $error) {
523                $errors->addChild(new Child('span', $error));
524            }
525            $describedBy[] = $errorId;
526
527            // aria-invalid is only valid on actual form controls, not on the grouping
528            // <fieldset> that composite elements like CheckboxSet/RadioSet render as.
529            if (!$isFieldset) {
530                $field->setAttribute('aria-invalid', 'true');
531            }
532        } else if (!$isFieldset) {
533            $field->removeAttribute('aria-invalid');
534        }
535
536        if (!empty($describedBy)) {
537            $field->setAttribute('aria-describedby', implode(' ', $describedBy));
538        } else {
539            $field->removeAttribute('aria-describedby');
540        }
541
542        return [$errors, $hint];
543    }
544
545    /**
546     * Prepare table
547     *
548     * @return void
549     */
550    protected function prepareTable(): void
551    {
552        foreach ($this->fields as $fields) {
553            $table = new Child('table');
554
555            foreach ($fields as $field) {
556                [$errors, $hint] = $this->prepareFieldAccessibility($field);
557
558                $tr = new Child('tr');
559                if ($field->hasLabel()) {
560                    $td = new Child('td');
561                    $labelFor = $field->getName() . (($field->getNodeName() == 'fieldset') ? '1' : '');
562
563                    $label = new Child('label', $field->getLabel());
564                    $label->setAttribute('for', $labelFor);
565                    if ($field->hasLabelAttributes()) {
566                        $label->setAttributes($field->getLabelAttributes());
567                    }
568                    if ($field->isRequired()) {
569                        if ($label->hasAttribute('class')) {
570                            $label->setAttribute('class', $label->getAttribute('class') . ' required');
571                        } else {
572                            $label->setAttribute('class', 'required');
573                        }
574                    }
575                    $td->addChild($label);
576                    $tr->addChild($td);
577                }
578
579                $nodeValue = null;
580                $options   = [];
581                if ($field->hasAppend()) {
582                    $nodeValue = $field->getAppend();
583                    $options   = ['childrenFirst' => true];
584                } else if ($field->hasPrepend()) {
585                    $nodeValue = $field->getPrepend();
586                    $options   = ['childrenFirst' => false];
587                }
588
589                $td = new Child('td', $nodeValue, $options);
590
591                if (!empty($errors) && ($field->isErrorPre())) {
592                    $td->addChild($errors);
593                }
594                $td->addChild($field);
595
596                if ($hint !== null) {
597                    $td->addChild($hint);
598                }
599
600                if ($field->hasLabel()) {
601                    $td->setAttribute('colspan', 2);
602                }
603                if (!empty($errors) && (!$field->isErrorPre())) {
604                    $td->addChild($errors);
605                }
606                $tr->addChild($td);
607                $table->addChild($tr);
608            }
609
610            $this->addChild($table);
611        }
612    }
613
614    /**
615     * Prepare DIV or P
616     *
617     * @param  string $element
618     * @return void
619     */
620    protected function prepareElement(string $element): void
621    {
622        foreach ($this->fields as $fields) {
623            foreach ($fields as $field) {
624                [$errors, $hint] = $this->prepareFieldAccessibility($field);
625
626                $nodeValue = null;
627                $options   = [];
628                if ($field->hasAppend()) {
629                    $nodeValue = $field->getAppend();
630                    $options   = ['childrenFirst' => true];
631                } else if ($field->hasPrepend()) {
632                    $nodeValue = $field->getPrepend();
633                    $options   = ['childrenFirst' => false];
634                }
635
636                $container = new Child($element, $nodeValue, $options);
637
638                if ($field->hasLabel()) {
639                    $labelFor = $field->getName() . (($field->getNodeName() == 'fieldset') ? '1' : '');
640                    $label    = new Child('label', $field->getLabel());
641                    $label->setAttribute('for', $labelFor);
642                    if ($field->hasLabelAttributes()) {
643                        $label->setAttributes($field->getLabelAttributes());
644                    }
645                    if ($field->isRequired()) {
646                        if ($label->hasAttribute('class')) {
647                            $label->setAttribute('class', $label->getAttribute('class') . ' required');
648                        } else {
649                            $label->setAttribute('class', 'required');
650                        }
651                    }
652                    $container->addChild($label);
653                }
654
655                if (!empty($errors) && ($field->isErrorPre())) {
656                    $container->addChild($errors);
657                }
658                $container->addChild($field);
659
660                if ($hint !== null) {
661                    $container->addChild($hint);
662                }
663                if (!empty($errors) && (!$field->isErrorPre())) {
664                    $container->addChild($errors);
665                }
666                $this->addChild($container);
667            }
668        }
669    }
670
671    /**
672     * Prepare DL
673     *
674     * @return void
675     */
676    protected function prepareDl(): void
677    {
678        foreach ($this->fields as $fields) {
679            $dl = new Child('dl');
680
681            foreach ($fields as $field) {
682                [$errors, $hint] = $this->prepareFieldAccessibility($field);
683
684                if ($field->hasLabel()) {
685                    $dt = new Child('dt');
686                    $labelFor = $field->getName() . (($field->getNodeName() == 'fieldset') ? '1' : '');
687
688                    $label = new Child('label', $field->getLabel());
689                    $label->setAttribute('for', $labelFor);
690                    if ($field->hasLabelAttributes()) {
691                        $label->setAttributes($field->getLabelAttributes());
692                    }
693                    if ($field->isRequired()) {
694                        if ($label->hasAttribute('class')) {
695                            $label->setAttribute('class', $label->getAttribute('class') . ' required');
696                        } else {
697                            $label->setAttribute('class', 'required');
698                        }
699                    }
700                    $dt->addChild($label);
701                    $dl->addChild($dt);
702                }
703
704                $nodeValue = null;
705                $options   = [];
706                if ($field->hasAppend()) {
707                    $nodeValue = $field->getAppend();
708                    $options   = ['childrenFirst' => true];
709                } else if ($field->hasPrepend()) {
710                    $nodeValue = $field->getPrepend();
711                    $options   = ['childrenFirst' => false];
712                }
713
714                $dd = new Child('dd', $nodeValue, $options);
715
716                if (!empty($errors) && ($field->isErrorPre())) {
717                    $dd->addChild($errors);
718                }
719                $dd->addChild($field);
720
721                if ($hint !== null) {
722                    $dd->addChild($hint);
723                }
724                if (!empty($errors) && (!$field->isErrorPre())) {
725                    $dd->addChild($errors);
726                }
727                $dl->addChild($dd);
728            }
729
730            $this->addChild($dl);
731        }
732    }
733
734    /**
735     * Set method to set the property to the value of fields[$name]
736     *
737     * @param  string $name
738     * @param  mixed $value
739     * @return void
740     */
741    public function __set(string $name, mixed $value): void
742    {
743        $this->setFieldValue($name, $value);
744    }
745
746    /**
747     * Get method to return the value of fields[$name]
748     *
749     * @param  string $name
750     * @return mixed
751     */
752    public function __get(string $name): mixed
753    {
754        return $this->getFieldValue($name);
755    }
756
757    /**
758     * Return the isset value of fields[$name]
759     *
760     * @param  string $name
761     * @return bool
762     */
763    public function __isset(string $name): bool
764    {
765        return $this->hasField($name);
766    }
767
768    /**
769     * Unset fields[$name]
770     *
771     * @param  string $name
772     * @return void
773     */
774    public function __unset(string $name): void
775    {
776        foreach ($this->fields as $i => $group) {
777            foreach ($group as $key => $value) {
778                if ($key == $name) {
779                    unset($this->fields[$i][$key]);
780                }
781            }
782        }
783    }
784
785    /**
786     * ArrayAccess offsetExists
787     *
788     * @param  mixed $offset
789     * @return bool
790     */
791    public function offsetExists(mixed $offset): bool
792    {
793        return $this->__isset($offset);
794    }
795
796    /**
797     * ArrayAccess offsetGet
798     *
799     * @param  mixed $offset
800     * @return mixed
801     */
802    public function offsetGet(mixed $offset): mixed
803    {
804        return $this->__get($offset);
805    }
806
807    /**
808     * ArrayAccess offsetSet
809     *
810     * @param  mixed $offset
811     * @param  mixed $value
812     * @return void
813     */
814    public function offsetSet(mixed $offset, mixed $value): void
815    {
816        $this->__set($offset, $value);
817    }
818
819    /**
820     * ArrayAccess offsetUnset
821     *
822     * @param  mixed $offset
823     * @return void
824     */
825    public function offsetUnset(mixed $offset): void
826    {
827        $this->__unset($offset);
828    }
829
830}