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

nette / forms / 20472712574

23 Dec 2025 10:10PM UTC coverage: 93.282% (-0.1%) from 93.405%
20472712574

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%)

22 existing lines in 4 files now uncovered.

2069 of 2218 relevant lines covered (93.28%)

0.93 hits per line

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

95.14
/src/Forms/Controls/BaseControl.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\Controls;
11

12
use Nette;
13
use Nette\Forms\Control;
14
use Nette\Forms\Form;
15
use Nette\Forms\Rules;
16
use Nette\Utils\Html;
17
use Stringable;
18
use function array_unique, explode, func_get_arg, func_num_args, get_parent_class, implode, is_array, sprintf, str_contains;
19

20

21
/**
22
 * Base class that implements the basic functionality common to form controls.
23
 *
24
 * @property-read Form $form
25
 * @property-deprecated string $htmlName
26
 * @property-deprecated   string|bool|null $htmlId
27
 * @property   mixed $value
28
 * @property-deprecated   string|Stringable $caption
29
 * @property   bool $disabled
30
 * @property-deprecated   bool $omitted
31
 * @property-read Html $control
32
 * @property-read Html $label
33
 * @property-read Html $controlPrototype
34
 * @property-read Html $labelPrototype
35
 * @property   bool $required
36
 * @property-deprecated bool $filled
37
 * @property-read array $errors
38
 * @property-deprecated array $options
39
 * @property-read string $error
40
 */
