Önállóan használt űrlapok

A Nette Forms jelentősen megkönnyíti a webes űrlapok létrehozását és feldolgozását. Alkalmazásaiban teljesen önállóan, a keretrendszer többi része nélkül is használhatja őket, amit ebben a fejezetben bemutatunk.

Ha azonban Nette alkalmazást és prezentereket használ, akkor van egy útmutató az Ön számára: Forms in presenters.

Első űrlap

Megpróbálunk írni egy egyszerű regisztrációs űrlapot. A kódja így fog kinézni (teljes kód):

use Nette\Forms\Form;

$form = new Form;
$form->addText('name', 'Name:');
$form->addPassword('password', 'Password:');
$form->addSubmit('send', 'Sign up');

És rendereljük le:

$form->render();

Az eredménynek így kell kinéznie:

Az űrlap a Nette\Forms\Form osztály objektuma (a Nette\Application\UI\Form osztály a prezenterekben használatos). Hozzáadtuk a vezérlőelemeket név, jelszó és küldés gomb.

Most újraélesztjük az űrlapot. A $form->isSuccess() megkérdezésével megtudjuk, hogy az űrlapot elküldtük-e, és hogy érvényesen töltöttük-e ki. Ha igen, akkor ki fogjuk dobni az adatokat. Az űrlap definíciója után hozzáadjuk:

if ($form->isSuccess()) {
	echo 'Az űrlapot helyesen töltöttük ki és küldtük el';
	$data = $form->getValues();
	// $data->name tartalmazza a nevet
	// $data->password tartalmazza a jelszót
	var_dump($data);
}

A getValues() metódus az elküldött adatokat egy ArrayHash objektum formájában adja vissza. Később megmutatjuk, hogyan lehet ezt megváltoztatni. A $data változó tartalmazza a name és password kulcsokat a felhasználó által megadott adatokkal.

Általában az adatokat közvetlenül küldjük el további feldolgozásra, ami lehet például az adatbázisba való beillesztés. A feldolgozás során azonban előfordulhat hiba, például a felhasználónév már foglalt. Ebben az esetben a hibát a addError() segítségével visszaadjuk az űrlapnak, és hagyjuk, hogy újra kirajzolódjon, hibaüzenettel:

$form->addError('Sorry, username is already in use.');

Az űrlap feldolgozása után átirányítjuk a következő oldalra. Ez megakadályozza, hogy az űrlapot véletlenül újra elküldjék a frissítés, vissza gombra kattintva, vagy a böngésző előzményeit mozgatva.

Alapértelmezés szerint az űrlapot POST módszerrel küldjük el ugyanarra az oldalra. Mindkettő megváltoztatható:

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

És ez minden :-) Van egy működőképes és tökéletesen biztosított űrlapunk.

Próbáljon meg több űrlapvezérlőt hozzáadni.

Hozzáférés a vezérlőkhöz

Az űrlapot és az egyes vezérlőelemeket komponenseknek nevezzük. Ezek egy komponensfát hoznak létre, amelynek gyökere az űrlap. Az egyes vezérlőelemeket a következőképpen érheti el:

$input = $form->getComponent('name');
// alternatív szintaxis: $input = $form['name'];

$button = $form->getComponent('send');
// alternatív szintaxis: $button = $form['send'];

A vezérlőelemek eltávolítása az unset használatával történik:

unset($form['name']);

Érvényesítési szabályok

Itt az érvényes szót használták, de az űrlapnak még nincsenek érvényesítési szabályai. Javítsuk ki.

A név kötelező lesz, ezért jelöljük meg a setRequired() metódussal, amelynek argumentuma a hibaüzenet szövege, amely akkor jelenik meg, ha a felhasználó nem tölti ki. Ha nem adunk meg argumentumot, akkor az alapértelmezett hibaüzenetet használjuk.

$form->addText('name', 'Name:')
	->setRequired('Please enter a name.');

Próbáljuk meg elküldeni az űrlapot a név kitöltése nélkül, és látni fogjuk, hogy hibaüzenet jelenik meg, és a böngésző vagy a szerver elutasítja a kitöltésig.

Ugyanakkor nem fogja tudni becsapni a rendszert azzal, hogy például csak szóközöket ír be a beviteli mezőbe. Szó sem lehet róla. A Nette automatikusan levágja a bal és jobb oldali szóközöket. Próbálja ki! Ezt mindig meg kellene tennie minden egysoros bevitelnél, de gyakran elfelejtik. A Nette automatikusan elvégzi. (Megpróbálhatja becsapni az űrlapokat, és többsoros karakterláncot küldhet névként. A Nette még ebben az esetben sem fog becsapni, és a sortörések szóközökre változnak).

Az űrlap mindig a szerveroldalon validálódik, de a JavaScript validáció is generálódik, ami gyors, és a felhasználó azonnal értesül a hibáról, anélkül, hogy az űrlapot a szerverre kellene küldeni. Ezt a netteForms.js szkript kezeli. Adja hozzá az oldalhoz:

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

