Best practice továren na formuláře

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

Zdravím,

snažím se najít nejvhodnější a zároveň nejrychlejší způsob psaní továren na formuláře.

Podle best-practice je vhodné buďto vracet Form metodou create(), což mi přijde vhodné, protože mohu zároveň v této továrně provádět operace, které mají proběhnout po odeslání formuláře, ale jak v tomhle případě notifikovat uživatele o nějaké chybě, která může nastat ? Když odchytím vyjímku, jak ji předat do presenteru ??? je sice fajn, že si mohu i na presenteru udělat krátký callback v onSuccess[] například s redirectem, ale to je přece blbost, protože když bude nějaká chyba uvnitř onSuccess[] v továrně tak poté proběhne redirect. Přijde mi to jako špatný způsob, ne jako best-practice z hlediska použitelnosti. Prozatím to řeším tím, že metodě create() předám předka, tudíž třeba presenter, ale tím si ubírám na znovupoužitelnosti třeba v komponentách, ano dá se to vychytat, ale myslím si že v tom pak může vzniknout chybovost, která se bude těžko dohledávat.

Druhý způsob je samozřejmě komponenta, ale to už mi zase přijde jako moc psaní. Jak tedy vyřešit efektivně a prakticky první způsob ?

Díky

F.Vesely
Člen | 367
+
+6
-

Podle me by mela tovarna pouze vytvorit formular a ne provadet operace po jeho odeslani. Co kdyz budu chtit ten formular pak pouzit nekde jinde a udelat jinou operaci po jeho odeslani?

kleinpetr
Člen | 480
+
0
-

Ano, to je sice pravda, ale zase na druhou stranu, když se bude řešit onSuccess() v presenteru (tak to řeším do teď), tak při více formulářích a náročnějším kódu je presenter zaprasený, a pokud si k tomu vytvořím nějaký Helper, tak dejme tomu, ale ten bude přejímat nějakou proměnnou $values, u které není zcela jasné na první pohled co obsahuje, apod.

Proto si myslím, že z hlediska návrhu je lepší provádět operaci v továrně, jako je tomu právě u best-practice. Například signForm, mylím si, že jeho úkol je jasný, ale nastává problém např. flashMessage při chybných údajích. Proto jsem chtěl podotknout, že best-practice asi úplně nebude best-practice a chtěl jsem zjistit, jak ideálně to řeší jiní.

Díky

Eda
Backer | 220
+
+1
-

Obecně by podle mého měla být s formulářem svázaná jen jeho „core činnost“. Tzn. update uživatele, vytvoření objednávky atd. Věci okolo typu přesměrování na jiné místo atd. by se měly spíš řešit zvenku přidáním do onSuccess callbacků. Ať už přímo do formu, pokud máš továrnu na ten, nebo do proměnné onSuccess vytvořené (a napojené) v komponentě, která obaluje form.

jiri.pudil
Nette Blogger | 1027
+
+2
-

Asi vycházíš z tohoto, že?

Když odchytím vyjímku, jak ji předat do presenteru ??? je sice fajn, že si mohu i na presenteru udělat krátký callback v onSuccess[] například s redirectem, ale to je přece blbost, protože když bude nějaká chyba uvnitř onSuccess[] v továrně tak poté proběhne redirect.

Když bude chyba v onSuccess[] v továrně, přidáš ji do formuláře pomocí addError() a ten další success handler z presenteru už se nezavolá.

ZahorskyJan
Člen | 55
+
+3
-

@kleinpetr on to naznačil @jiri.pudil , že když se ve zpracování vyhodí nějaká výjimka, tak se do formuláře přidá addError (pro obecné zobrazení chyby) a nebo ke konkrétnímu prvku. Hodně věcí se dá vyřešit už ve vlastním onValidate[] (třeba jestli přihlášený uživatel vůbec existuje, záleží na řešeném problému).

U sebe to ale často řeším tak, že formulář mám v komponentě (potokem UI\Control) a ten nese celou logiku kolem formuláře a má svoje eventy pro volání z presenteru. Obecně si nemyslím, že formulář je jenom přepravka na definic políček které se mohou vyplnit, ale že k tomu patří i to, co ten formulář má dělat a je to samostatná jednotka.

Potom to může vypadat nějak takhle + k tomu udělat interface pro továrnu. Pro mě výhody:

  • můžu formulář vykreslovat ručně nebo automaticky a stačí to změnit na jednom místě
  • když budu moc chtít, můžu používat různé šablony formuláře jenom tím, že budu parametrizovat komponentu
  • použiju to kde chci a kolikrát chci
  • presenter se jednoduše postará jenom o to, co dělat v případě že je success nebo error (pokud do toho přidám ajax, tak ten presenter nemusí dělat vlastně nic, protože v komponentě jde zavolat $this->redirect('this'); a vrátí se tam kde byl)
  • presenter jenom vytváří komponentu přes továrnu a zpracuje event

Nevýhody: může to být víc psaní a tam kde potřebuji jenom hloupý formulář a vím (nikdy nevím), že to nikde jinde opravdu nebude).

<?php

class SomethingSmart extends UI\Control
{

	/**
	 * Nejaka zavislost co potrebuju pro fungovani komponenty
	 * @var \App\ProjectModule\Facades\Settings
	 */
	protected $settings;

