Формуляри, използвани офлайн
Nette Forms значително опростява създаването и обработката на уеб формуляри. Можете да ги използвате в приложенията си напълно самостоятелно, без останалата част от рамката, както ще демонстрираме в тази глава.
Въпреки това, ако използвате приложение Nette и Presenters, има ръководство за вас: Forms in Presenters.
Първата форма
Ще се опитаме да напишем прост формуляр за регистрация. Нейният код ще изглежда по следния начин (пълен код)
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($form['name']);
Правила за валидиране
Думата valid е използвана няколко пъти, но формулярът все още няма правила за валидиране. Нека поправим това.
Името ще бъде задължително, затова ще го маркираме с метода
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()
, самия диапазон:
$form->addInteger('age', 'Возраст:')
->addRule($form::Range, 'Вы должны быть старше 18 лет и иметь возраст до 120 лет.', [18, 120]);
Ако потребителят не попълни полето, правилата за валидиране няма да бъдат проверени, тъй като полето е незадължително.
Очевидно е, че тук има място за малко преработване. В съобщението за
грешка и в третия параметър числата са изброени двойно, което не е
идеално. Ако създаваме многоезичен формуляр и
съобщението, съдържащо числата, трябва да бъде преведено на няколко
езика, това би затруднило промяната на стойностите. По тази причина
можем да използваме заместващи символи %d
:
->addRule($form::Range, 'Вы должны быть старше %d лет и иметь возраст до %d лет.', [18, 120]);
Нека се върнем към полето парола, да го направим задължително
и да проверим минималната дължина на паролата ($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', 'Имейл')
->setDefaultValue($lastUsedEmail);
Често е полезно да зададете стойности по подразбиране за всички контроли едновременно. Например, когато формулярът се използва за редактиране на записи. Прочитаме запис от базата данни и го задаваме като стойност по подразбиране:
//$row = ['name' => 'John', 'age' => '33', /* ... */];
$form->setDefaults($row);
Извикайте setDefaults()
след дефиниране на контролите.
Форма за показване
По подразбиране формулярът се показва като таблица. Отделните
контроли следват основните насоки за достъпност на уеб страниците.
Всички етикети се генерират като елементи <label>
и са свързани
с техните елементи, като щракването върху маркер ще премести курсора
върху съответния елемент.
Можем да зададем всякакви атрибути на HTML за всеки елемент. Например, добавете заместител:
$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,
) {
}
}
Свойствата на класа за данни също могат да бъдат енуми и те ще бъдат автоматично картографирани.
Как да кажем на 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 полага големи усилия за гарантиране на сигурността и тъй като формулярите са най-често срещаният вид въвеждане на данни от потребителя, формулярите на Nette са толкова добри, колкото и непробиваеми.
В допълнение към защитата на формулярите срещу атаки от известни уязвимости, като например Cross-Site Scripting (XSS) и Cross-Site Request Forgery (CSRF), тя изпълнява много малки задачи по сигурността, за които вече не е необходимо да мислите.
Например той филтрира всички контролни символи от входните данни и валидира кодирането UTF-8, така че данните от формуляра ви винаги ще бъдат чисти. За полетата за избор и радиосписъците се проверява дали избраните елементи са наистина от предлаганите елементи и дали няма фалшиви. Вече споменахме, че при въвеждане на текст от един ред тя премахва символите от края на реда, които атакуващият може да изпрати там. При многоредово въвеждане се нормализират символите в края на реда. И така нататък.
Nette елиминира за вас уязвимостите в сигурността, за които повечето програмисти дори не подозират, че съществуват.
Споменатата по-горе CSRF атака се състои в това, че нападателят примамва жертвата да посети страница, която безшумно изпълнява заявка в браузъра на жертвата към сървъра, в който жертвата е влязла в момента, и сървърът приема, че заявката е направена от жертвата по нейно желание. По този начин Nette предотвратява изпращането на формуляра чрез POST от друг домейн. Ако по някаква причина искате да деактивирате защитата и да позволите формулярът да бъде изпратен от друг домейн, използвайте
$form->allowCrossOrigin(); // ПРЕДУПРЕЖДЕНИЕ! Деактивира защитата!
Тази защита използва бисквитка на SameSite с име _nss
. Затова
създайте формуляра преди първия изход, за да може да се изпрати
„бисквитката“.
Защитата на бисквитките на SameSite може да не е 100% надеждна, затова е добре да активирате защитата с токени:
$form->addProtection();
Силно препоръчително е да приложите тази защита към формулярите в
административната част на приложението, които модифицират
чувствителни данни. Рамката се защитава от CSRF атака чрез генериране и
валидиране на токен за удостоверяване, който се съхранява в сесията
(аргументът е съобщение за грешка, което се показва, ако токенът е
изтекъл). Затова е необходимо да бъде стартирана сесия, преди да бъде
показан формулярът. В административната част на сайта сесията
обикновено вече е започнала, тъй като потребителят е влязъл в
системата. В противен случай стартирайте сесията, като използвате
метода Nette\Http\Session::start()
.
И така, запознахме се накратко с формите в Нете. Опитайте се да потърсите вдъхновение в директорията с примери в дистрибуцията.