Ha megnézi az űrlapot tartalmazó oldal forráskódját, észreveheti, hogy a Nette a szükséges mezőket a required CSS osztályú elemekbe illeszti be. Próbálja meg hozzáadni a következő stílust a sablonhoz, és a “Név” felirat piros lesz. Elegánsan jelöljük a kötelező mezőket a felhasználók számára:

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

További érvényesítési szabályokat a addRule() módszerrel adunk hozzá. Az első paraméter a szabály, a második ismét a hibaüzenet szövege, és az opcionális érvényesítési szabály argumentum következhet. Mit jelent ez?

Az űrlap kap egy másik opcionális bemenetet életkor azzal a feltétellel, hogy annak számnak kell lennie (addInteger()) és bizonyos határok között ($form::Range). És itt fogjuk használni a addRule() harmadik argumentumát , magát a tartományt:

$form->addInteger('age', 'Age:')
	->addRule($form::Range, 'You must be older 18 years and be under 120.', [18, 120]);

Ha a felhasználó nem tölti ki a mezőt, akkor az érvényesítési szabályok nem lesznek ellenőrizve, mivel a mező opcionális.

Nyilvánvalóan van hely egy kis refaktorálásra. A hibaüzenetben és a harmadik paraméterben a számok duplikáltan szerepelnek, ami nem ideális. Ha többnyelvű űrlapot hoznánk létre, és a számokat tartalmazó üzenetet több nyelvre kellene lefordítani, ez megnehezítené az értékek módosítását. Emiatt a %d helyettesítő karakterek használhatók:

	->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]);

Térjünk vissza a jelszó mezőhöz, tegyük követelményessé, és ellenőrizzük a minimális jelszóhosszúságot ($form::MinLength), ismét az üzenetben szereplő helyettesítő karakterek segítségével:

$form->addPassword('password', 'Password:')
	->setRequired('Pick a password')
	->addRule($form::MinLength, 'Your password has to be at least %d long', 8);

Adjunk hozzá egy passwordVerify mezőt az űrlaphoz, ahol a felhasználó újra beírja a jelszót, az ellenőrzéshez. Érvényesítési szabályok segítségével ellenőrizzük, hogy mindkét jelszó azonos-e ($form::Equal). Érvként pedig szögletes zárójelek segítségével megadjuk az első jelszóra való hivatkozást:

$form->addPassword('passwordVerify', 'Password again:')
	->setRequired('Fill your password again to check for typo')
	->addRule($form::Equal, 'Password mismatch', $form['password'])
	->setOmitted();

A setOmitted() segítségével egy olyan elemet jelöltünk meg, amelynek értéke nem igazán érdekel minket, és amely csak az érvényesítés miatt létezik. Az értékét nem adjuk át a $data.

Egy teljesen működőképes űrlapunk van, PHP és JavaScript validációval. A Nette validálási lehetőségei sokkal szélesebb körűek, létrehozhatunk feltételeket, megjeleníthetjük és elrejthetjük az oldal egyes részeit ezek alapján, stb. Mindent megtudhat az űrlapok validálásáról szóló fejezetben.

Alapértelmezett értékek

Gyakran állítunk be alapértelmezett értékeket űrlapvezérlőkhöz:

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

Gyakran hasznos, ha egyszerre állítjuk be az összes vezérlőelem alapértelmezett értékeit. Például amikor az űrlapot rekordok szerkesztésére használjuk. Beolvassuk a rekordot az adatbázisból, és beállítjuk az alapértelmezett értékeket:

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

A vezérlőelemek definiálása után hívjuk meg a setDefaults() címet.

Az űrlap megjelenítése

Az űrlap alapértelmezés szerint táblázatként jelenik meg. Az egyes vezérlőelemek az alapvető webes hozzáférhetőségi irányelveket követik. Minden címke <label> elemként jön létre, és a bemenetükhöz kapcsolódik, a címkére kattintva a kurzor a bemenetre kerül.

Az egyes elemekhez bármilyen HTML-attribútumot beállíthatunk. Például adjunk hozzá egy helyőrzőt:

$form->addInteger('age', 'Age:')
	->setHtmlAttribute('placeholder', 'Please fill in the age');

Valóban sokféleképpen lehet megjeleníteni egy űrlapot, ezért ez egy külön fejezet a megjelenítésről.

Osztályok leképezése

Térjünk vissza az űrlapadatok feldolgozásához. A getValues() metódus a ArrayHash objektumként adta vissza a beküldött adatokat. Mivel ez egy általános osztály, valami olyasmi, mint a stdClass, hiányozni fog néhány kényelmi funkció a vele való munka során, például a tulajdonságok kódkiegészítése a szerkesztőkben vagy a statikus kódelemzésben. Ezt úgy lehetne megoldani, ha minden űrlaphoz külön osztály lenne, amelynek tulajdonságai az egyes vezérlőelemeket képviselik. Pl:

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

A PHP 8.0-tól kezdve használhatja ezt az elegáns jelölést, amely egy konstruktort használ:

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

