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

nette / forms / 28607566428

02 Jul 2026 04:53PM UTC coverage: 93.28% (+0.01%) from 93.269%
28607566428

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

19 existing lines in 1 file 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

96.55
/src/Forms/Sources/HttpSource.php
1
<?php declare(strict_types=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\Sources;
9

10
use Nette;
11
use Nette\Forms\Controls\HiddenField;
12
use Nette\Forms\Form;
13
use Nette\Forms\SubmissionSource;
14
use function strcasecmp;
15

16

17
/**
18
 * Source that detects submission by reading the form's own HTTP request; for use outside Nette Application. Experimental.
19
 */
20
final class HttpSource implements SubmissionSource
21
{
22
        /** Name of the hidden field pairing submitted data with the form. */
23
        public const TrackerId = '_form_';
24

25
        private Nette\Http\IRequest $request;
26

27
        /** which Sec-Fetch-Site values are accepted for submission; null disables the check */
28
        private ?Nette\Http\FetchSite $allowedOrigin = Nette\Http\FetchSite::SameOrigin;
29

30

31
        public function __construct(?Nette\Http\IRequest $request = null)
1✔
32
        {
33
                if ($request !== null) {
1✔
34
                        $this->request = $request;
1✔
35
                }
36
        }
1✔
37

38

39
        /**
40
         * Disables the same-origin (Sec-Fetch) CSRF check, allowing cross-origin form submissions.
41
         */
42
        public function allowCrossOrigin(): void
43
        {
44
                $this->allowedOrigin = null;
1✔
45
        }
1✔
46

47

48
        public function receiveData(Form $form): ?array
1✔
49
        {
50
                $request = $this->getRequest();
1✔
51
                if (strcasecmp($form->getMethod(), $request->getMethod())) {
1✔
52
                        return null;
1✔
53
                }
54

55
                if ($request->isMethod('post')) {
1✔
56
                        if ($this->allowedOrigin !== null && !$request->isFrom($this->allowedOrigin)) {
1✔
57
                                return null;
1✔
58
                        }
59

60
                        $data = Nette\Utils\Arrays::mergeTree($request->getPost(), $request->getFiles());
1✔
61

62
                } else {
63
                        $data = $request->getQuery();
1✔
64
                        if (!$data) {
1✔
65
                                return null;
1✔
66
                        }
67
                }
68

69
                $name = $form->getName();
1✔
70
                if ($name !== null && ($data[self::TrackerId] ?? null) !== $name) {
1✔
NEW
71
                        return null;
×
72
                }
73

74
                return $data;
1✔
75
        }
76

77

78
        /**
79
         * Installs the tracker field pairing the submitted data with a named form.
80
         */
81
        public function prepare(Form $form): void
1✔
82
        {
83
                $name = $form->getName();
1✔
84
                if ($name !== null && !$form->getComponent(self::TrackerId, throw: false)) {
1✔
85
                        $tracker = new HiddenField($name);
1✔
86
                        $tracker->setOmitted();
1✔
87
                        $form[self::TrackerId] = $tracker;
1✔
88
                }
89
        }
1✔
90

91

92
        private function getRequest(): Nette\Http\IRequest
93
        {
94
                return $this->request ??= (new Nette\Http\RequestFactory)->fromGlobals();
1✔
95
        }
96
}
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