Validace v modelu – dualni validace

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

Ahoj,

všiml jsem si, že se to tady už párkrát řešilo, ale nenašel jsem žádné finální řešení, proto se zeptám rovnou zde.

Na fóru jsem se dočetl, že ve verzi 0.9 bude vyřešena duální validace v presenteru a modelu, tak aby byl zachován princip DRY, tzn. validační pravidla budou pouze jedny. Bylo toto již nějak oficiálně vyřešeno nebo řešíte to někdo nějak alespoň neoficiálně?

--

Protože může k modelu přistupovat několik presenterů, rád bych do něj umístil validaci hodnot, aby se mi do db nemohlo dostat něco co nechci. Model by při špatné hodnotě vyhodil výjimku, což samozřejmě vylučuje tu pěknou validaci formuláří typu Form.

Nechce (nepřipadá mí to moc čisté) se mi ale moc psát dvakrát stejné regulární výrazy, dvakrát stejné rozsahu hodnot a pod. jednou v presenteru a podruhé v modelu. Předpokládám, že by ideálně měl model poskytovat nějakou sadu validačních pravidel, která bych jednoduše aplikoval na presenter/form.

Dík za info.

bazo
Člen | 620
+
0
-

ako validacne pravidlo mozes pouzit lubovolny callback, napr

<?php
$form->addText(...)->addRule(Model::validate,'');
?>
vkuzel
Člen | 15
+
0
-

bazo napsal(a):

ako validacne pravidlo mozes pouzit lubovolny callback, napr

<?php
$form->addText(...)->addRule(Model::validate,'');
?>

Rozumím tomu, že mohu volat svou validační funkci, obávám se, že tím ale přijdu o validaci formuláře pomocí javascriptu nebo mi něco uniká?

Rád bych využil všech výhod, které validace ormuláře nabízí, tzn. javascript, validace po postu dat, ale rád bych tuto validaci „nějak“ tahal z modelu. Případně máte někdo nějaký jiný způsob řešení?

Ondřej Mirtes
Člen | 1536
+
0
-

Ad javascript – ano, obáváš se správně.

vkuzel
Člen | 15
+
0
-

Ondřej Mirtes napsal(a):

Ad javascript – ano, obáváš se správně.

A řešíte to tedy nějak?

Honza Kuchař
Člen | 1662
+
0
-

Teď mě napadlo co zkusit ten model validovat pomocí https://api.nette.org/…s/Rules.html a to by se potom určitě dalo nějak předat i formuláři.

Ondřej Mirtes
Člen | 1536
+
0
-

Ty samotné validační metody jsou myslím v TextBase, aktuálně je to prostě příliš svázané s formuláři.

Neřeším to nijak, v modelu nevaliduji.

vkuzel
Člen | 15
+
0
-

honzakuchar napsal(a):

Teď mě napadlo co zkusit ten model validovat pomocí https://api.nette.org/…s/Rules.html a to by se potom určitě dalo nějak předat i formuláři.

Jo, tak nějak to asi dopadne, jenom mě v tomhle případě trochu mrzí, že validační pravidlo Form::EMAIL volá metody objektu InstantClientScript který je svázaný s javascriptem, který by neměl v modelu co dělat.

Ale nějak to tam asi budu muset naklonovat no.

vkuzel
Člen | 15
+
0
-

Protože bych rád měl nadefinovanou sadu validačních pravidel pro jeden model na jednom místě a tato pravidla bych potom rád používal v různých presenterech/formulářích, bez nutnosti je znovu definovat, tak nakonec to budu řešit asi takhle:

Protože se mi líbí stromová struktura pravidel v Nette s možností nastavit pravidlo jako podmínku, vyjdu z ní. Abych mohl párovat pravidla s parametry v modelu a s parametry ve formulářích, potřebuji aby každá instance pravidla s sebou nesla název parametru, jehož hodnotu validuje. Zároveň potřebuji předem nadefinovat sadu pravidel, kterou mohu rozdistribuovat po formulářích a pod., to znamená, že data do validátorů mohou být předána až v době validace. Také chci zachovat validaci ve formulářích pomocí javascriptu. A nakonec chci aby jednotlivá pravidla byla co nejjednodušší, tzn. předá se jim pouze název a hodnota, kterou mají validovat.

Vytvořil jsem si tedy stromovou strukturu pravidel. Jedno pravidlo s sebou nese pouze svůj název, který mi potom poslouží k párování s parametry, tzn.:

<?php
abstract class Rule
{
	private $name = null;

	final public function __construct($name)
	{
		$this->name = $name;
	}

	abstract public function validate($value);

	public function getName()
	{
		return $this->name;
	}
}

