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

nette / forms / 28608035798

02 Jul 2026 04:53PM UTC coverage: 93.28% (-0.2%) from 93.512%
28608035798

push

github

dg
Form: submission detection redesigned around SubmissionSource objects

A form no longer inherits its way of receiving submitted data, it is
given a source:

- interface SubmissionSource { receiveData(Form): ?array; prepare(Form) }
- Sources\HttpSource - reads the form's own HTTP request: method match,
  Sec-Fetch origin policy (?FetchSite $allowedOrigin, null disables the
  check), POST+FILES/GET merge and the '_form_' tracker of a named form,
  which it installs itself in the prepare() render hook and validates
  against the form name at receive time
- Sources\NullSource - never submitted; used by Repeater item templates
  and Blueprint dummy forms instead of the former anonymous-class trick,
  so introspective code gets false from isSubmitted() instead of hacks

Form gains setSubmissionSource() and a protected createDefaultSource()
seam: the default HttpSource materializes lazily at the first submission
query or render, so a bare `new Form` keeps its behavior. Descendants
anchored elsewhere (Nette\Application\UI\Form) return null from
createDefaultSource() and set their source later; the write-once setter
runs a catch-up loadHttpData() pass so that controls attached before the
source load their values. The presenter signal field of UI\Form is the
same kind of marker as the tracker and moves to the prepare() hook of
its source on the application side.

Behavior notes: the tracker is installed at render time, so it renders
after user fields and $form[TrackerId] exists only after
fireRenderEvents(); Form::$httpRequest was replaced by injecting
`new HttpSource($request)`; the cross-origin flag lives in the source,
allowCrossOrigin() delegates to it.

284 of 326 new or added lines in 5 files covered. (87.12%)

58 existing lines in 9 files now uncovered.

2443 of 2619 relevant lines covered (93.28%)

0.93 hits per line

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

95.35
/src/Forms/Controls/TextInput.php
1
<?php declare(strict_types=1);
1✔
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
namespace Nette\Forms\Controls;
9

10
use Nette;
11
use Nette\Forms\Form;
12
use Stringable;
13
use function in_array, is_scalar, max, min;
14

15

16
/**
17
 * Single line text input control.
18
 */
19
class TextInput extends TextBase
20
{
21
        public function __construct(string|Stringable|null $label = null, ?int $maxLength = null)
1✔
22
        {
23
                parent::__construct($label);
1✔
24
                $this->control->maxlength = $maxLength;
1✔
25
                $this->setOption('type', 'text');
1✔
26
        }
1✔
27

28

29
        public function loadHttpData(): void
30
        {
31
                $this->setValue($this->getSubmittedValue(Form::DataLine));
1✔
32
        }
1✔
33

34

35
        /**
36
         * Changes control's type attribute.
37
         */
38
        public function setHtmlType(string $type): static
1✔
39
        {
40
                $this->control->type = $type;
1✔
41
                return $this;
1✔
42
        }
43

44

45
        #[\Deprecated('use setHtmlType()')]
46
        public function setType(string $type): static
47
        {
UNCOV
48
                trigger_error(__METHOD__ . '() was renamed to setHtmlType()', E_USER_DEPRECATED);
×
49
                return $this->setHtmlType($type);
×
50
        }
51

52

53
        public function getControl(): Nette\Utils\Html
54
        {
55
                return parent::getControl()->addAttributes([
1✔
56
                        'value' => $this->control->type === 'password' ? $this->control->value : $this->getRenderedValue(),
1✔
57
                        'type' => $this->control->type ?? 'text',
1✔
58
                ]);
59
        }
60

61

62
        /** @param  (callable(Nette\Forms\Control, mixed): bool)|string  $validator */
63
        public function addRule(
1✔
64
                callable|string $validator,
65
                string|Stringable|null $errorMessage = null,
66
                mixed $arg = null,
67
        ): static
68
        {
69
                foreach ($this->getRules() as $rule) {
1✔
70
                        if (!$rule->canExport() && !$rule->branch) {
1✔
71
                                return parent::addRule($validator, $errorMessage, $arg);
1✔
72
                        }
73
                }
74

75
                if ($this->control->type === null && in_array($validator, [Form::Email, Form::URL, Form::Integer], strict: true)) {
1✔
76
                        $types = [Form::Email => 'email', Form::URL => 'url', Form::Integer => 'number'];
1✔
77
                        $this->control->type = $types[$validator];
1✔
78

79
                } elseif (
80
                        in_array($validator, [Form::Min, Form::Max, Form::Range], strict: true)
1✔
81
                        && in_array($this->control->type, ['number', 'range', 'datetime-local', 'datetime', 'date', 'month', 'week', 'time'], strict: true)
1✔
82
                ) {
83
                        if ($validator === Form::Min) {
1✔
84
                                $range = [$arg, null];
1✔
85
                        } elseif ($validator === Form::Max) {
1✔
86
                                $range = [null, $arg];
1✔
87
                        } else {
88
                                $range = $arg;
1✔
89
                        }
90

91
                        if (isset($range[0]) && is_scalar($range[0])) {
1✔
92
                                $this->control->min = isset($this->control->min)
1✔
93
                                        ? max($this->control->min, $range[0])
1✔
94
                                        : $range[0];
1✔
95
                        }
96

97
                        if (isset($range[1]) && is_scalar($range[1])) {
1✔
98
                                $this->control->max = isset($this->control->max)
1✔
99
                                        ? min($this->control->max, $range[1])
1✔
100
                                        : $range[1];
1✔
101
                        }
102

103
                } elseif (
104
                        $validator === Form::Pattern
1✔
105
                        && is_scalar($arg)
1✔
106
                        && in_array($this->control->type, [null, 'text', 'search', 'tel', 'url', 'email', 'password'], strict: true)
1✔
107
                ) {
108
                        $this->control->pattern = (string) $arg;
1✔
109
                }
110

111
                return parent::addRule($validator, $errorMessage, $arg);
1✔
112
        }
113
}
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