• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nette / forms / 20836962018

09 Jan 2026 12:35AM UTC coverage: 93.481%. Remained the same
20836962018

push

github

dg
Container::setValues() and setDefaults() accepts array|Traversable|stdClass (BC break)

3 of 4 new or added lines in 1 file covered. (75.0%)

44 existing lines in 9 files now uncovered.

2065 of 2209 relevant lines covered (93.48%)

0.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.78
/src/Forms/Container.php
1
<?php
2

3
/**
4
 * This file is part of the Nette Framework (https://nette.org)
5
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6
 */
7

8
declare(strict_types=1);
9

10
namespace Nette\Forms;
11

12
use Nette;
13
use Nette\Utils\ArrayHash;
14
use Stringable;
15
use function array_combine, array_key_exists, array_map, array_merge, array_unique, explode, func_get_args, in_array, is_object, iterator_to_array, str_contains;
16

17

18
/**
19
 * Container for form controls.
20
 *
21
 * @property   ArrayHash $values
22
 * @property-read \Iterator $controls
23
 * @property-read Form|null $form
24
 */
25
class Container extends Nette\ComponentModel\Container implements \ArrayAccess
26
{
27
        use Nette\ComponentModel\ArrayAccess;
28

29
        public const Array = 'array';
30

31
        /**
32
         * Occurs when the form was validated
33
         * @var array<callable(self, array|object): void|callable(array|object): void>
34
         */
35
        public array $onValidate = [];
36
        protected ?ControlGroup $currentGroup = null;
37

38
        /** @var callable[]  extension methods */
39
        private static array $extMethods = [];
40
        private ?bool $validated = false;
41
        private ?string $mappedType = null;
42

43

44
        /********************* data exchange ****************d*g**/
45

46

47
        /**
48
         * Fill-in with default values.
49
         * @param  array|\Traversable|\stdClass  $values
50
         */
51
        public function setDefaults(array|object $values, bool $erase = false): static
1✔
52
        {
53
                $form = $this->getForm(throw: false);
1✔
54
                $this->setValues($values, $erase, $form?->isAnchored() && $form->isSubmitted());
1✔
55
                return $this;
1✔
56
        }
57

58

59
        /**
60
         * Fill-in with values.
61
         * @param  array|\Traversable|\stdClass  $values
62
         * @internal
63
         */
64
        public function setValues(array|object $values, bool $erase = false, bool $onlyDisabled = false): static
1✔
65
        {
66
                if (is_object($values) && !($values instanceof \Traversable || $values instanceof \stdClass)) {
1✔
NEW
67
                        trigger_error(__METHOD__ . ': argument should be array|Traversable|stdClass, ' . get_debug_type($values) . ' given.');
×
68
                }
69

70
                $values = $values instanceof \Traversable
1✔
71
                        ? iterator_to_array($values)
1✔
72
                        : (array) $values;
1✔
73

74
                foreach ($this->getComponents() as $name => $control) {
1✔
75
                        if ($control instanceof Control) {
1✔
76
                                if ((array_key_exists($name, $values) && (!$onlyDisabled || $control->isDisabled())) || $erase) {
1✔
77
                                        $control->setValue($values[$name] ?? null);
1✔
78
                                }
79
                        } elseif ($control instanceof self) {
1✔
80
                                if (isset($values[$name]) || $erase) {
1✔
81
                                        $control->setValues($values[$name] ?? [], $erase, $onlyDisabled);
1✔
82
                                }
83
                        }
84
                }
85

86
                return $this;
1✔
87
        }
88

89

90
        /**
91
         * Returns the values submitted by the form.
92
         * @param  Control[]|null  $controls
93
         */
94
        public function getValues(string|object|null $returnType = null, ?array $controls = null): object|array
1✔
95
        {
96
                $form = $this->getForm(throw: false);
1✔
97
                if ($form && ($submitter = $form->isSubmitted())) {
1✔
98
                        if ($this->validated === null) {
1✔
UNCOV
99
                                throw new Nette\InvalidStateException('You cannot call getValues() during the validation process. Use getUntrustedValues() instead.');
×
100

101
                        } elseif (!$this->isValid()) {
1✔
UNCOV
102
                                trigger_error(__METHOD__ . "() invoked but the form is not valid (form '{$this->getName()}').", E_USER_WARNING);
×
103
                        }
104

105
                        if ($controls === null && $submitter instanceof SubmitterControl) {
1✔
106
                                $controls = $submitter->getValidationScope();
1✔
107
                                if ($controls !== null && !in_array($this, $controls, strict: true)) {
1✔
108
                                        $scope = $this;
1✔
109
                                        while (($scope = $scope->getParent()) instanceof self) {
1✔
UNCOV
110
                                                if (in_array($scope, $controls, strict: true)) {
×
UNCOV
111
                                                        $controls[] = $this;
×
UNCOV
112
                                                        break;
×
113
                                                }
114
                                        }
115
                                }
116
                        }
117
                }
118

119
                return $this->getUntrustedValues($returnType, $controls);
1✔
120
        }
121

122

123
        /**
124
         * Returns the potentially unvalidated values submitted by the form.
125
         * @param  Control[]|null  $controls
126
         */
127
        public function getUntrustedValues(string|object|null $returnType = null, ?array $controls = null): object|array
1✔
128
        {
129
                if (is_object($returnType)) {
1✔
130
                        $resultObj = $returnType;
1✔
131
                        $properties = (new \ReflectionClass($resultObj))->getProperties();
1✔
132

133
                } else {
134
                        $returnType ??= $this->mappedType ?? ArrayHash::class;
1✔
135
                        $rc = new \ReflectionClass($returnType === self::Array ? \stdClass::class : $returnType);
1✔
136
                        $constructor = $rc->hasMethod('__construct') ? $rc->getMethod('__construct') : null;
1✔
137
                        if ($constructor?->getNumberOfRequiredParameters()) {
1✔
138
                                $resultObj = new \stdClass;
1✔
139
                                $properties = $constructor->getParameters();
1✔
140
                        } else {
141
                                $constructor = null;
1✔
142
                                $resultObj = $rc->newInstance();
1✔
143
                                $properties = $rc->getProperties();
1✔
144
                        }
145
                }
146

147
                $properties = array_combine(array_map(fn($p) => $p->getName(), $properties), $properties);
1✔
148

149
                foreach ($this->getComponents() as $name => $control) {
1✔
150
                        $allowed = $controls === null || in_array($this, $controls, strict: true) || in_array($control, $controls, strict: true);
1✔
151
                        $name = (string) $name;
1✔
152
                        $property = $properties[$name] ?? null;
1✔
153
                        if (
154
                                $control instanceof Control
1✔
155
                                && $allowed
1✔
156
                                && !$control->isOmitted()
1✔
157
                        ) {
158
                                $resultObj->$name = Helpers::tryEnumConversion($control->getValue(), $property);
1✔
159

160
                        } elseif ($control instanceof self) {
1✔
161
                                $type = $returnType === self::Array && !$control->mappedType
1✔
162
                                        ? self::Array
1✔
163
                                        : ($property ? Helpers::getSingleType($property) : null);
1✔
164
                                $resultObj->$name = $control->getUntrustedValues($type, $allowed ? null : $controls);
1✔
165
                        }
166
                }
167

168
                return match (true) {
169
                        isset($constructor) => new $returnType(...(array) $resultObj),
1✔
170
                        $returnType === self::Array => (array) $resultObj,
1✔
171
                        default => $resultObj,
1✔
172
                };
173
        }
174

175

176
        /** @deprecated use getUntrustedValues() */
177
        public function getUnsafeValues($returnType, ?array $controls = null)
178
        {
UNCOV
179
                return $this->getUntrustedValues($returnType, $controls);
×
180
        }
181

182

183
        public function setMappedType(string $type): static
1✔
184
        {
185
                $this->mappedType = $type;
1✔
186
                return $this;
1✔
187
        }
188

189

190
        /********************* validation ****************d*g**/
191

192

193
        /**
194
         * Is form valid?
195
         */
196
        public function isValid(): bool
197
        {
198
                if ($this->validated === null) {
1✔
UNCOV
199
                        throw new Nette\InvalidStateException('You cannot call isValid() during the validation process.');
×
200

201
                } elseif (!$this->validated) {
1✔
202
                        if ($this->getErrors()) {
1✔
203
                                return false;
1✔
204
                        }
205

206
                        $this->validate();
1✔
207
                }
208

209
                return !$this->getErrors();
1✔
210
        }
211

212

213
        /**
214
         * Performs the server side validation.
215
         * @param  (Control|self)[]|null  $controls
216
         */
217
        public function validate(?array $controls = null): void
1✔
218
        {
219
                $this->validated = null;
1✔
220
                foreach ($controls ?? $this->getComponents() as $control) {
1✔
221
                        if ($control instanceof Control || $control instanceof self) {
1✔
222
                                $control->validate();
1✔
223
                        }
224
                }
225

226
                $this->validated = true;
1✔
227

228
                foreach ($this->onValidate as $handler) {
1✔
229
                        $params = Nette\Utils\Callback::toReflection($handler)->getParameters();
1✔
230
                        $types = array_map(Helpers::getSingleType(...), $params);
1✔
231
                        $args = isset($types[0]) && !$this instanceof $types[0]
1✔
232
                                ? [$this->getUntrustedValues($types[0])]
1✔
233
                                : [$this, isset($params[1]) ? $this->getUntrustedValues($types[1]) : null];
1✔
234
                        $handler(...$args);
1✔
235
                }
236
        }
1✔
237

238

239
        /**
240
         * Returns all validation errors.
241
         * @return string[]
242
         */
243
        public function getErrors(): array
244
        {
245
                $errors = [];
1✔
246
                foreach ($this->getControls() as $control) {
1✔
247
                        $errors = array_merge($errors, $control->getErrors());
1✔
248
                }
249

250
                return array_unique($errors);
1✔
251
        }
252

253

254
        /********************* form building ****************d*g**/
255

256

257
        public function setCurrentGroup(?ControlGroup $group = null): static
1✔
258
        {
259
                $this->currentGroup = $group;
1✔
260
                return $this;
1✔
261
        }
262

263

264
        /**
265
         * Returns current group.
266
         */
267
        public function getCurrentGroup(): ?ControlGroup
268
        {
UNCOV
269
                return $this->currentGroup;
×
270
        }
271

272

273
        /**
274
         * Adds the specified component to the IContainer.
275
         * @throws Nette\InvalidStateException
276
         */
277
        public function addComponent(
1✔
278
                Nette\ComponentModel\IComponent $component,
279
                ?string $name,
280
                ?string $insertBefore = null,
281
        ): static
282
        {
283
                parent::addComponent($component, $name, $insertBefore);
1✔
284
                $this->currentGroup?->add($component);
1✔
285
                return $this;
1✔
286
        }
287

288

289
        /**
290
         * Iterates over all form controls.
291
         * @return \Iterator<int, Control>
292
         */
293
        public function getControls(): iterable
294
        {
295
                return Nette\Utils\Iterables::repeatable(function () {
1✔
296
                        foreach ($this->getComponentTree() as $component) {
1✔
297
                                if ($component instanceof Control) {
1✔
298
                                        yield $component->getName() => $component;
1✔
299
                                }
300
                        }
301
                });
1✔
302
        }
303

304

305
        /**
306
         * Returns form.
307
         * @return ($throw is true ? Form : ?Form)
308
         */
309
        public function getForm(bool $throw = true): ?Form
1✔
310
        {
311
                return $this->lookup(Form::class, $throw);
1✔
312
        }
313

314

315
        /********************* control factories ****************d*g**/
316

317

318
        /**
319
         * Adds single-line text input control to the form.
320
         */
321
        public function addText(
1✔
322
                string $name,
323
                string|Stringable|null $label = null,
324
                ?int $cols = null,
325
                ?int $maxLength = null,
326
        ): Controls\TextInput
327
        {
328
                return $this[$name] = (new Controls\TextInput($label, $maxLength))
1✔
329
                        ->setHtmlAttribute('size', $cols);
1✔
330
        }
331

332

333
        /**
334
         * Adds single-line text input control used for sensitive input such as passwords.
335
         */
336
        public function addPassword(
1✔
337
                string $name,
338
                string|Stringable|null $label = null,
339
                ?int $cols = null,
340
                ?int $maxLength = null,
341
        ): Controls\TextInput
342
        {
343
                return $this[$name] = (new Controls\TextInput($label, $maxLength))
1✔
344
                        ->setHtmlAttribute('size', $cols)
1✔
345
                        ->setHtmlType('password');
1✔
346
        }
347

348

349
        /**
350
         * Adds multi-line text input control to the form.
351
         */
352
        public function addTextArea(
1✔
353
                string $name,
354
                string|Stringable|null $label = null,
355
                ?int $cols = null,
356
                ?int $rows = null,
357
        ): Controls\TextArea
358
        {
359
                return $this[$name] = (new Controls\TextArea($label))
1✔
360
                        ->setHtmlAttribute('cols', $cols)->setHtmlAttribute('rows', $rows);
1✔
361
        }
362

363

364
        /**
365
         * Adds input for email.
366
         */
367
        public function addEmail(
1✔
368
                string $name,
369
                string|Stringable|null $label = null,
370
                int $maxLength = 255,
371
        ): Controls\TextInput
372
        {
373
                return $this[$name] = (new Controls\TextInput($label, $maxLength))
1✔
374
                        ->addRule(Form::Email);
1✔
375
        }
376

377

378
        /**
379
         * Adds input for integer.
380
         */
381
        public function addInteger(string $name, string|Stringable|null $label = null): Controls\TextInput
1✔
382
        {
383
                return $this[$name] = (new Controls\TextInput($label))
1✔
384
                        ->setNullable()
1✔
385
                        ->addRule(Form::Integer);
1✔
386
        }
387

388

389
        /**
390
         * Adds input for float.
391
         */
392
        public function addFloat(string $name, string|Stringable|null $label = null): Controls\TextInput
1✔
393
        {
394
                return $this[$name] = (new Controls\TextInput($label))
1✔
395
                        ->setNullable()
1✔
396
                        ->setHtmlType('number')
1✔
397
                        ->setHtmlAttribute('step', 'any')
1✔
398
                        ->addRule(Form::Float);
1✔
399
        }
400

401

402
        /**
403
         * Adds input for date selection.
404
         */
405
        public function addDate(string $name, string|object|null $label = null): Controls\DateTimeControl
1✔
406
        {
407
                return $this[$name] = new Controls\DateTimeControl($label, Controls\DateTimeControl::TypeDate);
1✔
408
        }
409

410

411
        /**
412
         * Adds input for time selection.
413
         */
414
        public function addTime(
1✔
415
                string $name,
416
                string|object|null $label = null,
417
                bool $withSeconds = false,
418
        ): Controls\DateTimeControl
419
        {
420
                return $this[$name] = new Controls\DateTimeControl($label, Controls\DateTimeControl::TypeTime, $withSeconds);
1✔
421
        }
422

423

424
        /**
425
         * Adds input for date and time selection.
426
         */
427
        public function addDateTime(
1✔
428
                string $name,
429
                string|object|null $label = null,
430
                bool $withSeconds = false,
431
        ): Controls\DateTimeControl
432
        {
433
                return $this[$name] = new Controls\DateTimeControl($label, Controls\DateTimeControl::TypeDateTime, $withSeconds);
1✔
434
        }
435

436

437
        /**
438
         * Adds control that allows the user to upload files.
439
         */
440
        public function addUpload(string $name, string|Stringable|null $label = null): Controls\UploadControl
1✔
441
        {
442
                return $this[$name] = new Controls\UploadControl($label, multiple: false);
1✔
443
        }
444

445

446
        /**
447
         * Adds control that allows the user to upload multiple files.
448
         */
449
        public function addMultiUpload(string $name, string|Stringable|null $label = null): Controls\UploadControl
1✔
450
        {
451
                return $this[$name] = new Controls\UploadControl($label, multiple: true);
1✔
452
        }
453

454

455
        /**
456
         * Adds hidden form control used to store a non-displayed value.
457
         */
458
        public function addHidden(string $name, $default = null): Controls\HiddenField
1✔
459
        {
460
                return $this[$name] = (new Controls\HiddenField)
1✔
461
                        ->setDefaultValue($default);
1✔
462
        }
463

464

465
        /**
466
         * Adds check box control to the form.
467
         */
468
        public function addCheckbox(string $name, string|Stringable|null $caption = null): Controls\Checkbox
1✔
469
        {
470
                return $this[$name] = new Controls\Checkbox($caption);
1✔
471
        }
472

473

474
        /**
475
         * Adds set of radio button controls to the form.
476
         */
477
        public function addRadioList(
1✔
478
                string $name,
479
                string|Stringable|null $label = null,
480
                ?array $items = null,
481
        ): Controls\RadioList
482
        {
483
                return $this[$name] = new Controls\RadioList($label, $items);
1✔
484
        }
485

486

487
        /**
488
         * Adds set of checkbox controls to the form.
489
         */
490
        public function addCheckboxList(
1✔
491
                string $name,
492
                string|Stringable|null $label = null,
493
                ?array $items = null,
494
        ): Controls\CheckboxList
495
        {
496
                return $this[$name] = new Controls\CheckboxList($label, $items);
1✔
497
        }
498

499

500
        /**
501
         * Adds select box control that allows single item selection.
502
         */
503
        public function addSelect(
1✔
504
                string $name,
505
                string|Stringable|null $label = null,
506
                ?array $items = null,
507
                ?int $size = null,
508
        ): Controls\SelectBox
509
        {
510
                return $this[$name] = (new Controls\SelectBox($label, $items))
1✔
511
                        ->setHtmlAttribute('size', $size > 1 ? $size : null);
1✔
512
        }
513

514

515
        /**
516
         * Adds select box control that allows multiple item selection.
517
         */
518
        public function addMultiSelect(
1✔
519
                string $name,
520
                string|Stringable|null $label = null,
521
                ?array $items = null,
522
                ?int $size = null,
523
        ): Controls\MultiSelectBox
524
        {
525
                return $this[$name] = (new Controls\MultiSelectBox($label, $items))
1✔
526
                        ->setHtmlAttribute('size', $size > 1 ? $size : null);
1✔
527
        }
528

529

530
        /**
531
         * Adds color picker.
532
         */
533
        public function addColor(string $name, string|Stringable|null $label = null): Controls\ColorPicker
1✔
534
        {
535
                return $this[$name] = new Controls\ColorPicker($label);
1✔
536
        }
537

538

539
        /**
540
         * Adds button used to submit form.
541
         */
542
        public function addSubmit(string $name, string|Stringable|null $caption = null): Controls\SubmitButton
1✔
543
        {
544
                return $this[$name] = new Controls\SubmitButton($caption);
1✔
545
        }
546

547

548
        /**
549
         * Adds push buttons with no default behavior.
550
         */
551
        public function addButton(string $name, string|Stringable|null $caption = null): Controls\Button
1✔
552
        {
553
                return $this[$name] = new Controls\Button($caption);
1✔
554
        }
555

556

557
        /**
558
         * Adds graphical button used to submit form.
559
         * @param  string|null  $src  URI of the image
560
         * @param  string|null  $alt  alternate text for the image
561
         */
562
        public function addImageButton(string $name, ?string $src = null, ?string $alt = null): Controls\ImageButton
1✔
563
        {
564
                return $this[$name] = new Controls\ImageButton($src, $alt);
1✔
565
        }
566

567

568
        /** @deprecated  use addImageButton() */
569
        public function addImage(): Controls\ImageButton
570
        {
UNCOV
571
                return $this->addImageButton(...func_get_args());
×
572
        }
573

574

575
        /**
576
         * Adds naming container to the form.
577
         */
578
        public function addContainer(string|int $name): self
1✔
579
        {
580
                $control = new self;
1✔
581
                $control->currentGroup = $this->currentGroup;
1✔
582
                $this->currentGroup?->add($control);
1✔
583
                return $this[$name] = $control;
1✔
584
        }
585

586

587
        /********************* extension methods ****************d*g**/
588

589

590
        public function __call(string $name, array $args)
1✔
591
        {
592
                if (isset(self::$extMethods[$name])) {
1✔
593
                        return (self::$extMethods[$name])($this, ...$args);
1✔
594
                }
595

UNCOV
596
                return parent::__call($name, $args);
×
597
        }
598

599

600
        public static function extensionMethod(string $name, /*callable*/ $callback): void
1✔
601
        {
602
                if (str_contains($name, '::')) { // back compatibility
1✔
UNCOV
603
                        [, $name] = explode('::', $name);
×
604
                }
605

606
                self::$extMethods[$name] = $callback;
1✔
607
        }
1✔
608

609

610
        /**
611
         * Prevents cloning.
612
         */
613
        public function __clone()
614
        {
UNCOV
615
                throw new Nette\NotImplementedException('Form cloning is not supported yet.');
×
616
        }
617
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc