Форми, използвани самостоятелно
Nette Forms значително улесняват създаването и обработката на уеб форми. Можете да ги използвате във вашите приложения напълно самостоятелно, без останалата част от framework-а, което ще покажем в тази глава.
Но ако използвате Nette Application и презентери, за вас е предназначено ръководството за използване в презентери.
Първа форма
Нека опитаме да напишем проста форма за регистрация. Кодът й ще бъде следният (целия код):
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
се използва в презентери). Добавихме към нея т.нар. елементи име, парола
и бутон за изпращане.
А сега да оживим формата. С проверка на $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 автоматично премахва левите и десните интервали. Опитайте. Това е нещо, което винаги трябва да правите с всеки едноредов input, но често се забравя. 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()
, с който ще предадем на валидатора изисквания
диапазон като двойка [от, до]
:
$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', 'E-mail')
->setDefaultValue($lastUsedEmail);
Често е полезно да се зададат стойности по подразбиране на всички елементи едновременно. Например, когато формата служи за редактиране на записи. Прочитаме записа от базата данни и задаваме стойностите по подразбиране:
//$row = ['name' => 'John', 'age' => '33', /* ... */];
$form->setDefaults($row);
Извиквайте setDefaults()
след дефинирането на елементите.
Рендиране на формата
Стандартно формата се рендира като таблица. Отделните елементи
отговарят на основното правило за достъпност – всички надписи са
записани като <label>
и са свързани със съответния елемент на
формата. При кликване върху надписа курсорът автоматично се появява в
полето на формата.
На всеки елемент можем да задаваме произволни HTML атрибути. Например да добавим placeholder:
$form->addInteger('age', 'Възраст:')
->setHtmlAttribute('placeholder', 'Моля, попълнете възрастта');
Начините за рендиране на форма са наистина много, затова на това е посветена самостоятелна глава за рендиране.
Мапиране към класове
Да се върнем към обработката на данните от формата. Методът
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,
) {
}
}
Свойствата на класа с данни могат да бъдат и enum-и и те ще бъдат автоматично мапирани.
Как да кажем на 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 кодирането, така че данните от формата винаги ще бъдат чисти. При select кутиите и radio списъците проверява дали избраните елементи са били действително от предлаганите и дали не е имало подправяне. Вече споменахме, че при едноредовите текстови входове премахва знаците за край на ред, които нападателят е могъл да изпрати. При многоредовите входове пък нормализира знаците за край на ред. И така нататък.
Nette решава вместо вас рисковете за сигурността, за които много програмисти дори не подозират, че съществуват.
Споменатата CSRF атака се състои в това, че нападателят примамва жертвата на страница, която незабележимо в браузъра на жертвата изпълнява заявка към сървъра, на който жертвата е влязла, и сървърът смята, че заявката е била изпълнена от жертвата по нейна воля. Затова Nette предотвратява изпращането на POST форма от друг домейн. Ако по някаква причина искате да изключите защитата и да позволите изпращането на формата от друг домейн, използвайте:
$form->allowCrossOrigin(); // ВНИМАНИЕ! Изключва защитата!
Тази защита използва SameSite бисквитка с име _nss
. Затова
създавайте обекта на формата преди изпращането на първия изход, за да
може бисквитката да бъде изпратена.
Защитата с помощта на SameSite бисквитка може да не е 100% надеждна, затова е препоръчително да включите и защита с помощта на токен:
$form->addProtection();
Препоръчваме да защитавате по този начин формите в
административната част на сайта, които променят чувствителни данни в
приложението. Framework-ът се защитава срещу CSRF атака чрез генериране и
проверка на оторизационен токен, който се съхранява в сесията. Затова е
необходимо преди показването на формата да има отворена сесия. В
административната част на сайта обикновено сесията вече е стартирана
поради влизането на потребителя. В противен случай стартирайте
сесията с метода Nette\Http\Session::start()
.
Така, преминахме през бързо въведение във формите в Nette. Опитайте да разгледате още директорията examples в дистрибуцията, където ще намерите повече вдъхновение.