	/**
	 * Error event
	 * @var array
	 */
	public $onSuccess;

	/**
	 * Error event
	 * @var array
	 */
	public $onError;

	function __construct(\App\ProjectModule\Facades\Settings $settings)
	{
		$this->settings = $settings;
	}

	public function render() {
		// nactu si jestli neco potrebuju
		$this->template->something = $this->settings->load('something');
		$this->template->setFile( __DIR__ . '/Controls/SomethingSmart.latte');
		$this->template->render();
	}

	public function createComponentMyForm()
	{
		$form = new Form;
		$form->addText('name', 'Jmeno');
		$form->onSuccess[] = $this->processForm;
		return $form;
	}

	public function processForm(Form $form, ArrayHash $values)
	{
		// zpracuji formular

		if ( true )
		{
			$this->flashMessage('povedlo se, zobrazim v sablone komponenty');
			// volam event co si chytim v presenteru
			$this->onSuccess($this);
		}
		else
		{
			$this->flashMessage('nepovedlo se, zobrazim v sablone komponenty');
			// volam event co si chytim v presenteru
			$this->onError($this);
		}
	}
}
?>
kleinpetr
Člen | 480
+
0
-

@ZahorskyJan Přesně o tomhle mluvím, díky za pochopení, zkrátka je mi jasné, že mohu formuláři přidat addError() ale je to takové nic moc.. Vím, že komponenta to celé řeší, ale šlo mi o to obejít to nějakou kratší cestou než si dělat interface na továrnu, atd.. Ikdyž je pravda, že onError() by se dalo použít :)

Díky za připomínky.

Editoval kleinpetr (3. 11. 2016 16:02)

ZahorskyJan
Člen | 55
+
+1
-

@kleinpetr na to „kratší cestou“ si chci napsat generátor, který mi do projektu vygeneruje soubory: základní kostru komponenty, interface, šablonu komponenty, zaregistruje interface do správného config.neon (mám jich víc rozdělených podle modulů) a vypíše mi, jaký kód vložit do presenteru. Pak je to otázka chvilky :-)

kleinpetr
Člen | 480
+
0
-

@jiri.pudil Nemáš úplně pravdu, onSuccess() v presenteru se zavolá tak i tak.

Edit: moje chyba, zkoušel jsem jiný form. Takže díky, prozatím bude onError() stačit :)

Editoval kleinpetr (3. 11. 2016 16:11)

CZechBoY
Člen | 3608
+
0
-

Vsak tovarna pres interface je automaticky generovana, sablona nemusi byt – staci $this[‚form‘]->render() v render metode.
Je to cely otazka max minuty coz neni moc a furt se to da automatizovat.

Šaman
Člen | 2625
+
+1
-

Tak jsem sepsal svoje řešení na Gist. Dřív jsem taky používal komponentu, ale to se mi neosvědčilo – občas potřebuji formulář z presenteru ještě mírně upravit (v ukázce třeba přidávám nové tlačítko) a hrabat se dovnitř komponenty není hezké. Takhle prostě dostanu připravený formulář, kterému defaultně musím jen nastavit flashmessage a redirect, ostatní už umí sám (do toho, jak uloží data většinou presenteru nic není, osobně chápu formulář spíše jako třídu modelu). Pokud by to bylo potřeba, tak není problém zvenku přepsat celou obsluhu onSuccess.

Výhody:

  • presenter pracuje opravdu s třídou formuláře, nikoliv s něčím, co ho obaluje
  • formulář si validaci a ukládání řeší sám
  • presenter, nebo komponenta si dořeší jen hlášky a redirecty
  • renderer se nastavuje na jednom místě (v tomto případě ho stačí jen zaregistrovat v kontejneru), ale lze dodatečně přepsat

Nevýhoda:

  • oproti notoricky známé obsluze Nette formuláře zavádím trojici událostí beforeSave, save a afterSave (rozdíly proti tomu, co každý zná, považuji za nevýhodu)

Jediný problém, který leží na pomezí mezi modelovou a presenční vrstvou je vytváření odkazů v error message. Například pokud Foo musí být unikátní, tak během validace mohu chtít vyhodit hlášku „Tento záznam již existuje, viz: <odkaz na Foo:detail, $id>“. To lze řešit pomocí LinkGeneratoru, jen holt formulář musí znát strukturu aplikace a vědět, že má odkázat na Foo:detail. Kromě tohoto případu jsem nenarazil na nic, co by mi bránilo považovat továrnu na formulář za třídu modelové vrstvy (a taky ji mám uloženou přímo vedle entity a repozitáře – změní-li se entita, budu nejspíš taky měnit formulář, zatímco presenter to většinou nezajímá).


Dodatek: Pokud nebude požadavek na to, aby mohl presenter případně udělat něco ještě před zpracovánim dat, pak FormFactory není nutná (kromě nastavení rendereru hlavně definuje callbacky při zpracování a jejich pořadí). V továrně konkrétního formuláře mohu nastavit rovnou $form->onSuccess[] = [$this, 'save']; a následně v presenteru v továrničce přidat ještě druhou obsluhu $form->onSuccess[] = function($form, $values){…};. Pořadí se provede od formuláře, přes případné obalující komponenty, až k presenteru.
Zápis $form->onSuccess[] nepřepíše obsluhu, jen přidá další.

Editoval Šaman (4. 11. 2016 7:08)