Формы отдельно от фреймворка
****************************
.[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 на ту же страницу. И то и
другое можно изменить:
Форму и её отдельные элементы называют компонентами. Они образуют
дерево компонентов, корнем которого является форма. К отдельным
элементам формы можно обратиться так:
Прозвучало слово корректна, но у формы пока нет никаких правил
проверки. Исправим это.
Имя будет обязательным, поэтому пометим его методом setRequired().
Его аргумент – текст сообщения об ошибке, которое отобразится, если
пользователь имя не заполнит. Если аргумент не указан, будет
использовано стандартное сообщение об ошибке.
Попробуйте отправить форму, не заполнив имя, и вы увидите, что
появится сообщение об ошибке. Браузер или сервер будут её отклонять,
пока вы поле не заполните.
При этом систему не обмануть, введя в поле только пробелы. Никак. Nette
автоматически обрезает пробелы слева и справа. Попробуйте. Это то, что
нужно всегда делать с каждым однострочным полем, но об этом часто
забывают. Nette делает это автоматически. (Можете попробовать одурачить
форму, отправив в качестве имени многострочную строку. И тут Nette не
проведёшь, переводы строк будут заменены пробелами.)
Форма всегда проверяется на стороне сервера, но порождается и
проверка на JavaScript. Она выполняется мгновенно, и пользователь узнаёт об
ошибках сразу, без необходимости отправлять форму на сервер. За это
отвечает скрипт netteForms.js. Вставьте его в страницу:
Если вы посмотрите на исходный код страницы с формой, то заметите, что
Nette помещает обязательные элементы в элементы с CSS-классом required.
Попробуйте добавить в шаблон следующий стиль, и метка “Имя” станет
красной. Так вы элегантно выделите для пользователей обязательные
элементы:
Другие правила проверки мы добавляем методом 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 намного шире: можно создавать
условия, по ним показывать и скрывать части страницы и т. д. Обо всём вы
узнаете в главе о проверке форм.
Значения по умолчанию
Значения по умолчанию для элементов формы мы задаём часто:
Часто бывает полезно задать значения по умолчанию сразу всем
элементам, например когда форма используется для редактирования
записей. Мы считываем запись из базы данных и задаём её значения как
значения по умолчанию:
Вызывайте setDefaults() после определения элементов.
У уже отправленной формы setDefaults() ничего не делает: он не
перезапишет то, что заполнил пользователь, так что вызывать его в
фабрике формы безусловно безопасно. Если вам нужно задать значения
принудительно и после отправки, используйте вместо него
setValues().
Отрисовка формы
По умолчанию форма отрисовывается как таблица. Отдельные элементы
соблюдают основные правила доступности: все метки порождаются как
элементы <label> и связаны с соответствующими элементами формы.
Щелчок по метке автоматически ставит курсор в поле формы.
Каждому элементу мы можем задать произвольные HTML-атрибуты. Например,
добавить placeholder:
Если у вас под рукой шаблонизатор 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, чтобы она возвращала данные как объекты этого класса?
Проще, чем вы думаете. Достаточно указать параметром имя класса или
объект для наполнения:
В качестве параметра можно указать и '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 уделяет безопасности большое внимание и поэтому тщательно
следит за должной защитой форм.
Например, она отфильтровывает из ввода все управляющие символы и
проверяет корректность кодировки UTF-8, благодаря чему данные из формы
всегда будут чистыми. У выпадающих списков и радиосписков она
проверяет, что выбранные пункты действительно были среди предложенных
и что подделки не произошло. Мы уже упоминали, что у однострочных
текстовых полей она заменяет пробелами символы конца строки, которые
мог отправить злоумышленник. У многострочных полей она приводит
символы конца строки к единому виду. И так далее.
Nette решает за вас риски безопасности, о существовании которых многие
программисты не подозревают.
Упомянутая атака CSRF состоит в том, что злоумышленник заманивает
жертву на страницу, которая незаметно выполняет в браузере жертвы
запрос к серверу, где жертва в этот момент авторизована. Сервер
считает, что запрос сделала жертва по своей воле. Поэтому Nette отклоняет
POST-формы, отправленные с чужого источника; чужим считается даже другой
поддомен того же сайта. Если вам нужно разрешить отправку с другого
источника, отключите защиту так:
$form->allowCrossOrigin(); // ВНИМАНИЕ! Полностью отключает защиту!
Правда, это отключает защиту для любого источника. Чтобы разрешить
только определённые источники, отключите защиту и сами сверяйте
заголовок Origin со своим списком разрешённых.
Защита опирается на браузерный заголовок Sec-Fetch-Site (Fetch Metadata),
который браузер отправляет автоматически и который нельзя подделать
даже при наличии XSS-уязвимости. Старые браузеры, которые эти заголовки
не отправляют, проверку не пройдут. Подробно это описано в статье Браузер наконец решает CSRF.
Прежняя защита с помощью авторизационного токена в сессии,
включаемая через $form->addProtection(), больше не нужна и объявлена
устаревшей начиная с версии 3.3.
Итак, мы бегло познакомились с формами в Nette. За дополнительным
вдохновением загляните в каталог examples в дистрибутиве.