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

nette / forms / 7167068645

11 Dec 2023 11:59AM UTC coverage: 93.082% (-0.05%) from 93.13%
7167068645

push

github

dg
Latte: {formContext}, {formPrint} & {formClassPrint) are deprecated

1 of 5 new or added lines in 2 files covered. (20.0%)

92 existing lines in 13 files now uncovered.

2072 of 2226 relevant lines covered (93.08%)

0.93 hits per line

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

93.84
/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

19

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

47
        protected mixed $value = null;
48
        protected Html $control;
49
        protected Html $label;
50

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

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

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

65

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

80

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

90

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

96

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

106

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

115

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

124

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

133

134
        /********************* interface Control ****************d*g**/
135

136

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

147

148
        /**
149
         * Returns control's value.
150
         */
151
        public function getValue(): mixed
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
         */
170
        public function setDefaultValue($value): static
171
        {
172
                $form = $this->getForm(false);
1✔
173
                if ($this->isDisabled() || !$form || !$form->isAnchored() || !$form->isSubmitted()) {
1✔
174
                        $this->setValue($value);
1✔
175
                }
176

177
                return $this;
1✔
178
        }
179

180

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

192
                return $this;
1✔
193
        }
194

195

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

204

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

214

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

223

224
        /********************* rendering ****************d*g**/
225

226

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

244

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

258

259
        public function getControlPart(): ?Html
260
        {
261
                return $this->getControl();
1✔
262
        }
263

264

265
        public function getLabelPart(): ?Html
266
        {
267
                return $this->getLabel();
1✔
268
        }
269

270

271
        /**
272
         * Returns control's HTML element template.
273
         */
274
        public function getControlPrototype(): Html
275
        {
276
                return $this->control;
1✔
277
        }
278

279

280
        /**
281
         * Returns label's HTML element template.
282
         */
283
        public function getLabelPrototype(): Html
284
        {
UNCOV
285
                return $this->label;
×
286
        }
287

288

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

298

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

312
                return $this->control->id;
1✔
313
        }
314

315

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

332
                return $this;
1✔
333
        }
334

335

336
        /**
337
         * @deprecated  use setHtmlAttribute()
338
         */
339
        public function setAttribute(string $name, mixed $value = true): static
340
        {
UNCOV
341
                return $this->setHtmlAttribute($name, $value);
×
342
        }
343

344

345
        /********************* translator ****************d*g**/
346

347

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

357

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

369
                return $this->translator;
1✔
370
        }
371

372

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

387
                return $value;
1✔
388
        }
389

390

391
        /********************* rules ****************d*g**/
392

393

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

407

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

416

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

425

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

435

436
        public function getRules(): Rules
437
        {
438
                return $this->rules;
1✔
439
        }
440

441

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

451

452
        /**
453
         * Is control mandatory?
454
         */
455
        public function isRequired(): bool
456
        {
457
                return $this->rules->isRequired();
1✔
458
        }
459

460

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

470
                $this->cleanErrors();
1✔
471
                $this->rules->validate();
1✔
472
        }
1✔
473

474

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

483

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

492

493
        /**
494
         * Returns errors corresponding to control.
495
         */
496
        public function getErrors(): array
497
        {
498
                return array_unique($this->errors);
1✔
499
        }
500

501

502
        public function hasErrors(): bool
503
        {
504
                return (bool) $this->errors;
1✔
505
        }
506

507

508
        public function cleanErrors(): void
509
        {
510
                $this->errors = [];
1✔
511
        }
1✔
512

513

514
        /********************* user data ****************d*g**/
515

516

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

528
                return $this;
1✔
529
        }
530

531

532
        /**
533
         * Returns user-specific option.
534
         */
535
        public function getOption($key): mixed
536
        {
537
                if (func_num_args() > 1) {
1✔
UNCOV
538
                        trigger_error(__METHOD__ . '() parameter $default is deprecated, use operator ??', E_USER_DEPRECATED);
×
UNCOV
539
                        $default = func_get_arg(1);
×
540
                }
541
                return $this->options[$key] ?? $default ?? 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): mixed
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

© 2026 Coveralls, Inc