Zachytávání, validace a předání dat
Obecně řečeno zpracování formuláře se dá rozdělit na tři kroky:
- Zachycení vstupu
Tedy zachycení vstupních dat. Nejčastěji z POST, nebo GET, ale jak si později ukážeme, tak to není podmínkou. Lze "zachytit" jakákoliv data jakéhokoliv původu. - Validace
Zachycená data je samozřejmě nutné ověřit dle definovaných pravidel a/nebo pomocí různých validátorů. Bez toho data nelze "pustit" dál. - Předání data
S daty která máme zachycená a víme že jsou v pořádku jistě budeme chtít něco dělat. Je to vlastně závěrečný krok práce s formulářem a také samostatné téma.
Zachycení vstupu
K zachycení vstupních dat slouží metoda catchInput, která vrací true/false podle toho zda byl formulář odeslán, nebo ne.
if( $form->catchInput() ) {
//Form has been sent
//... ... ...
}
Jak jsme si již řekli v kapitole o definici, tak každý formulář má jméno, které se posílá ve skrytém formulářovém poli a podle toho lze identifikovat že byl právě onen formulář odeslán i když na stránce může být víc formulářů.
Ovšem také jsme si řekli, že to není jediná možnost jak data zachytit. Například pokud potřebujeme formulářem zachytávat a validovat data v rámci REST API, tak těžko budeme chtít po uživateli API zasílání nějaké "kouzelné hodnoty". Proto lze vynutit zachycení formuláře i bez existence speciálního pole:
$form->catchInput( force_catch: true ) );
Tedy formulář je zachycen i bez pole s názvem formuláře. Stále však zachytává data z POST, nebo GET (podle toho jak je definován).
Ovšem již jsme si řekli že formulář nemusí nutné zachytávat data z POST, nebo GET, ale libovolná data odkudkoliv. To lze řešit takto:
$form->catchInput(
input_data: $my_data,
force_catch: true
);
Data musí mít buď podobu pole, nebo instance Jet\Data_Array.
Validace
Abychom si objasnili validaci, tak musíme ještě zabrousit na téma definice formulářů. Řada typů polí má integrovanou základní validaci. Například číslená pole Form_Field_Int a Form_Field_Float disponují možností omezit rozsah zadaných čísel, pole pro email Form_Field_Email validuje adresu automaticky, obecné pole Form_Field_Input umožňuje zadat regulární výraz pro kontrolu formátu a tak dále. Viz typy formulářových polí.
Ovšem tato integrovaná validace zdaleka nemusí stačit. Představte si že tvoříte registrační formulář a potřebujete zjistit zda je uživatel s daným e-mailem již registrovaný. Nebo zda je zadávané heslo dostatečně silné. Možností je celá řada. Proto Jet disponuje možností definovat polím validárory.
Ale než se dostaneme k validátorům, tak se musíme kouknout na definování chybových kódů. A tím začneme.
Chybové kódy
Když si projdete typy formulářových polí, tak si všimnete, že drtivá většina polí má nějaké výchozí chybové kódy. Tyto kódy reprezentují chybové stavy při validaci v závislosti na typu pole. Jet po vás dokonce bude vyžadovat, aby pro daný relevantní chybový kód byla definována chybová hláška v opačném případě formulář vyhodí výjimku. Co to znamená? Pojďme si to ukázat v praxi:
use Jet\Form;
Máme registrační formulář s polem uživatelské jméno. Toto pole je označeno jako povinné ($username_field->setIsRequired( true )), ale není definována vůbec žádná chybová hláška. Tedy formulář je neplatný a při pokusu o jeho použití Jet vyhodí výjimku. Aby vše správně fungovalo, tak je nutné chybovou hlášku pro správný chybový kód definovat:
use Jet\Form_Field_Input;
$username_field = new Form_Field_Input('username', 'Username:');
$username_field->setIsRequired( true );
$reg_form = new Form('registration_form', [
$username_field
]);
use Jet\Form;
A teď je to správně. Pole se od teď automaticky kontroluje jako povinné, uživateli může zobrazit chybová hláška (nebo se může vygenerovat chybové hlášení v odpovědi REST API). Jet vám napomáhá vytvořit konzistentní aplikaci.
use Jet\Form_Field_Input;
$username_field = new Form_Field_Input('username', 'Username:');
$username_field->setIsRequired( true );
$username_field->setErrorMessages([
Form_Field_Input::ERROR_CODE_EMPTY => 'Please enter your username'
]);
$reg_form = new Form('registration_form', [
$username_field
]);
Poznámka: Všimněte si prosím, že chybová hláška při definici není "prohnána" přes překladač. Formuláře jsou na překladač napojeny automaticky - viz samostatná kapitola.
Samozřejmě že výchozí chybové kódy nejsou konečným seznamem chybových kódů se kterými může formulář operovat. Další chybové kódy si můžete (nebo vlastně musíte) definovat dle vašich potřeb. Například při tvorbě vlastního validátoru.
Validátory
Plynule navážeme a vylepšíme náš zárodek registračního formuláře. Již máme kontrolu zda uživatel vůbec údaj zadal. To se od teď již bude kontrolovat. Ale jak ověřit zda uživatelské jméno nemá již někdo jiný? K tomu již budeme potřebovat vlastní validátor:
use Jet\Form;
use Jet\Form_Field_Input;
$username_field = new Form_Field_Input('username', 'Username:');
$username_field->setIsRequired( true );
$username_field->setErrorMessages([
Form_Field_Input::ERROR_CODE_EMPTY => 'Please enter your username',
'already_exists' => 'Sorry, but username %username% is already reserved'
]);
$username_field->setValidator( function( Form_Field_Input $field ) : bool {
$username = $field->getValue();
if(Auth_Visitor_User::usernameExists($username)) {
$field->setError(
code: 'already_exists',
data: ['username'=>$username]
);
return false;
}
return true;
} );
$reg_form = new Form('registration_form', [
$username_field
]);
Validátor není nic jiného, než callback, který jako jediný parametr dostane instanci pole, které má validovat. Jeho úkolem je vrátit true pokud nenarazil na problém a v případě chyby nastavit příslušný chybový kód a vrátit false.
Jistě jste si všimli, že společně s chybovým kódem je možné předávat i chybová data, která je možné rovnou doplňovat na příslušná místa do chybové hlášky (která se samozřejmě přeloží).
Výsledek validace formuláře
Názorně jsme si ukázali jak pověsit validaci na jednotlivá pole. Teď si ukážeme jak s validací pracovat na úrovni formuláře. Volání validace je jednoduché:
if(
$form->catchInput() &&
$form->validate()
) {
//Form has been sent and values are valid
//... ... ...
}
To byla základní běžná situace, kdy je formulář standardně zachycen, validován a pokud je vše v pořádku, tak se bude něco dít. Případné chybové hlášky se u formuláře zobrazí automaticky.
Ale co když prostě budeme potřebovat seznam chyb, pokud validace neprojde? Uděláme to takto:
if( $form->catchInput() ) {
Tedy s chybami je možné dál operovat. Ať už se všemi, nebo jednotlivě, na jednotlivých polích.
if($form->validate()) {
//Form has been sent and values are valid
//... ... ...
} else {
$errors = $form->getValidationErrors();
var_dump($errors);
//... ... ...
if(!$username_field->isValid()) {
var_dump(
$username_field->getLastErrorCode(),
$username_field->getLastErrorCode()
);
}
}
}
Ovlivnění validace
Představme si tuto situace: Máme objednávkový formulář e-shopu. Ten obsahuje checkbox "nákup na firmu". Pokud je tento zaškrtnutý, tak je nutné začít validovat firemní údaje. V opačném případě nejsou údaje povinné. Jak to udělat? Ovlivnit formulář po jeho zachycení, před validací:
if( $form->catchInput() ) {
if($form->field('is_company_order')->getValue()) {
$form->field('company_name')->setIsRequired( true );
$form->field('company_id')->setIsRequired( true );
$form->field('company_vat_id')->setIsRequired( true );
}
if($form->validate()) {
//Form has been sent and values are valid
//... ... ...
}
}
Předání dat
Formulář již máme zachycený, validovaný a je možné operovat se zachycenými daty.
Prosté získání hodnot
První základní možností (ale mnou samotným v praxi používanou minimálně) je převzetí všech hodnot formuláře v podobě pole:
if(
$form->catchInput() &&
$form->validate()
) {
var_dump( $form->getValues() );
}
Zachytávače hodnot
Mnohem častěji používám zachytávače hodnot. Pak použití formuláře vypadá takto:
if(
Nebo ještě běžněji (jak je vidět v ukázkové aplikaci) takto:
$form->catchInput() &&
$form->validate()
) {
$form->catchFieldValues();
//Form has been sent, is valid ....
}
if( $form->catch() ) {
//Form has been sent, is valid ....
}
A to je vše! Data se kouzlem dostanou tam kam potřebuji a konec. No dobře ... Dělám si legraci. Ano, běžně se takto formulář používá a automaticky generované formuláře mapované na třídy (nejčastěji z DataModel a konfiguračního systému) jsou již připraveny na tento styl použití.
Ale samozřejmě si povíme proč a jak to vlastně funguje. Málo který formulář bude osamocený. Většina formulářů se bude týkat nějakého objektu. Například registrační formulář se bude týkat uživatele - tedy nějaké instance třídy představující uživatele. Nebo objednávkový formulář v e-shopu se bude týkat objektu objednávka, formulář pro editaci popisu zboží se bude týkat objektu zboží, tento formulář ve kterém právě píšu text se týká objektu článek dokumentace a tak dále.
A co potřebujeme aby se stalo po té co je formulář zachycen a víme že je validní? Potřebujeme "přelít" data z formuláře do objektu kterého se formulář týká. Tedy když tento text uložím, tak tento text doteče až do vlastnosti třídy představující článek a tato třída je následně uložena (je to samozřejmě DataModel).
Jak již bylo řečeno, tak pokud používáte to co Jet umí, tak se o to musíte starat pouze pokud je to nějaká speciální situace. Jinak se toto mapování děje automaticky. Ale je dobré, nebo vlastně nutné, vědět jak to funguje. Zkusme si představit situaci, že děláme registraci uživatele, ale uživatel nebude standardně uložen do nějaké databáze (tedy nepoužijete DataModel), ale třeba pomocí nějakého API bude poslán do nějaké služby.
Stále platí co bylo řečeno na začátku. Uživatel je nějaký objekt - tedy instance nějaké třídy. Ovšem v rámci této třídy si vše uděláme tak říkajíc ručně:
use Jet\Form;
use Jet\Form_Field_Input;
class MyUser {
protected string $username = '';
protected ?Form $reg_form = null;
public function getUsername(): string
{
return $this->username;
}
public function setUsername( string $username ): void
{
$this->username = $username;
}
public function getRegForm() : Form
{
if(!$this->reg_form) {
$username_field = new Form_Field_Input(
name: 'username',
label: 'Username'
);
$username_field->setDefaultValue( $this->username );
$username_field->setIsRequired( true );
$username_field->setErrorMessages([
//... ... ..
]);
$username_field->setValidator(function( Form_Field_Input $field ) : bool {
//... ... ...
});
//************************************
//************************************
$username_field->setFieldValueCatcher(function( $value ) {
$this->setUsername($value);
});
//************************************
//************************************
$this->reg_form = new Form('reg_form', [
$username_field
]);
}
return $this->reg_form;
}
public function save() : void
{
//TODO: ... ...
}
}
Třída i s formulářem je nachystána. Teď stačí ji použít v nějakém kontroleru:
$new_user = new MyUser();
$form = $new_user->getRegForm();
if($form->catch()) {
$new_user->save();
}
Pozor! Možná vás teď nepotěším, ale to co jsme si teď ukázali je zbytečné. V praxi lze takovou situaci řešit jinak pomocí mapování tříd na formuláře. Ale každopádně znát tento princip je důležité a v některých situacích je nutné jej použít. Tedy zas tak zbytečné to není :-) ;-).
Kam dál?
Teď už umíte formulář definovat, zachytit, validovat a prostě použít. Co dál? Je čas kouknout na to jak formuláře zobrazovat.