// podedim z predka a vytvorim pravidlo zjistujici, zda-li promenna obsahuje hodnotu xxx
class XxxRule extends Rule
{
	public function validate($value)
	{
		return ($value == 'xxx');
	}
}
?>

Protože pravidlo jako takové nenese informaci o tom, zda-li se jedná o validaci nebo podmínku, vytvořím si pro něj obálku. Obálka bude také zjišťovat aktuální hodnotu validované proměnné:

<?php
class RuleType
{
	const VALIDATOR = 1;
	const CONDITION = 2;

	private $rules = null;
	private $rule = null;
	private $type = null;

	// do obalky predam pravidlo a jeho typ
	public function __construct(Rule $rule, $type = self::VALIDATOR) {
		$this->rules = new Rules();
		$this->rule = $rule;
		$this->type = $type;
	}

	// validuji hodnotu parametru predaneho objektu
	public function validate($object) {
		// nazev validovaneho parametru ziskam z nazvu pravidla
		$name = $this->rule->getName();

		switch ($this->type) {
			case self::VALIDATOR:
				if ($this->rule->validate(isset($object->$name) ? $object->$name : null)) {
					return $this->rules->validate();
				}
				return false;
			break;
			case self::CONDITION:
				if ($this->rule->validate(isset($object->$name) ? $object->$name : null)) {
					return $this->rules->validate();
				}
				return true;
			break;
		}
	}

	// vrati vnorena pravidla pro pripad podminky
	public function getRules() {
		return $this->rules;
	}

	// vrati pravidlo, pro pripad ze chci vratit strukturu pravidel
	public function getRule() {
		return $this->rule;
	}
}
?>

Pravidla uložím do kolekce pravidel, abych z nich mohl vytvořit strukturu.

<?php
class Rules implements IteratorAggregate
{
	private $rules = array();

	public function __construct() {
	}

	// prida pravidlo do kolekce
	public function addRule(Rule $rule) {
		$this->rules[] = new RuleType($rule);
		return $this;
	}

	// prida podminku
	public function addCondition(Rule $rule) {
		$ruleType = new RuleType($rule, RuleType::CONDITION);
		$this->rules[] = $ruleType;

		return $ruleType->getRules();
	}

	// zavola pravidla v kolekci a preda jim objekt, jehoz parametry budou validovany
	public function validate($object) {
		foreach ($this->rules as $rule) {
			if (!$rule->validate($object)) {
				return false;
			}
		}

		return true;
	}

	public function getIterator() {
		return new ArrayIterator($this->rules);
	}
}
?>

A nakonec pravidla pouziju nekde v modelu:

<?php
class Model
{
	public $var = 'xxx';

	public function __construct() {
	}

	// vytvorim si strom pravidel
	private static function getRules() {
		$rules = new Rules();
		$rules->addRule(new XxxRule('var'))->addCondition(new XxxRule('var'))->addRule(new XxxRule('vare'));

		return $rules;
	}

	// tahle funkce bude treba neco ukladat do databaze
	public function save() {
		// pouziju vytvorena pravidla
		var_dump(self::getRules()->validate($this));
	}

	// vytisknu strom pravidel, toto vyuziju pri aplikaci pravidel na Form
	public function printRules(IteratorAggregate $rules = null) {
		if ($rules == null) {
			$rules = self::getRules();
		}

		foreach ($rules as $ruleType) {
			$rule = $ruleType->getRule();
			echo "<br />\n" . get_class($rule) . "::" . $rule->getName();
			$this->printRules($ruleType->getRules());
		}
	}

}

$model = new Model();
$model->save();
$model->printRules();
?>

Krásné by bylo, kdybych vytvořený strom mohl vzít a snadno ho použít i jinde mimo model, například ve formuláři, štěstí mi bohužel nepřeje, tak budu muset namapovat pravidla na pravidla formuláře. To udělám tak, že podědím třídu AppForm a přidám metodu addRules(Rules $rules) pomocí které naliju pravidla do formuláře.

To která pravidla mám ke kterým prvkům přiřadit zjistím tak, že porovnám název třídy, který určí název pravidla a name v instanci pravidla mi určí název parametru, tzn.:

<?php
	$rule = new EmailRule('login_name');
	get_class($rule) == 'EmailRule'; // vim, ze chci pridat Form::EMAIL
	$rule->getName() == 'login_name'; // vim, ze jej chci pridat parametru login_name
?>

Tuto operaci můžu provést až po vytvoření formuláře, tzn. přidání všech jeho polí (možná to půjde přidat do přetížené metody FormContainer::validate(), takže můžu přidat pravidla před přidáním prvků, to ale ještě budu muset promyslet).