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

nette / forms / 19834063961

01 Dec 2025 06:58PM UTC coverage: 93.405%. Remained the same
19834063961

push

github

dg
Container::getControls() does not use deprecated parameters

5 of 5 new or added lines in 1 file covered. (100.0%)

4 existing lines in 1 file now uncovered.

2082 of 2229 relevant lines covered (93.41%)

0.93 hits per line

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

92.27
/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
         */
50
        public function setDefaults(array|object $data, bool $erase = false): static
1✔
51
        {
52
                $form = $this->getForm(false);
1✔
53
                $this->setValues($data, $erase, $form?->isAnchored() && $form->isSubmitted());
1✔
54
                return $this;
1✔
55
        }
56

57

58
        /**
59
         * Fill-in with values.
60
         * @internal
61
         */
62
        public function setValues(array|object $values, bool $erase = false, bool $onlyDisabled = false): static
1✔
63
        {
64
                $values = $values instanceof \Traversable
1✔
65
                        ? iterator_to_array($values)
1✔
66
                        : (array) $values;
1✔
67

68
                foreach ($this->getComponents() as $name => $control) {
1✔
69
                        if ($control instanceof Control) {
1✔
70
                                if ((array_key_exists($name, $values) && (!$onlyDisabled || $control->isDisabled())) || $erase) {
1✔
71
                                        $control->setValue($values[$name] ?? null);
1✔
72
                                }
73
                        } elseif ($control instanceof self) {
1✔
74
                                if (isset($values[$name]) || $erase) {
1✔
75
                                        $control->setValues($values[$name] ?? [], $erase, $onlyDisabled);
1✔
76
                                }
77
                        }
78
                }
79

80
                return $this;
1✔
81
        }
82

83

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

95
                        } elseif (!$this->isValid()) {
1✔
96
                                trigger_error(__METHOD__ . "() invoked but the form is not valid (form '{$this->getName()}').", E_USER_WARNING);
×
97
                        }
98

99
                        if ($controls === null && $submitter instanceof SubmitterControl) {
1✔
100
                                $controls = $submitter->getValidationScope();
1✔
101
                                if ($controls !== null && !in_array($this, $controls, true)) {
1✔
102
                                        $scope = $this;
1✔
103
                                        while (($scope = $scope->getParent()) instanceof self) {
1✔
104
                                                if (in_array($scope, $controls, true)) {
×
105
                                                        $controls[] = $this;
×
106
                                                        break;
×
107
                                                }
108
                                        }
109
                                }
110
                        }
111
                }
112

113
                if ($returnType === true) {
1✔
114
                        trigger_error(static::class . '::' . __FUNCTION__ . "(true) is deprecated, use getValues('array').", E_USER_DEPRECATED);
×
115
                        $returnType = self::Array;
×
116
                }
117

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

121

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

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

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

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

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

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

174

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

181

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

188

189
        /********************* validation ****************d*g**/
190

191

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

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

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

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

211

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

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

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

237

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

248
                return array_unique($errors);
1✔
249
        }
250

251

252
        /********************* form building ****************d*g**/
253

254

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

261

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

270

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

286

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

301

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

311

312
        /********************* control factories ****************d*g**/
313

314

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

329

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

345

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

360

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

374

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

385

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

398

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

407

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

420

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

433

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

442

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

451

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

461

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

470

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

483

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

496

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

511

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

526

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

535

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

544

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

553

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

564

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

571

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

583

584
        /********************* extension methods ****************d*g**/
585

586

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

UNCOV
593
                return parent::__call($name, $args);
×
594
        }
595

596

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

603
                self::$extMethods[$name] = $callback;
1✔
604
        }
1✔
605

606

607
        /**
608
         * Prevents cloning.
609
         */
610
        public function __clone()
611
        {
UNCOV
612
                throw new Nette\NotImplementedException('Form cloning is not supported yet.');
×
613
        }
614
}
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