41
abstract class BaseControl extends Nette\ComponentModel\Component implements Control
42
{
43
        public static string $idMask = 'frm-%s';
44

45
        protected mixed $value = null;
46
        protected Html $control;
47
        protected Html $label;
48

49
        /** @var bool|bool[] */
50
        protected bool|array $disabled = false;
51

52
        /** @var callable[][]  extension methods */
53
        private static array $extMethods = [];
54
        private string|Stringable|null $caption;
55
        private array $errors = [];
56
        private ?bool $omitted = null;
57
        private Rules $rules;
58

59
        /** true means autodetect */
60
        private Nette\Localization\Translator|bool|null $translator = true;
61
        private array $options = [];
62

63

64
        public function __construct(string|Stringable|null $caption = null)
1✔
65
        {
66
                $this->control = Html::el('input', ['type' => null, 'name' => null]);
1✔
67
                $this->label = Html::el('label');
1✔
68
                $this->caption = $caption;
1✔
69
                $this->rules = new Rules($this);
1✔
70
                $this->setValue(null);
1✔
71
                $this->monitor(Form::class, function (Form $form): void {
1✔
72
                        if (!$this->isDisabled() && $form->isAnchored() && $form->isSubmitted()) {
1✔
73
                                $this->loadHttpData();
1✔
74
                        }
75
                });
1✔
76
        }
1✔
77

78

79
        /**
80
         * Sets textual caption or label.
81
         */
82
        public function setCaption(string|Stringable|null $caption): static
83
        {
84
                $this->caption = $caption;
×
85
                return $this;
×
86
        }
87

88

89
        public function getCaption(): string|Stringable|null
90
        {
91
                return $this->caption;
1✔
92
        }
93

94

95
        /**
96
         * Returns form.
97
         * @return ($throw is true ? Form : ?Form)
98
         */
99
        public function getForm(bool $throw = true): ?Form
1✔
100
        {
101
                return $this->lookup(Form::class, $throw);
1✔
102
        }
103

104

105
        /**
106
         * Loads HTTP data.
107
         */
108
        public function loadHttpData(): void
109
        {
110
                $this->setValue($this->getHttpData(Form::DataText));
1✔
111
        }
1✔
112

113

114
        /**
115
         * Loads HTTP data.
116
         */
117
        protected function getHttpData($type, ?string $htmlTail = null): mixed
1✔
118
        {
119
                return $this->getForm()->getHttpData($type, $this->getHtmlName() . $htmlTail);
1✔
120
        }
121

122

123
        /**
124
         * Returns HTML name of control.
125
         */
126
        public function getHtmlName(): string
127
        {
128
                return $this->control->name ?? Nette\Forms\Helpers::generateHtmlName($this->lookupPath(Form::class));
1✔
129
        }
130

131

132
        /********************* interface Control ****************d*g**/
133

134

135
        /**
136
         * Sets control's value.
137
         * @return static
138
         * @internal
139
         */
140
        public function setValue(mixed $value)
1✔
141
        {
142
                $this->value = $value;
1✔
143
                return $this;
1✔
144
        }
145

146

147
        /**
148
         * Returns control's value.
149
         * @return mixed
150
         */
151
        public function getValue()
152
        {
153
                return $this->value;
1✔
154
        }
155

156

157
        /**
158
         * Is control filled?
159
         */
160
        public function isFilled(): bool
161
        {
162
                $value = $this->getValue();
1✔
163
                return $value !== null && $value !== [] && $value !== '';
1✔
164
        }
165

166

167
        /**
168
         * Sets control's default value.
169
         * @return static
170
         */
171
        public function setDefaultValue($value)
172
        {
173
                $form = $this->getForm(throw: false);
1✔
174
                if ($this->isDisabled() || !$form || !$form->isAnchored() || !$form->isSubmitted()) {
1✔
175
                        $this->setValue($value);
1✔
176
                }
177

178
                return $this;
1✔
179
        }
180

181

182
        /**
183
         * Disables or enables control.
184
         * @return static
185
         */
186
        public function setDisabled(bool $state = true)
1✔
187
        {
188
                $this->disabled = $state;
1✔
189
                if ($state) {
1✔
190
                        $this->setValue(null);
1✔
191
                } elseif (($form = $this->getForm(throw: false)) && $form->isAnchored() && $form->isSubmitted()) {
1✔
192
                        $this->loadHttpData();
1✔
193
                }
194

195
                return $this;
1✔
196
        }
197

198

199
        /**
200
         * Is control disabled?
201
         */
202
        public function isDisabled(): bool
203
        {
204
                return $this->disabled === true;
1✔
205
        }
206

207

208
        /**
209
         * Sets whether control value is excluded from $form->getValues() result.
210
         */
211
        public function setOmitted(bool $state = true): static
1✔
212
        {
213
                $this->omitted = $state;
1✔
214
                return $this;
1✔
215
        }
216

217

218
        /**
219
         * Is control value excluded from $form->getValues() result?
220
         */
221
        public function isOmitted(): bool
222
        {
223
                return $this->omitted || ($this->isDisabled() && $this->omitted === null);
1✔
224
        }
225

226

227
        /********************* rendering ****************d*g**/
228

229

230
        /**
231
         * Generates control's HTML element.
232
         * @return Html|string
233
         */
234
        public function getControl()
235
        {
236
                $this->setOption('rendered', true);
1✔
237
                $el = clone $this->control;
1✔
238
                return $el->addAttributes([
1✔
239
                        'name' => $this->getHtmlName(),
1✔
240
                        'id' => $this->getHtmlId(),
1✔
241
                        'required' => $this->isRequired(),
1✔
242
                        'disabled' => $this->isDisabled(),
1✔
243
                        'data-nette-rules' => Nette\Forms\Helpers::exportRules($this->rules) ?: null,
1✔
244
                ]);
245
        }
246

247

248
        /**
249
         * Generates label's HTML element.
250
         * @return Html|string|null
251
         */
252
        public function getLabel(string|Stringable|null $caption = null)
1✔
253
        {
254
                $label = clone $this->label;
1✔
255
                $label->for = $this->getHtmlId();
1✔
256
                $caption ??= $this->caption;
1✔
257
                $translator = $this->getForm()->getTranslator();
1✔
258
                $label->setText($translator && !$caption instanceof Nette\HtmlStringable ? $translator->translate($caption) : $caption);
1✔
259
                return $label;
1✔
260
        }
261

262

263
        public function getControlPart(): ?Html
264
        {
265
                return $this->getControl();
1✔
266
        }
267

268

269
        public function getLabelPart(): ?Html
270
        {
271
                return $this->getLabel();
1✔
272
        }
273

274

275
        /**
276
         * Returns control's HTML element template.
277
         */
278
        public function getControlPrototype(): Html
279
        {
280
                return $this->control;
1✔
281
        }
282

283

284
        /**
285
         * Returns label's HTML element template.
286
         */
287
        public function getLabelPrototype(): Html
288
        {
289
                return $this->label;
×
290
        }
291

292

293
        /**
294
         * Changes control's HTML id.
295
         */
296
        public function setHtmlId(string|bool|null $id): static
1✔
297
        {
298
                $this->control->id = $id;
1✔
299
                return $this;
1✔
300
        }
301

302

303
        /**
304
         * Returns control's HTML id.
305
         */
306
        public function getHtmlId(): string|bool|null
307
        {
308
                if (!isset($this->control->id)) {
1✔
309
                        $form = $this->getForm();
1✔
310
                        $prefix = $form instanceof Nette\Application\UI\Form || $form->getName() === null
1✔
311
                                ? ''
1✔
312
                                : $form->getName() . '-';
1✔
313
                        $this->control->id = sprintf(self::$idMask, $prefix . $this->lookupPath());
1✔
314
                }
315

316
                return $this->control->id;
1✔
317
        }
318

319

320
        /**
321
         * Changes control's HTML attribute.
322
         */
323
        public function setHtmlAttribute(string $name, mixed $value = true): static
1✔
324
        {
325
                $this->control->$name = $value;
1✔
326
                if (
327
                        $name === 'name'
1✔
328
                        && ($form = $this->getForm(false))
1✔
329
                        && !$this->isDisabled()
1✔
330
                        && $form->isAnchored()
1✔
331
                        && $form->isSubmitted()
1✔
332
                ) {
333
                        $this->loadHttpData();
1✔
334
                }
335

336
                return $this;
1✔
337
        }
338

339

340
        /**
341
         * @deprecated  use setHtmlAttribute()
342
         */
343
        public function setAttribute(string $name, mixed $value = true): static
344
        {
345
                return $this->setHtmlAttribute($name, $value);
×
346
        }
347

348

349
        /********************* translator ****************d*g**/
350

351

352
        /**
353
         * Sets translate adapter.
354
         */
355
        public function setTranslator(?Nette\Localization\Translator $translator): static
1✔
356
        {
357
                $this->translator = $translator;
1✔
358
                return $this;
1✔
359
        }
360

361

362
        /**
363
         * Returns translate adapter.
364
         */
365
        public function getTranslator(): ?Nette\Localization\Translator
366
        {
367
                if ($this->translator === true) {
1✔
368
                        return $this->getForm(false)
1✔
369
                                ? $this->getForm()->getTranslator()
1✔
370
                                : null;
1✔
371
                }
372

373
                return $this->translator;
1✔
374
        }
375

376

377
        /**
378
         * Returns translated string.
379
         */
380
        public function translate($value, ...$parameters): mixed
1✔
381
        {
382
                if ($translator = $this->getTranslator()) {
1✔
383
                        $tmp = is_array($value) ? [&$value] : [[&$value]];
1✔
384
                        foreach ($tmp[0] as &$v) {
1✔
385
                                if ($v != null && !$v instanceof Nette\HtmlStringable) { // intentionally ==
1✔
386
                                        $v = $translator->translate($v, ...$parameters);
1✔
387
                                }
388
                        }
389
                }
390

391
                return $value;
1✔
392
        }
393

394

395
        /********************* rules ****************d*g**/
396

397

398
        /**
399
         * Adds a validation rule.
400
         * @return static
401
         */
402
        public function addRule(
1✔
403
                callable|string $validator,
404
                string|Stringable|null $errorMessage = null,
405
                mixed $arg = null,
406
        ) {
407
                $this->rules->addRule($validator, $errorMessage, $arg);
1✔
408
                return $this;
1✔
409
        }
410

411

412
        /**
413
         * Adds a validation condition a returns new branch.
414
         */
415
        public function addCondition($validator, $value = null): Rules
1✔
416
        {
417
                return $this->rules->addCondition($validator, $value);
1✔
418
        }
419

420

421
        /**
422
         * Adds a validation condition based on another control a returns new branch.
423
         */
424
        public function addConditionOn(Control $control, $validator, $value = null): Rules
1✔
425
        {
426
                return $this->rules->addConditionOn($control, $validator, $value);
1✔
427
        }
428

429

430
        /**
431
         * Adds an input filter callback.
432
         */
433
        public function addFilter(callable $filter): static
1✔
434
        {
435
                $this->getRules()->addFilter($filter);
1✔
436
                return $this;
1✔
437
        }
438

439

440
        public function getRules(): Rules
441
        {
442
                return $this->rules;
1✔
443
        }
444

445

446
        /**
447
         * Makes control mandatory.
448
         */
449
        public function setRequired(string|Stringable|bool $value = true): static
1✔
450
        {
451
                $this->rules->setRequired($value);
1✔
452
                return $this;
1✔
453
        }
454

455

456
        /**
457
         * Is control mandatory?
458
         */
459
        public function isRequired(): bool
460
        {
461
                return $this->rules->isRequired();
1✔
462
        }
463

464

465
        /**
466
         * Performs the server side validation.
467
         */
468
        public function validate(): void
469
        {
470
                if ($this->isDisabled()) {
1✔
471
                        return;
1✔
472
                }
473

474
                $this->cleanErrors();
1✔
475
                $this->rules->validate();
1✔
476
        }
1✔
477

478

479
        /**
480
         * Adds error message to the list.
481
         */
482
        public function addError(string|Stringable $message, bool $translate = true): void
1✔
483
        {
484
                $this->errors[] = $translate ? $this->translate($message) : $message;
1✔
485
        }
1✔
486

487

488
        /**
489
         * Returns errors corresponding to control.
490
         */
491
        public function getError(): ?string
492
        {
493
                return $this->errors ? implode(' ', array_unique($this->errors)) : null;
1✔
494
        }
495

496

497
        /**
498
         * Returns errors corresponding to control.
499
         */
500
        public function getErrors(): array
501
        {
502
                return array_unique($this->errors);
1✔
503
        }
504

505

506
        public function hasErrors(): bool
507
        {
508
                return (bool) $this->errors;
1✔
509
        }
510

511

512
        public function cleanErrors(): void
513
        {
514
                $this->errors = [];
1✔
515
        }
1✔
516

517

518
        /********************* user data ****************d*g**/
519

520

521
        /**
522
         * Sets user-specific option.
523
         */
524
        public function setOption($key, mixed $value): static
1✔
525
        {
526
                if ($value === null) {
1✔
527
                        unset($this->options[$key]);
×
528
                } else {
529
                        $this->options[$key] = $value;
1✔
530
                }
531

532
                return $this;
1✔
533
        }
534

535

536
        /**
537
         * Returns user-specific option.
538
         */
539
        public function getOption($key): mixed
540
        {
541
                return $this->options[$key] ?? null;
1✔
542
        }
543

544

545
        /**
546
         * Returns user-specific options.
547
         */
548
        public function getOptions(): array
549
        {
UNCOV
550
                return $this->options;
×
551
        }
552

553

554
        /********************* extension methods ****************d*g**/
555

556

557
        public function __call(string $name, array $args)
1✔
558
        {
559
                $class = static::class;
1✔
560
                do {
561
                        if (isset(self::$extMethods[$name][$class])) {
1✔
562
                                return (self::$extMethods[$name][$class])($this, ...$args);
1✔
563
                        }
564

565
                        $class = get_parent_class($class);
1✔
566
                } while ($class);
1✔
567

568
                return parent::__call($name, $args);
1✔
569
        }
570

571

572
        public static function extensionMethod(string $name, /*callable*/ $callback): void
1✔
573
        {
574
                if (str_contains($name, '::')) { // back compatibility
1✔
UNCOV
575
                        [, $name] = explode('::', $name);
×
576
                }
577

578
                self::$extMethods[$name][static::class] = $callback;
1✔
579
        }
1✔
580
}
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

© 2025 Coveralls, Inc