• 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

97.73
/src/Forms/Controls/TextBase.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 Nette\Utils\Strings;
13
use Stringable;
14
use function get_debug_type, is_array, is_scalar, min, sprintf;
15

16

17
/**
18
 * Base for text-based controls (TextInput, TextArea) with nullable and empty-value support.
19
 */
20
abstract class TextBase extends BaseControl
21
{
22
        protected string $emptyValue = '';
23
        protected mixed $rawValue = '';
24
        private bool $nullable = false;
25

26

27
        /** @internal */
28
        public function setValue(mixed $value): static
1✔
29
        {
30
                if ($value === null) {
1✔
31
                        $value = '';
1✔
32
                } elseif (!is_scalar($value) && !$value instanceof Stringable) {
1✔
33
                        throw new Nette\InvalidArgumentException(sprintf("Value must be scalar or null, %s given in field '%s'.", get_debug_type($value), $this->getName()));
1✔
34
                }
35

36
                $this->value = $value;
1✔
37
                $this->rawValue = (string) $value;
1✔
38
                return $this;
1✔
39
        }
40

41

42
        /**
43
         * Returns the value, substituting empty string when it matches the empty value. Returns null when nullable is set and value is empty.
44
         */
45
        public function getValue(): mixed
46
        {
47
                $value = $this->value === Strings::trim($this->translate($this->emptyValue))
1✔
48
                        ? ''
1✔
49
                        : $this->value;
1✔
50
                return $this->nullable && $value === '' ? null : $value;
1✔
51
        }
52

53

54
        /**
55
         * Sets whether getValue() returns null instead of empty string.
56
         */
57
        public function setNullable(bool $value = true): static
1✔
58
        {
59
                $this->nullable = $value;
1✔
60
                return $this;
1✔
61
        }
62

63

64
        public function isNullable(): bool
65
        {
66
                return $this->nullable;
1✔
67
        }
68

69

70
        /**
71
         * Sets the special value which is treated as empty string.
72
         */
73
        public function setEmptyValue(string $value): static
1✔
74
        {
75
                $this->emptyValue = $value;
1✔
76
                return $this;
1✔
77
        }
78

79

80
        /**
81
         * Returns the special value which is treated as empty string.
82
         */
83
        public function getEmptyValue(): string
84
        {
UNCOV
85
                return $this->emptyValue;
×
86
        }
87

88

89
        /**
90
         * Sets the maximum number of allowed characters.
91
         */
92
        public function setMaxLength(?int $length): static
1✔
93
        {
94
                $this->control->maxlength = $length;
1✔
95
                return $this;
1✔
96
        }
97

98

99
        public function getControl(): Nette\Utils\Html
100
        {
101
                $el = parent::getControl();
1✔
102
                if ($this->emptyValue !== '') {
1✔
103
                        $el->attrs['data-nette-empty-value'] = Strings::trim($this->translate($this->emptyValue));
1✔
104
                }
105

106
                if (isset($el->placeholder)) {
1✔
107
                        $el->placeholder = $this->translate($el->placeholder);
1✔
108
                }
109

110
                return $el;
1✔
111
        }
112

113

114
        protected function getRenderedValue(): ?string
115
        {
116
                return $this->rawValue === ''
1✔
117
                        ? ($this->emptyValue === '' ? null : $this->translate($this->emptyValue))
1✔
118
                        : $this->rawValue;
1✔
119
        }
120

121

122
        /** @param  (callable(Nette\Forms\Control, mixed): bool)|string  $validator */
123
        public function addRule(
1✔
124
                callable|string $validator,
125
                string|Stringable|null $errorMessage = null,
126
                mixed $arg = null,
127
        ): static
128
        {
129
                foreach ($this->getRules() as $rule) {
1✔
130
                        if (!$rule->canExport() && !$rule->branch) {
1✔
131
                                return parent::addRule($validator, $errorMessage, $arg);
1✔
132
                        }
133
                }
134

135
                if ($validator === Form::Length || $validator === Form::MaxLength) {
1✔
136
                        $tmp = is_array($arg) ? $arg[1] : $arg;
1✔
137
                        if (is_scalar($tmp)) {
1✔
138
                                $this->control->maxlength = isset($this->control->maxlength)
1✔
139
                                        ? min($this->control->maxlength, $tmp)
1✔
140
                                        : $tmp;
1✔
141
                        }
142
                }
143

144
                return parent::addRule($validator, $errorMessage, $arg);
1✔
145
        }
146
}
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