Hogyan mondjuk meg a Nette-nek, hogy az adatokat ennek az osztálynak az objektumaként adja vissza nekünk? Egyszerűbb, mint gondolnád. Mindössze annyit kell tennünk, hogy paraméterként megadjuk a hidratálandó osztály nevét vagy objektumát:

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

A 'array' is megadható paraméterként, és akkor az adatok tömbként térnek vissza.

Ha az űrlapok konténerekből álló többszintű struktúrából állnak, hozzon létre mindegyikhez külön osztályt:

$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;
}

A leképezés ekkor a $person tulajdonságtípusból tudja, hogy a konténert a PersonFormData osztályhoz kell leképeznie. Ha a tulajdonság tárolók tömbjét tartalmazná, adja meg a array típust, és adja át a leképezendő osztályt közvetlenül a tárolóhoz:

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

Egy űrlap adatosztályának javaslatát a Nette\Forms\Blueprint::dataClass($form) metódussal generálhatja, amely a böngészőoldalra nyomtatja ki. Ezután egyszerűen rákattintva kiválaszthatja és bemásolhatja a kódot a projektjébe.

Többszörös beküldőgombok

Ha az űrlapon egynél több gomb van, általában meg kell különböztetnünk, hogy melyiket nyomta meg. A gomb isSubmittedBy() metódusa adja vissza nekünk ezt az információt:

$form->addSubmit('save', 'Mentés');
$form->addSubmit('delete', 'Törlés');

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

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

Ne hagyjuk ki a $form->isSuccess(), hogy ellenőrizzük az adatok érvényességét.

Ha egy űrlapot az Enter billentyűvel küldünk be, azt úgy kezeljük, mintha az első gombbal küldtük volna be.

Sebezhetőségi védelem

A Nette Framework nagy erőfeszítéseket tesz a biztonság érdekében, és mivel az űrlapok a leggyakoribb felhasználói bevitel, a Nette űrlapok szinte áthatolhatatlanok.

Amellett, hogy védi az űrlapokat az olyan jól ismert sebezhetőségek támadása ellen, mint a Cross-Site Scripting (XSS ) és a Cross-Site Request Forgery (CSRF ), sok apró biztonsági feladatot is elvégez, amelyekre már nem kell gondolnia.

Például kiszűri az összes vezérlő karaktert a bemenetekből, és ellenőrzi az UTF-8 kódolás érvényességét, így az űrlapból származó adatok mindig tiszták lesznek. A kiválasztó dobozok és rádiólisták esetében ellenőrzi, hogy a kiválasztott elemek valóban a felkínáltak közül kerültek-e ki, és nem történt-e hamisítás. Már említettük, hogy az egysoros szövegbevitel esetében eltávolítja a sor végi karaktereket, amelyeket egy támadó oda küldhet. Többsoros bevitel esetén normalizálja a sor végi karaktereket. És így tovább.

A Nette olyan biztonsági réseket is kijavít az Ön számára, amelyekről a legtöbb programozónak fogalma sincs, hogy léteznek.

Az említett CSRF-támadás lényege, hogy a támadó egy olyan oldal meglátogatására csábítja az áldozatot, amely némán végrehajt egy kérést az áldozat böngészőjében a szerver felé, ahová az áldozat éppen be van jelentkezve, és a szerver azt hiszi, hogy a kérést az áldozat akaratából tette. Ezért a Nette megakadályozza, hogy az űrlapot egy másik tartományból POST-on keresztül küldjék el. Ha valamilyen oknál fogva ki akarja kapcsolni a védelmet, és engedélyezni szeretné, hogy az űrlapot egy másik tartományból küldjék el, használja a következőt:

$form->allowCrossOrigin(); // FIGYELEM! Kikapcsolja a védelmet!

Ez a védelem a _nss nevű SameSite cookie-t használja. Ezért hozzon létre egy űrlapot az első kimenet kiürítése előtt, hogy a cookie-t el lehessen küldeni.

A SameSite cookie-védelem nem biztos, hogy 100%-ig megbízható, ezért érdemes bekapcsolni a token-védelmet:

$form->addProtection();

Erősen ajánlott ezt a védelmet alkalmazni az alkalmazás adminisztrációs részében lévő, érzékeny adatokat módosító űrlapokra. A keretrendszer a CSRF-támadás ellen a munkamenetben tárolt hitelesítési token generálásával és érvényesítésével védekezik (az érv a token lejárta esetén megjelenő hibaüzenet). Ezért szükséges, hogy az űrlap megjelenítése előtt elinduljon egy munkamenet. A webhely adminisztrációs részében a munkamenet általában már elindult, a felhasználó bejelentkezése miatt. Ellenkező esetben a munkamenetet a Nette\Http\Session::start() metódussal kell elindítani.

Tehát van egy gyors bevezetésünk a Nette űrlapokba. További inspirációért próbálja megkeresni a disztribúcióban található példák könyvtárát.

verzió: 4.0