Nette Documentation Preview

syntax
Формы отдельно от фреймворка
****************************

.[perex]
Nette Forms радикально упрощают создание и обработку веб-форм. Вы можете использовать их в своих приложениях совершенно самостоятельно, без остального фреймворка, как показано в этой главе.

Однако если вы используете Nette Application и презентеры, для вас есть отдельное руководство: [формы в презентерах |in-presenter].


Первая форма
============

Прежде чем начать, установите пакет с помощью [Composer |best-practices:composer]:

```shell
composer require nette/forms
```

Попробуем написать простую регистрационную форму. Её код будет таким ("полный код":https://gist.github.com/dg/370a7e3094d9ba9a9e913b8e2a2dc851):

```php
use Nette\Forms\Form;

$form = new Form;
$form->addText('name', 'Имя:');
$form->addPassword('password', 'Пароль:');
$form->addSubmit('send', 'Зарегистрироваться');
```

И отрисуем её совсем просто:

```php
$form->render();
```

Результат в браузере должен выглядеть так:

[* form-en.webp *]

Форма - это объект класса `Nette\Forms\Form` (класс `Nette\Application\UI\Form` используется в презентерах). Мы добавили в неё элементы с именами "name", "password" и кнопку отправки.

Теперь оживим форму. Запросив `$form->isSuccess()`, мы узнаем, была ли форма отправлена и была ли она корректно заполнена. Если да, выведем данные. После определения формы допишите:

```php
if ($form->isSuccess()) {
	echo 'Форма была корректно заполнена и отправлена';
	$data = $form->getValues();
	// $data->name содержит имя
	// $data->password содержит пароль
	var_dump($data);
}
```

Метод `getValues()` возвращает отправленные данные в виде объекта [ArrayHash |utils:arrays#ArrayHash]. Как это изменить, мы покажем [позже |#Отображение в классы]. Объект `$data` содержит ключи `name` и `password` с данными, которые ввёл пользователь.

Обычно мы отправляем данные прямо на дальнейшую обработку, например на вставку в базу данных. Однако при обработке может возникнуть ошибка, например если имя пользователя уже занято. В таком случае мы передаём ошибку обратно в форму методом `addError()` и даём отрисовать её снова вместе с сообщением об ошибке.

```php
$form->addError('Извините, это имя пользователя уже занято.');
```

После обработки формы мы перенаправляем на следующую страницу. Это предотвращает непреднамеренную повторную отправку формы кнопками *обновить* и *назад* или переходом по истории браузера.

По умолчанию форма отправляется методом POST на ту же страницу. И то и другое можно изменить:

```php
$form->setAction('/submit.php');
$form->setMethod('GET');
```

И это, по сути, всё :-) У нас есть работающая и превосходно [защищённая |#Защита от уязвимостей] форма.

Попробуйте добавить и другие [элементы формы |controls].


Доступ к элементам
==================

Форму и её отдельные элементы называют компонентами. Они образуют дерево компонентов, корнем которого является форма. К отдельным элементам формы можно обратиться так:

```php
$input = $form->getComponent('name');
// альтернативная запись: $input = $form['name'];

$button = $form->getComponent('send');
// альтернативная запись: $button = $form['send'];
```

Элементы удаляются через `unset`:

```php
unset($form['name']);
```


Правила проверки
================

Прозвучало слово *корректна*, но у формы пока нет никаких правил проверки. Исправим это.

Имя будет обязательным, поэтому пометим его методом `setRequired()`. Его аргумент - текст сообщения об ошибке, которое отобразится, если пользователь имя не заполнит. Если аргумент не указан, будет использовано стандартное сообщение об ошибке.

```php
$form->addText('name', 'Имя:')
	->setRequired('Введите, пожалуйста, имя.');
```

Попробуйте отправить форму, не заполнив имя, и вы увидите, что появится сообщение об ошибке. Браузер или сервер будут её отклонять, пока вы поле не заполните.

При этом систему не обмануть, введя в поле только пробелы. Никак. Nette автоматически обрезает пробелы слева и справа. Попробуйте. Это то, что нужно всегда делать с каждым однострочным полем, но об этом часто забывают. Nette делает это автоматически. (Можете попробовать одурачить форму, отправив в качестве имени многострочную строку. И тут Nette не проведёшь, переводы строк будут заменены пробелами.)

Форма всегда проверяется на стороне сервера, но порождается и проверка на JavaScript. Она выполняется мгновенно, и пользователь узнаёт об ошибках сразу, без необходимости отправлять форму на сервер. За это отвечает скрипт `netteForms.js`. Вставьте его в страницу:

```latte
<script src="https://unpkg.com/nette-forms@3"></script>
```

Если вы посмотрите на исходный код страницы с формой, то заметите, что Nette помещает обязательные элементы в элементы с CSS-классом `required`. Попробуйте добавить в шаблон следующий стиль, и метка "Имя" станет красной. Так вы элегантно выделите для пользователей обязательные элементы:

```latte
<style>
.required label { color: maroon }
</style>
```

Другие правила проверки мы добавляем методом `addRule()`. Первый параметр - правило, второй - снова текст сообщения об ошибке, а за ним может следовать необязательный аргумент правила проверки. Что это значит?

Расширим форму новым необязательным полем "возраст", которое должно быть целым числом (`addInteger()`) и находиться в допустимом диапазоне (`$form::Range`). Здесь мы используем третий параметр метода `addRule()`, чтобы передать валидатору нужный диапазон парой `[min, max]`:

```php
$form->addInteger('age', 'Возраст:')
	->addRule($form::Range, 'Возраст должен быть от 18 до 120 лет.', [18, 120]);
```

.[tip]
Если пользователь поле не заполнит, правила проверки проверяться не будут, потому что элемент необязателен.

Здесь появляется место для небольшого рефакторинга. Числа дублируются в сообщении об ошибке и в третьем параметре, а это неидеально. Если бы мы создавали [многоязычные формы |rendering#Перевод] и сообщение с числами переводилось бы на несколько языков, менять значения стало бы трудно. Поэтому можно использовать подстановки `%d`, и Nette значения подставит:

```php
	->addRule($form::Range, 'Возраст должен быть от %d до %d лет.', [18, 120]);
```

Вернёмся к элементу `password`, сделаем его тоже обязательным и заодно проверим минимальную длину пароля (`$form::MinLength`), снова с подстановкой в сообщении:

```php
$form->addPassword('password', 'Пароль:')
	->setRequired('Выберите пароль')
	->addRule($form::MinLength, 'Пароль должен быть длиной не менее %d символов', 8);
```

Добавим в форму ещё одно поле `passwordVerify`, где пользователь введёт пароль повторно для проверки. С помощью правил проверки убедимся, что оба пароля одинаковы (`$form::Equal`). В качестве параметра передадим ссылку на первый пароль через [квадратные скобки |#Доступ к элементам]:

```php
$form->addPassword('passwordVerify', 'Пароль ещё раз:')
	->setRequired('Введите, пожалуйста, пароль ещё раз для проверки')
	->addRule($form::Equal, 'Пароли не совпадают', $form['password'])
	->setOmitted();
```

С помощью `setOmitted()` мы пометили элемент, значение которого нас на самом деле не интересует и который существует только ради проверки. Его значение в `$data` не передаётся.

Тем самым у нас есть полностью работающая форма с проверкой и в PHP, и в JavaScript. Возможности проверки в Nette намного шире: можно создавать условия, по ним показывать и скрывать части страницы и т. д. Обо всём вы узнаете в главе о [проверке форм |validation].


Значения по умолчанию
=====================

Значения по умолчанию для элементов формы мы задаём часто:

```php
$form->addEmail('email', 'Email')
	->setDefaultValue($lastUsedEmail);
```

Часто бывает полезно задать значения по умолчанию сразу всем элементам, например когда форма используется для редактирования записей. Мы считываем запись из базы данных и задаём её значения как значения по умолчанию:

```php
// $row = ['name' => 'John', 'age' => '33', /* ... */];
$form->setDefaults($row);
```

Вызывайте `setDefaults()` после определения элементов.

У уже отправленной формы `setDefaults()` ничего не делает: он не перезапишет то, что заполнил пользователь, так что вызывать его в фабрике формы безусловно безопасно. Если вам нужно задать значения принудительно и после отправки, используйте вместо него `setValues()`.


Отрисовка формы
===============

По умолчанию форма отрисовывается как таблица. Отдельные элементы соблюдают основные правила доступности: все метки порождаются как элементы `<label>` и связаны с соответствующими элементами формы. Щелчок по метке автоматически ставит курсор в поле формы.

Каждому элементу мы можем задать произвольные HTML-атрибуты. Например, добавить placeholder:

```php
$form->addInteger('age', 'Возраст:')
	->setHtmlAttribute('placeholder', 'Заполните, пожалуйста, возраст');
```

Способов отрисовать форму много, поэтому [отрисовке посвящена отдельная глава |rendering].


Отрисовка с помощью Latte
-------------------------

Если у вас под рукой шаблонизатор [Latte |latte:], вы можете поручить отрисовку формы ему и получить полный контроль над итоговым HTML. Вы создаёте движок, регистрируете расширение форм и передаёте форму в шаблон переменной:

```php
$latte = new Latte\Engine;
$latte->addExtension(new Nette\Bridges\FormsLatte\FormsExtension);

$latte->render('form.latte', ['form' => $form]);
```

В шаблоне вы затем работаете с формой через переменную `$form` и теги вроде `{input}`, `{label}` или `n:name`. Полный пример вместе с шаблоном найдёте в каталоге [examples |https://github.com/nette/forms/tree/master/examples] (файлы `latte.php` и `latte/`). Отдельные теги описаны в главе об [отрисовке |rendering].


Отображение в классы
====================

Вернёмся к обработке данных формы. Метод `getValues()` возвращал отправленные данные как объект `ArrayHash`. Поскольку это универсальный класс, похожий на `stdClass`, при работе с ним нам не хватает определённых удобств, например автодополнения свойств в редакторах или статического анализа кода. Это можно решить, заведя для каждой формы отдельный класс, свойства которого представляют отдельные элементы. Например:

```php
class RegistrationFormData
{
	public string $name;
	public ?int $age;
	public string $password;
}
```

Как вариант, можно использовать конструктор:

```php
class RegistrationFormData
{
	public function __construct(
		public string $name,
		public ?int $age,
		public string $password,
	) {
	}
}
```

Свойства класса данных могут быть и перечислениями, и они будут отображены автоматически. .{data-version:3.2.4}

Как сказать Nette, чтобы она возвращала данные как объекты этого класса? Проще, чем вы думаете. Достаточно указать параметром имя класса или объект для наполнения:

```php
$data = $form->getValues(RegistrationFormData::class);
$name = $data->name;
```

В качестве параметра можно указать и `'array'`, тогда данные вернутся массивом.

Если формы состоят из многоуровневой структуры из контейнеров, создайте для каждого отдельный класс:

```php
$form = new Form;
$person = $form->addContainer('person');
$person->addText('firstName');
/* ... */

class PersonFormData
{
	public string $firstName;
	public string $lastName;
}

class RegistrationFormData
{
	public PersonFormData $person;
	public ?int $age;
	public string $password;
}
```

Тогда отображение по типу свойства `$person` понимает, что контейнер нужно отобразить в класс `PersonFormData`. Если бы свойство содержало массив контейнеров, укажите тип `array`, а класс для отображения передайте прямо контейнеру:

```php
$person->setMappedType(PersonFormData::class);
```

Заготовку класса данных формы можно породить методом `Nette\Forms\Blueprint::dataClass($form)`, который выведет её на страницу в браузере. Дальше достаточно щелчком выделить код и скопировать его в проект. .{data-version:3.1.15}


Несколько кнопок отправки
=========================

Если у формы больше одной кнопки, нам обычно нужно различить, какая была нажата. Эти сведения возвращает метод кнопки `isSubmittedBy()`:

```php
$form->addSubmit('save', 'Сохранить');
$form->addSubmit('delete', 'Удалить');

if ($form->isSuccess()) {
	if ($form['save']->isSubmittedBy()) {
		// ...
	}

	if ($form['delete']->isSubmittedBy()) {
		// ...
	}
}
```

Не опускайте проверку `$form->isSuccess()`, она проверяет корректность данных.

Когда форма отправляется нажатием клавиши <kbd>Enter</kbd>, это считается отправкой первой кнопкой.


Защита от уязвимостей
=====================

Nette Framework уделяет безопасности большое внимание и поэтому тщательно следит за должной защитой форм.

Кроме защиты форм от известных уязвимостей вроде [Cross-Site Scripting (XSS) |nette:glossary#Cross-Site Scripting (XSS)] и [Cross-Site Request Forgery (CSRF) |nette:glossary#Cross-Site Request Forgery (CSRF)], она выполняет множество мелких мер безопасности, о которых вам больше не нужно думать.

Например, она отфильтровывает из ввода все управляющие символы и проверяет корректность кодировки UTF-8, благодаря чему данные из формы всегда будут чистыми. У выпадающих списков и радиосписков она проверяет, что выбранные пункты действительно были среди предложенных и что подделки не произошло. Мы уже упоминали, что у однострочных текстовых полей она заменяет пробелами символы конца строки, которые мог отправить злоумышленник. У многострочных полей она приводит символы конца строки к единому виду. И так далее.

Nette решает за вас риски безопасности, о существовании которых многие программисты не подозревают.

Упомянутая атака CSRF состоит в том, что злоумышленник заманивает жертву на страницу, которая незаметно выполняет в браузере жертвы запрос к серверу, где жертва в этот момент авторизована. Сервер считает, что запрос сделала жертва по своей воле. Поэтому Nette отклоняет POST-формы, отправленные с чужого источника; чужим считается даже другой поддомен того же сайта. Если вам нужно разрешить отправку с другого источника, отключите защиту так:

```php
$form->allowCrossOrigin(); // ВНИМАНИЕ! Полностью отключает защиту!
```

Правда, это отключает защиту для любого источника. Чтобы разрешить только определённые источники, отключите защиту и сами сверяйте заголовок `Origin` со своим списком разрешённых.

Защита опирается на браузерный заголовок `Sec-Fetch-Site` (Fetch Metadata), который браузер отправляет автоматически и который нельзя подделать даже при наличии XSS-уязвимости. Старые браузеры, которые эти заголовки не отправляют, проверку не пройдут. Подробно это описано в статье [Браузер наконец решает CSRF |https://blog.nette.org/en/quarter-century-of-csrf].

.[note]
Прежняя защита с помощью авторизационного токена в сессии, включаемая через `$form->addProtection()`, больше не нужна и объявлена устаревшей начиная с версии 3.3.

Итак, мы бегло познакомились с формами в Nette. За дополнительным вдохновением загляните в каталог [examples |https://github.com/nette/forms/tree/master/examples] в дистрибутиве.

Формы отдельно от фреймворка

Nette Forms радикально упрощают создание и обработку веб-форм. Вы можете использовать их в своих приложениях совершенно самостоятельно, без остального фреймворка, как показано в этой главе.

Однако если вы используете Nette Application и презентеры, для вас есть отдельное руководство: формы в презентерах.

Первая форма

Прежде чем начать, установите пакет с помощью Composer:

composer require nette/forms

Попробуем написать простую регистрационную форму. Её код будет таким (полный код):

use Nette\Forms\Form;

$form = new Form;
$form->addText('name', 'Имя:');
$form->addPassword('password', 'Пароль:');
$form->addSubmit('send', 'Зарегистрироваться');

И отрисуем её совсем просто:

$form->render();

Результат в браузере должен выглядеть так:

Форма – это объект класса Nette\Forms\Form (класс Nette\Application\UI\Form используется в презентерах). Мы добавили в неё элементы с именами „name“, „password“ и кнопку отправки.

Теперь оживим форму. Запросив $form->isSuccess(), мы узнаем, была ли форма отправлена и была ли она корректно заполнена. Если да, выведем данные. После определения формы допишите:

if ($form->isSuccess()) {
	echo 'Форма была корректно заполнена и отправлена';
	$data = $form->getValues();
	// $data->name содержит имя
	// $data->password содержит пароль
	var_dump($data);
}

Метод getValues() возвращает отправленные данные в виде объекта ArrayHash. Как это изменить, мы покажем позже. Объект $data содержит ключи name и password с данными, которые ввёл пользователь.

Обычно мы отправляем данные прямо на дальнейшую обработку, например на вставку в базу данных. Однако при обработке может возникнуть ошибка, например если имя пользователя уже занято. В таком случае мы передаём ошибку обратно в форму методом addError() и даём отрисовать её снова вместе с сообщением об ошибке.

$form->addError('Извините, это имя пользователя уже занято.');

После обработки формы мы перенаправляем на следующую страницу. Это предотвращает непреднамеренную повторную отправку формы кнопками обновить и назад или переходом по истории браузера.

По умолчанию форма отправляется методом POST на ту же страницу. И то и другое можно изменить:

$form->setAction('/submit.php');
$form->setMethod('GET');

И это, по сути, всё :-) У нас есть работающая и превосходно защищённая форма.

Попробуйте добавить и другие элементы формы.

Доступ к элементам

Форму и её отдельные элементы называют компонентами. Они образуют дерево компонентов, корнем которого является форма. К отдельным элементам формы можно обратиться так:

$input = $form->getComponent('name');
// альтернативная запись: $input = $form['name'];

$button = $form->getComponent('send');
// альтернативная запись: $button = $form['send'];

Элементы удаляются через unset:

unset($form['name']);

Правила проверки

Прозвучало слово корректна, но у формы пока нет никаких правил проверки. Исправим это.

Имя будет обязательным, поэтому пометим его методом setRequired(). Его аргумент – текст сообщения об ошибке, которое отобразится, если пользователь имя не заполнит. Если аргумент не указан, будет использовано стандартное сообщение об ошибке.

$form->addText('name', 'Имя:')
	->setRequired('Введите, пожалуйста, имя.');

Попробуйте отправить форму, не заполнив имя, и вы увидите, что появится сообщение об ошибке. Браузер или сервер будут её отклонять, пока вы поле не заполните.

При этом систему не обмануть, введя в поле только пробелы. Никак. Nette автоматически обрезает пробелы слева и справа. Попробуйте. Это то, что нужно всегда делать с каждым однострочным полем, но об этом часто забывают. Nette делает это автоматически. (Можете попробовать одурачить форму, отправив в качестве имени многострочную строку. И тут Nette не проведёшь, переводы строк будут заменены пробелами.)

Форма всегда проверяется на стороне сервера, но порождается и проверка на JavaScript. Она выполняется мгновенно, и пользователь узнаёт об ошибках сразу, без необходимости отправлять форму на сервер. За это отвечает скрипт netteForms.js. Вставьте его в страницу:

<script src="https://unpkg.com/nette-forms@3"></script>

Если вы посмотрите на исходный код страницы с формой, то заметите, что Nette помещает обязательные элементы в элементы с CSS-классом required. Попробуйте добавить в шаблон следующий стиль, и метка „Имя“ станет красной. Так вы элегантно выделите для пользователей обязательные элементы:

<style>
.required label { color: maroon }
</style>

Другие правила проверки мы добавляем методом addRule(). Первый параметр – правило, второй – снова текст сообщения об ошибке, а за ним может следовать необязательный аргумент правила проверки. Что это значит?

Расширим форму новым необязательным полем „возраст“, которое должно быть целым числом (addInteger()) и находиться в допустимом диапазоне ($form::Range). Здесь мы используем третий параметр метода addRule(), чтобы передать валидатору нужный диапазон парой [min, max]:

$form->addInteger('age', 'Возраст:')
	->addRule($form::Range, 'Возраст должен быть от 18 до 120 лет.', [18, 120]);

Если пользователь поле не заполнит, правила проверки проверяться не будут, потому что элемент необязателен.

Здесь появляется место для небольшого рефакторинга. Числа дублируются в сообщении об ошибке и в третьем параметре, а это неидеально. Если бы мы создавали многоязычные формы и сообщение с числами переводилось бы на несколько языков, менять значения стало бы трудно. Поэтому можно использовать подстановки %d, и Nette значения подставит:

	->addRule($form::Range, 'Возраст должен быть от %d до %d лет.', [18, 120]);

Вернёмся к элементу password, сделаем его тоже обязательным и заодно проверим минимальную длину пароля ($form::MinLength), снова с подстановкой в сообщении:

$form->addPassword('password', 'Пароль:')
	->setRequired('Выберите пароль')
	->addRule($form::MinLength, 'Пароль должен быть длиной не менее %d символов', 8);

Добавим в форму ещё одно поле passwordVerify, где пользователь введёт пароль повторно для проверки. С помощью правил проверки убедимся, что оба пароля одинаковы ($form::Equal). В качестве параметра передадим ссылку на первый пароль через квадратные скобки:

$form->addPassword('passwordVerify', 'Пароль ещё раз:')
	->setRequired('Введите, пожалуйста, пароль ещё раз для проверки')
	->addRule($form::Equal, 'Пароли не совпадают', $form['password'])
	->setOmitted();

С помощью setOmitted() мы пометили элемент, значение которого нас на самом деле не интересует и который существует только ради проверки. Его значение в $data не передаётся.

Тем самым у нас есть полностью работающая форма с проверкой и в PHP, и в JavaScript. Возможности проверки в Nette намного шире: можно создавать условия, по ним показывать и скрывать части страницы и т. д. Обо всём вы узнаете в главе о проверке форм.

Значения по умолчанию

Значения по умолчанию для элементов формы мы задаём часто:

$form->addEmail('email', 'Email')
	->setDefaultValue($lastUsedEmail);

Часто бывает полезно задать значения по умолчанию сразу всем элементам, например когда форма используется для редактирования записей. Мы считываем запись из базы данных и задаём её значения как значения по умолчанию:

// $row = ['name' => 'John', 'age' => '33', /* ... */];
$form->setDefaults($row);

Вызывайте setDefaults() после определения элементов.

У уже отправленной формы setDefaults() ничего не делает: он не перезапишет то, что заполнил пользователь, так что вызывать его в фабрике формы безусловно безопасно. Если вам нужно задать значения принудительно и после отправки, используйте вместо него setValues().

Отрисовка формы

По умолчанию форма отрисовывается как таблица. Отдельные элементы соблюдают основные правила доступности: все метки порождаются как элементы <label> и связаны с соответствующими элементами формы. Щелчок по метке автоматически ставит курсор в поле формы.

Каждому элементу мы можем задать произвольные HTML-атрибуты. Например, добавить placeholder:

$form->addInteger('age', 'Возраст:')
	->setHtmlAttribute('placeholder', 'Заполните, пожалуйста, возраст');

Способов отрисовать форму много, поэтому отрисовке посвящена отдельная глава.

Отрисовка с помощью Latte

Если у вас под рукой шаблонизатор Latte, вы можете поручить отрисовку формы ему и получить полный контроль над итоговым HTML. Вы создаёте движок, регистрируете расширение форм и передаёте форму в шаблон переменной:

$latte = new Latte\Engine;
$latte->addExtension(new Nette\Bridges\FormsLatte\FormsExtension);

$latte->render('form.latte', ['form' => $form]);

В шаблоне вы затем работаете с формой через переменную $form и теги вроде {input}, {label} или n:name. Полный пример вместе с шаблоном найдёте в каталоге examples (файлы latte.php и latte/). Отдельные теги описаны в главе об отрисовке.

Отображение в классы

Вернёмся к обработке данных формы. Метод getValues() возвращал отправленные данные как объект ArrayHash. Поскольку это универсальный класс, похожий на stdClass, при работе с ним нам не хватает определённых удобств, например автодополнения свойств в редакторах или статического анализа кода. Это можно решить, заведя для каждой формы отдельный класс, свойства которого представляют отдельные элементы. Например:

class RegistrationFormData
{
	public string $name;
	public ?int $age;
	public string $password;
}

Как вариант, можно использовать конструктор:

class RegistrationFormData
{
	public function __construct(
		public string $name,
		public ?int $age,
		public string $password,
	) {
	}
}

Свойства класса данных могут быть и перечислениями, и они будут отображены автоматически.

Как сказать Nette, чтобы она возвращала данные как объекты этого класса? Проще, чем вы думаете. Достаточно указать параметром имя класса или объект для наполнения:

$data = $form->getValues(RegistrationFormData::class);
$name = $data->name;

В качестве параметра можно указать и 'array', тогда данные вернутся массивом.

Если формы состоят из многоуровневой структуры из контейнеров, создайте для каждого отдельный класс:

$form = new Form;
$person = $form->addContainer('person');
$person->addText('firstName');
/* ... */

class PersonFormData
{
	public string $firstName;
	public string $lastName;
}

class RegistrationFormData
{
	public PersonFormData $person;
	public ?int $age;
	public string $password;
}

Тогда отображение по типу свойства $person понимает, что контейнер нужно отобразить в класс PersonFormData. Если бы свойство содержало массив контейнеров, укажите тип array, а класс для отображения передайте прямо контейнеру:

$person->setMappedType(PersonFormData::class);

Заготовку класса данных формы можно породить методом Nette\Forms\Blueprint::dataClass($form), который выведет её на страницу в браузере. Дальше достаточно щелчком выделить код и скопировать его в проект.

Несколько кнопок отправки

Если у формы больше одной кнопки, нам обычно нужно различить, какая была нажата. Эти сведения возвращает метод кнопки isSubmittedBy():

$form->addSubmit('save', 'Сохранить');
$form->addSubmit('delete', 'Удалить');

if ($form->isSuccess()) {
	if ($form['save']->isSubmittedBy()) {
		// ...
	}

	if ($form['delete']->isSubmittedBy()) {
		// ...
	}
}

Не опускайте проверку $form->isSuccess(), она проверяет корректность данных.

Когда форма отправляется нажатием клавиши Enter, это считается отправкой первой кнопкой.

Защита от уязвимостей

Nette Framework уделяет безопасности большое внимание и поэтому тщательно следит за должной защитой форм.

Кроме защиты форм от известных уязвимостей вроде Cross-Site Scripting (XSS) и Cross-Site Request Forgery (CSRF), она выполняет множество мелких мер безопасности, о которых вам больше не нужно думать.

Например, она отфильтровывает из ввода все управляющие символы и проверяет корректность кодировки UTF-8, благодаря чему данные из формы всегда будут чистыми. У выпадающих списков и радиосписков она проверяет, что выбранные пункты действительно были среди предложенных и что подделки не произошло. Мы уже упоминали, что у однострочных текстовых полей она заменяет пробелами символы конца строки, которые мог отправить злоумышленник. У многострочных полей она приводит символы конца строки к единому виду. И так далее.

Nette решает за вас риски безопасности, о существовании которых многие программисты не подозревают.

Упомянутая атака CSRF состоит в том, что злоумышленник заманивает жертву на страницу, которая незаметно выполняет в браузере жертвы запрос к серверу, где жертва в этот момент авторизована. Сервер считает, что запрос сделала жертва по своей воле. Поэтому Nette отклоняет POST-формы, отправленные с чужого источника; чужим считается даже другой поддомен того же сайта. Если вам нужно разрешить отправку с другого источника, отключите защиту так:

$form->allowCrossOrigin(); // ВНИМАНИЕ! Полностью отключает защиту!

Правда, это отключает защиту для любого источника. Чтобы разрешить только определённые источники, отключите защиту и сами сверяйте заголовок Origin со своим списком разрешённых.

Защита опирается на браузерный заголовок Sec-Fetch-Site (Fetch Metadata), который браузер отправляет автоматически и который нельзя подделать даже при наличии XSS-уязвимости. Старые браузеры, которые эти заголовки не отправляют, проверку не пройдут. Подробно это описано в статье Браузер наконец решает CSRF.

Прежняя защита с помощью авторизационного токена в сессии, включаемая через $form->addProtection(), больше не нужна и объявлена устаревшей начиная с версии 3.3.

Итак, мы бегло познакомились с формами в Nette. За дополнительным вдохновением загляните в каталог examples в дистрибутиве.