Formuláře a DI Factories – best practice
- Jiří Nápravník
- Člen | 710
Jaká je best practice pro tvorbu formulářů? Používám variantu, co doporučuje David , nicméně objevil jsem DI factories a je to ideální pro komponenty. Formuláře jsou taky komponenty, ale dá se to tam nějak rozumně napasovat? Přijde mi blbost dělat si DI továrnu na továrnu a dělat továrnu přímo na formulář, dědí UI/Form taky není ideální…
- enumag
- Člen | 2118
Filip to už sepsal, jenom to nemohu najít nikde na nette.org. https://github.com/…ponenty.texy
- Jiří Nápravník
- Člen | 710
Díky za odkaz. Nicméně můžete mi upřesnit, který pohled je „lepší“, sám Filip tam píš, každý se hodí na jiný případ, tak kdybyste mohli nahodit, který je lepší, na který případ. Byl bych vděčný.
Nicméně zkusil jsem druhu možnost a nějak ji nemohu rozchodit, mám to takhle:
class PageEditFormFactory extends Control
{
private $pageFacade;
public function __construct(PageFacade $facade)
{
parent::__construct();
$this->pageFacade = $facade;
}
/**
* @return BaseForm - tohle je potomek UI/Form
*/
protected function createComponentForm()
{
$form = new BaseForm();
...
return $form;
}
}
interface IPageEditFormFactory
{
/**
* @return \PageModule\Form\PageEditFormFactory
*/
function create();
}
class PagePresenter extends AdminPresenter
{
/**
*
* @var PageModule\Form\IPageEditFormFactory
* @inject
*/
public $pageEditFormFactory;
public function createComponentPageEditForm()
{
$form = $this->pageEditFormFactory->create();
$presenter = $this;
$form->onSuccess[] = function() use ($presenter) {
$presenter->redirect('default');
};
return $form;
}
v latte pak volám
{control pageEditForm}
a dostanu:
Cannot read an undeclared property
PageModule\Form\PageEditFormFactory::$onSuccess.
když si dumpnu v presenteru tu proměnnou form, tak je to objekt: PageModule\Form\PageEditFormFactory , ale tady by to správně měl být už ten Form ne? A když udělám tedy
$form = $this->pageEditFormFactory->create()['form'];
tak při dumpu téhle proměnné už sice mám ten správný form, ale pro změnu skončím na exceptione: Component with name ‚pageEditForm‘ does not exist.
- David Matějka
- Moderator | 6445
prvni vec, nepojmenovavej to PageEditFormFactory
je to matouci,
nejedna se o tovarnu, ale o komponentu – lepsi je treba jen
PageEditForm
ted k veci, v te verzi, co posilal enumag je chyba, v tom, co jsem posilal
ja, je to uz opraveno, musis tam mit
$control['form']->onSuccess[] ..
ja osobne mam radsi vytvorit si vlastni onSuccess event v tom controlu, pak
par veci v te ukazce od hosiplana chybi (ta komponenta potrebuje sablonu, resp
render() metodu apod.)
takze:
class CategoryForm extends UI\Control
{
private $database;
public $onSuccess = array();
public function __construct(Nette\Database\Connection $database)
{
parent::__construct();
$this->database = $database;
}
protected function createComponentForm()
{
$form = new UI\Form;
// mohu použít $this->database
$form->addSubmit('send', 'Odeslat');
$form->onSuccess[] = $this->processForm;
return $form;
}
public function processForm($form)
{
// mohu použít $this->database
// zpracovani formulare
$this->onSuccess($form);
}
public function render()
{
$this->template->setFile(__DIR__ . '/categoryForm.latte');
$this->template->render();
}
}
v categoryForm.latte staci jen
{control form}
nebo ho muzes vykreslit manualne
s timhle zpusobem ti bude v presenteru fungovat to ->onSuccess jak jsi psal…
- Jiří Nápravník
- Člen | 710
prvni vec, nepojmenovavej to
PageEditFormFactory
je to matouci, nejedna se o tovarnu, ale o komponentu – lepsi je treba jenPageEditForm
jj, to určitě přejmenuji, to factory tam je ještě jako důsledek nedokončeného refaktoringu metody 1, co jsem používal.
ja osobne mam radsi vytvorit si vlastni onSuccess event v tom controlu, pak par veci v te ukazce od hosiplana chybi (ta komponenta potrebuje sablonu, resp render() metodu apod.)
- ja mam taky přímo u toho contrulu onSuccess, kde pak volá přímo model apod, ale potřebuji ještě po zpracování přesměrovat, což mi přijde nejlepší v presenteru. nebo je možná jiná varianta?
jinak díky za doplnění, už to funguje. Můžeš ještě poradit, kdy je lepší použít variantu 1 a variantu 2? Mě osobně se více líbí ta druhá varianta, ale nevím ani proč:-)
- David Matějka
- Moderator | 6445
ja mam taky přímo u toho contrulu onSuccess, kde pak volá přímo model apod, ale potřebuji ještě po zpracování přesměrovat, což mi přijde nejlepší v presenteru. nebo je možná jiná varianta?
nevim, jestli jsme se uplne pochopili – ten onSuccess, o kterym mluvim,
je vlastni event vytvoreny pres public $onSuccess = array();
a
zavolany pres $this->onSuccess($form);
– proste pri
zpracovani formulare (pres onSuccess event formulare) zavolam vlastni vytvoreny
onSuccess event komponenty (ktery uz nesouvisi s tim eventem formulare) :))
(snad je to srozumitelny)
presmerovani muzes provest i v komponente – a to bud v pres
$this->presenter->redirect()
, coz ale moc nedoporucuju, nebo
pouze pres $this->redirect()
(pouze presmerovani v ramci
komponenty) – to je vhodne v pripade, ze komponenta je vice
„samostatna“
pokud ta komponenta jen obaluje formular, je podle me lepsi flash message a
redirect resit v onSuccess v presenteru
variantou 1 myslis jen tovarnu na formular? to jsem pouzival, neni to spatny reseni, ale ted uz na vse pouzivam vlastni komponentu – je to mnohem flexibilnejsi – muzes primo tam upravit vzhled formulare, vyresit snadnejc zpracovani a pracovat s ajaxem
EDIT: jeste k tomu zpracovani formulare, respektive co se mi nelibi na tom zpusobu, co ukazuje David s tou tovarnickou
- kdo by cekal, ze „Factory“ se bude starat o zpracovani formulare? to by se melo postarat pouze o jeho vytvoreni
- kdyz teda vezmu, ze factory ten formular muze zpracovavat, tak je celkem problem s prenosem nejakych stavovych promennych – v ukazce je $userId, coz se necha jeste celkem snadno prenest pres hidden pole (i kdyz se mi tenhle zpusob taky nelibi), ale je v podstate neresitelne, kdybys chtel predat primo entitu (respektive objekt) $user. v pripade formulare jako komponenty + tovarna predas $user to create() metody tovarny, ktera ho preda konstruktoru komponenty a ta si ho ulozi jako clenskou promennou
Editoval matej21 (11. 10. 2013 17:02)
- Jiří Nápravník
- Člen | 710
To s tím onSuccess nějak nechápu, ale to nevadí:)
Ad přesměrování, pro mě je lepší tohle opravdu v presenteru, protože ta komponenta obaluje v podstatě jen formulář a pokud bych použil v jiném preseneru musel bych tam if-ovat, kvůli redirectu.
Ano variantou 1 jsem myslel factory na formulář, co doporučoval David. Ok, ka jsme rád, že jsme se shodli, že je lepší komponenta, díky za důvody!
- David Matějka
- Moderator | 6445
EDIT: zmenil jsem nazev vlastni udalosti z onSuccess na onFormProcessed, aby bylo jasno, ktera je ktera
ok, tak jeste k tomu onSuccess :)
jedna se o vlastni udalost
formular ma v nette udalost onSuccess, na kterou muzu navazat ruzny
callbacky.
v komponente si vytvorim vlastni udalost onFormProcessed, to udelam takto:
class PageEditForm extends Control
{
public $onFormProcessed = array();
...
}
ted budu moci pridavat na tuto udalost callbacky treba z presenteru:
//presenter
public function createComponentPageEditForm()
{
$control = $this->pageEditFormFactory->create();
$presenter = $this;
$control->onFormProcessed[] = function() use ($presenter) {
$presenter->redirect('default');
};
return $control;
}
(ta anonymni fce neni nyni navazana na onSuccess u formu, ale na moji vlastni udalost onFormProcessed u me vlastni komponenty, kterou budu muset spustit vlastnimi silami)
a posledni vec: mam vytvorenou udalost, mam na ni navazanej callback, ted uz jen staci tu udalost vyvolat, to bude nejlepsi az po zpracovani formulare (ulozeni do db atd). takze nyni na formular, ktery vytvarim v komponente navazu klasickou onSuccess udalost (ted je vazana na formular):
//komponenta
protected function createComponentForm()
{
$form = new UI\Form;
// mohu použít $this->database
$form->addSubmit('send', 'Odeslat');
$form->onSuccess[] = $this->processForm;
return $form;
}
nyni ve chvili, kdy Form spusti udalost onSuccess, tak se zavola callback $this->processForm, ten muze vypadat takto:
//komponenta
public function processForm($form)
{
// mohu použít $this->database
// zpracovani formulare
$this->onFormProcessed($form);
}
tam ulozim do databaze nebo cokoliv a pak – konecne onen moment :) – spustim moji definovanou udalost onFormProcessed a tim zavolam callback, ktery jsem navazal v presenteru
je to o par radku kodu vic (nechalo by se asi udelat BaseFormControl, ktery by resil ty udalosti apod a od kteryho bys dedil), ale oproti tomu, co pise hosiplan nemusim mit takovou znalost one komponenty, tedy nemusim vedet, ze obsahuje subkomponentu „form“ na kterou je potreba callback navazat – moji vlastni udalost onFormProcessed vidim na prvni pohled a je jasna. navic je kompatibilni s Kdyby\Events
Editoval matej21 (11. 10. 2013 18:03)
- Jiří Nápravník
- Člen | 710
Díky moc za upřesnění, to se mi hodně líbí a je to tak opravdu lepší. Budu rád používat..
- Jiří Nápravník
- Člen | 710
Tak měl bych ještě jeden dotaz, když chci nastavit defaultní hodnoty, případně nějak jinak manipulovat s formulářem. Máš nějaké lepší řešení, než si vytahovat přímo tu kompononentu v komponentně (tedy $this->getComponent(‚pageEditForm‘)->getComponent(‚form‘)) nebo si udělat nějakého společného předka pro control, od kterého pak budu dědit a tam ty metody budou?
kdyz teda vezmu, ze factory ten formular muze zpracovavat, tak je celkem problem s prenosem nejakych stavovych promennych – v ukazce je $userId, coz se necha jeste celkem snadno prenest pres hidden pole (i kdyz se mi tenhle zpusob taky nelibi), ale je v podstate neresitelne, kdybys chtel predat primo entitu (respektive objekt) $user. v pripade formulare jako komponenty + tovarna predas $user to create() metody tovarny, ktera ho preda konstruktoru komponenty a ta si ho ulozi jako clenskou promennou
a ještě kdybys mohl nějak upřesnit tohle, nějak se mi tam nedaří propašovat cokoli přes tu create() metodu…
Editoval Jiří Nápravník (12. 10. 2013 0:32)
- David Matějka
- Moderator | 6445
nejdriv k druhe otazce:
1. je treba do konstruktoru komponenty pridat jako parametr tu promennou, treba $page
class PageEditForm extends Control
{
...
protected $page;
public function __construct($page)
{
$this->page = $page;
}
...
2. je potreba upravit to rozhrani pro tovarnicku
interface IPageEditFormFactory
{
/**
* @return \PageModule\Form\PageEditFormFactory
*/
function create($page);
}
3. je potreba upravit config
services:
pageEditFormFactory:
implement: IPageEditFormFactory
class: PageEditForm
parameters: [page]
arguments: [%page%]
(parameters jsou parametry ty tovarny (rozhrani) a arguments jsou do ty komponenty)
a kdyz si ted pri vytvareni ty komponenty v presenteru predas objekt s page
public function createComponentPageEditForm()
{
$control = $this->pageEditFormFactory->create($this->page);
....
return $control;
}
($this->page naplnis nekde v action* metode dle parametru presenteru)
tak budes moci s tim objektem pracovat v komponente a pouzit ho treba prave na
naplneni defaultnich hodnot formulare
takze tim je vyreseno nastavovani defaultnich hodnot :)
kdyz bys potreboval jeste nejak manipulovat s formularem, tak ti asi nezbyde
nez si ho vytahnou jak pises, respektive muzes pouzit zkratky:
$this['pageEditForm']['form'];
//nebo rovnou:
$this['pageEditForm-form'];
nebo lepe – napsat si do ty komponenty metody, ktery budou s tim formem manipulovat a ty je jen budes volat z presenteru. ale myslim, ze vetsinou vubec neni treba s formularem manipulovat z presenteru
- Jiří Nápravník
- Člen | 710
Díky za upřesnění, to vypadá pěkně. Když budu mít defaultní hodnoty ve formulářové komponentě tak v podstatě manipulovat v presenteru nebude většinou třeba, či-li ot řeší ten problém.
Jenom se mi příliš nelíbí jedna věc a to, že budu muset zavádět instanční proměnnou u presenteru, kterou budu plnit ty defaultní data, co pak půjdou do komponenty. Přeci jen ten atribut budu používat jen v jedné metodě kvůli přenesení dat, či-li v tom presenteru je taková trochu navíc a nevím jestli dostatečně čistý. Ale ono asi nic jiného tam vymyslet nejde co? Snad jen ještě setter komponenty, který bych volal v actionEdit()
A ještě potom, když mám ted v konstruktoru té komponenty:
public function __construct(PageFacade $pageFacade)
a chi aby mi to načítalo z DIC automaticky, jako doposud a zároveň tam mít nějak tu možnost posunout tam třeba ty defaultní hodnoty. to je nějak rozumně řešitelné?
Editoval Jiří Nápravník (12. 10. 2013 16:20)