Custom validace

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
mkrause
Člen | 20
+
0
-

Ahoj Davide,

zdá se mi, že custom validace vstupů z formuláře není momentálně to pravé ořechové. Pokud chci přidat vlastní validační pravidlo, musím zřejmě udělat jednu z následujících věcí:

  1. vytvořit si potomka patřičného formulářového pole a přepsat jeho metodu validate
  2. ve zpracování formuláře po zavolání isValid provést vlastní validaci, nasetovat případné chyby pomocí metody addError a pak se vrátit zpět do té větve zpracování, kde se formulář považuje za nevalidní
  3. vytvořit potomka třídy Form, která umožní po isSubmitted provést vlastní validaci, pak při volání isValid nepřepíše $errors prázdným polem a nakonec nastaví příznak valid i s ohledem na výsledky custom validace

Ani jedno mi nepřipadá moc elegantní. První varianta předpokládá, že formulářové pole bude mít přístup ke všem věcem potřebným k validaci (například databázová konexe), což vede k nežádoucím vazbám. Kromě toho IMHO není z hlediska designu hezké, když kvůli validaci musím vytvořit nový typ formulářového pole, které se od svého předka jinak vůbec neliší, a navíc tím poněkud utrpí původní vize, že validátory jsou zbytečný overhead (uživateli je celkem fuk, jestli musí napsat validátor nebo potomka jiné třídy).

Druhá varianta vede k menší přehlednosti hlavního kódu okolo zpracování formuláře (formulář je přes isValid prohlášen za validní a pak posléze znevalidněn).

Třetí varianta je asi použitelná, ale spíš tak nějak z nouze – psát potomka formuláře kvůli validaci jeho polí také není nic moc.

Co si o tom myslíš?

David Grudl
Nette Core | 8108
+
0
-

Podporu custom validace jsem implementoval do „nových“ formulářů. Jde o rozšíření současného zápisu pravidel $control->addRule(Form::FILLED, 'Message') tak, že místo konstanty je možné uvést vlastní callback:

class MyValidators
{
    public static function oddNumber($control /*, $arg]*/)
    {
        // pokud je podmínka splněna, metoda vrací TRUE; jinak FALSE
        return (bool) $control->value % 2;
    }
}

$form = new Form();
$form->addText('field', 'Odd number:')
    ->addRule('MyValidators::oddNumber', 'The entered number is not odd.');

Lze použít i pseudotyp callback addRule(array($obj, 'method') ...) nebo v PHP 5.3 novou syntax:

$form = new Form();
$form->addText('number', 'Číslo:')
    ->addRule(
        function ($control) { return (bool) $control->value % 2; },
        'Zadané číslo není dostatečně liché'
    );

Pokud se však callback zapíše jako řetězec (tedy první příklad), má to výhodu v tom, že je možné ho předat i klientské straně a provést validaci v JavaScriptu.

mkrause
Člen | 20
+
0
-

Pokud se však callback zapíše jako řetězec (tedy první příklad), má to výhodu v tom, že je možné ho předat i klientské straně a provést validaci v JavaScriptu

Prošel jsem si související třídy (Nette/Forms/JavaScript) a tuhle možnost jsem v tom nějak nenašel. Díval jsem se špatně nebo tam ještě něco chybí? Existuje ekvivalent dřívějšího $form->rules->addScript()? Používali jsme to právě pro custom validaci v klientovi a také jako hack k tomu, aby se s formulářem po úspěšné validaci provedly některé speciální akce namísto normálního submitu. Jak by se tohle mělo ideálně řešit teď?

David Grudl
Nette Core | 8108
+
0
-

mkrause napsal(a):

Prošel jsem si související třídy (Nette/Forms/JavaScript) a tuhle možnost jsem v tom nějak nenašel. Díval jsem se špatně nebo tam ještě něco chybí?

Ukázkou, jak by to mohlo vypadat, je třída UserClientScript. (V současné době je do třídy Form natvrdo zadrátována třída InstantClientScript, která generuje JavaScript stejný od prvních verzí formulářů.)

Existuje ekvivalent dřívějšího $form->rules->addScript()?

Ano, dá se to udělat takto addRule(Form::SCRIPT, NULL, $script). Nebo přes extension method:

// lze od PHP 5.3
use Nette\Forms\Rules;
use Nette\Forms\Form;

Rules::extensionMethod('addScript', function(Rules $thisObj, $script)
{
	return $thisObj->addRule(Form::SCRIPT, NULL, $script);
});

// PHP 5.2
function Rules_prototype_addScript(Rules $thisObj, $script)
{
	return $thisObj->addRule(Forms::SCRIPT, NULL, $script);
}

a pak bude fungovat addScript jako dřív.

Neříkám, že je to ideální, podpora pro JavaScript není ve finálním stádiu.

Bohouš
Člen | 4
+
0
-

Čau, zkouším si udělat vlastní validátor na unikátnost uživatelského jména, pravidlo přidávám taklhle:

<?php
(...)
$form->addText('username','Uživatelské jméno')
			->addRule(array('Users_Users','userExists'),'msg');
(...)
?>

a třída Users_Users vypadá takhle:

<?php
class Users_Users extends DibiTable
{
	protected $name = 's_uzivatele'; // nejsem si jisty, jestli se to takhle v dibi pise

	public function userExists($control)
	{
		if(!$row = $this->fetch(array('username'=>$control->control->value)))
			return true;

		return false;
	}
}
?>

nastavení databáze:

database.host = "localhost"
database.driver = "mysqli"
database.username = "***"
database.database = "***"
database.password = "***"
database.charset = "utf8"

Co chci udělat je asi jasné, ale když formulář odešlu, tak mi spadne apache :-) Co dělám špatně?

Že to nemá s nette a dibi nic společného si říkám taky, ale bez problémů mi tu beží několik webů na Zendu + samozřejmě další části webu, ke kterému ta registrace patří.

David Grudl
Nette Core | 8108
+
0
-

Určitě by tam mělo být místo $control->control->value jen $control->value.

Dále pokud v pravidlu máš statickou metodu array('Users_Users','userExists'), musí být i userExists statická a nemůže používat $this. Nebo jako callback definovat nestatickou metodu array($nejakyObj,'userExists')

Bohouš
Člen | 4
+
0
-

Ah, máš recht, už to běží. Díky

Ola
Člen | 385
+
0
-

David Grudl napsal(a):

Určitě by tam mělo být místo $control->control->value jen $control->value.

Dále pokud v pravidlu máš statickou metodu array('Users_Users','userExists'), musí být i userExists statická a nemůže používat $this. Nebo jako callback definovat nestatickou metodu array($nejakyObj,'userExists')

Zdravím, řeším něco podobnýho jako uživatel Bohouš a mám s tím pár problémů..

  1. Když použiju v addRule array jakožto první parametr, vyskočí mi Notice o tom že používám místo stringu array .. jde to nějak vyřešit? Používám nejnovější revizi..
  2. Jde u toho příkladu co posílal Bohouš nějak vyřešit to používání this kromě použití callbacku array($nejakyObj, 'userExists') ? Konkrétně mě zlobí proměnná $this, zkoušel jsem jí nahradit parent:: ale stejně potom aplikace křičí že je někde na řádku 278 v DibiTable.php použitá proměnná $this a aplikaci to zastaví..

btw. v DibiTable.php je to tenhle řádek: return $this->complete($this->connection->query(

Díky za rady..

Editoval Ola (29. 10. 2008 17:18)