Vícenásobné použití formuláře: dědičnost nebo továrna?
- David Grudl
- Nette Core | 8218
Pokud potřebujeme v programu opakovaně vytvořit & nakonfigurovat určitý objekt, použijeme k tomu účelu továrničku (factory). To je asi zřejmé.
Továrničku obvykle implementujeme jako samostatnou třídu. Implementovat ji tak, že vytvoříme potomka vytvářené třídy a do něj ji přidáme, by bylo porušením Single Responsibility Principe. Samozřejmě z praktických důvodů lze jakékoliv pravidlo porušit, ale pokud praktické důvody nejsou, pišme čistější kód.
A pokud tedy potřebujeme na webu vytvořit opakovaně stejný formulář, je správnější si na to vytvořit továrničku, nikoliv (zne|vy)užívat konstruktor.
Místo řešení uvedeném na https://pla.nette.org/…ho-formulare nebo https://forum.nette.org/…a-onvalidate#… bych doporučoval spíš následující:
class CreateOrEditUserFormFactory
{
private $db;
public function __construct(Nette\Database\Connection $db)
{
$this->db = $db;
}
public function createForm($userId)
{
$form = new Form;
...
$form->addHidden('userId', $userId);
$form->addSubmit('send', 'Update Account');
$form->addSubmit('sendandview', 'Update Account and continue editing')
->onClick[] = $this->process;
return $form;
}
public function process($button)
{
...
}
}
A poté si do presenteru předáme službu CreateOrEditUserFormFactory…
services:
CreateOrEditUserFormFactory: CreateOrEditUserFormFactory
…čímž se vyhneme používání contextu:
class MyPresenter extends Nette\Application\UI\Presenter
{
private $formFactory;
public function __construct(CreateOrEditUserFormFactory $formFactory)
{
$this->formFactory = $formFactory;
}
protected function createComponentCreateOrEditUserForm()
{
return $this->formFactory->createForm($this->user->id);
}
}
- Filip Procházka
- Moderator | 4668
Tak udělej stable tag, kde se to bude dát použít a já to začnu radit lidem ;)
- David Grudl
- Nette Core | 8218
Podstatný je rozdíl mezi
CreateOrEditUserForm extends Form
versus
CreateOrEditUserFormFactory
.
Pokud jde o předání služby do presenteru, ve 2.0.4 se použije místo
__construct
metoda setContext
(případe
v konstruktoru předá Nette\DI\Container), což je detail.
- Filip Procházka
- Moderator | 4668
setContext()
je nepoužitelný
Co se týče továrniček. Přístup který ukazuješ je super, ale moc nekamarádí s DIC factories. Pokud tedy chceme jít touhle cestou, bude potřeba se jich zbavit. Ostatně, už jsem vzdal snahy o jejich použitelnost.
Editoval HosipLan (22. 8. 2012 18:03)
- David Grudl
- Nette Core | 8218
Proč by byl nepoužitelný? Je IMHO lépe použitelný než __construct, protože v případě chyby kvůli dědičnosti vyhodí strict error. A s DIC factories se to nijak netluče.
- Filip Procházka
- Moderator | 4668
No netluče. Ale na co máme DIC factories, když jsou „nepoužitelné“?
- Elijen
- Člen | 171
22 napsal(a):
- + 1 @Hosiplan zrovna dneska jsme narazili při debatě na to, jak předat továrnu někam dál v rámci DIC? Respektive jak se vyhnout přístupu k továrně volaním
$this->context
, tedy jak ji injectnout dopresenteru
?
No přece úplně stejně jako jakoukoliv jinou závislost.
@HosipLan: Proč by DIC factories měli být nepoužitelné? A není to OT? :)
- thunderbuff
- Člen | 164
22 napsal(a):
@Elijen: Můžeš mi ukázat, jak se v presenteru dostaneš k factory nadefinované v neonu, aniž by ses dotknul $this->context?
neřeší se to zde?
https://phpfashion.com/…-presenterum
- pawouk
- Člen | 172
50 formulářů v presenteru je dost málo pravděpodobné že budeš mít :-) Ale pokud jich opravdu budeš mít 50 a chceš dělat věci v souladu s DI tak ano. Mě teda osobně strašně vadí že v configu nejde udělat callback/anonymni funkce. Tak jsem si to dodělal a používám to nějak takto:
Config:
services:
userAccountFormFactory:
callback:
new: MyApp\UserAccountFormFactory
To vytvoří callback a ten mohu předávat smostatně bez contextu. Už jsem na to narážel asi před půl rokem a odpověď davida byla že to bude v příštím vydání, škoda že to tam stále není. Nebo se pletu?
Davidovo řešení se mi teda vůbec nelíbí, podle mě zbytečné psaní kod, zbytečná třída…
- mildabre
- Člen | 62
David Grudl napsal(a):
Podstatný je rozdíl mezi
CreateOrEditUserForm extends Form
versusCreateOrEditUserFormFactory
.Pokud jde o předání služby do presenteru, ve 2.0.4 se použije místo
__construct
metodasetContext
(případe v konstruktoru předá Nette\DI\Container), což je detail.
Díky Davide za tenhle článeček. První formulář jako komponentu jsem vytvořil takto:
<?php
class emloyeeForm extends \Nette\Application\UI\Form
{
public function __construct() {
parent::__construct();
$this->addText('jmeno', 'Jméno:');
$this->addText('bydliste', 'Bydliště:');
$this->addSubmit('send', 'Odeslat');
$this->onSuccess[] = $this->sendData;
return $this;
}
}
?>
Přitom je jasné, že třída emloyeeForm je stejná jako její předek, takže to dědění je zde „zneužité“ pro jiné účely. Když k tomu člověk přijde poprvé, tak ho asi vždy napadne dědění, factory třída je takový ten Nette way, člověk si na ty Nette postupy musí zvyknout.
- doublemcz
- Člen | 15
Vytvářím formuláře přesným postupem nahoře. Oddědim od Form, vytvářit přes factory nadefinovaného v neonu.
Každopádně středně velká aplikace bude mít desítky formulářů (zatím mají všechny stejné nastavení) a neon roste a roste. Nejde vytvořit nějakou jednu factory classu, která by to řešila a já bych v presenteru pouze určil jméno?
factories:
userDetailForm:
class: Frontend\Forms\UserDetailForm
setup:
- setTranslator()
- setDatabase(@database)
- setLocalization(@localization)
orderDetailForm:
class: Frontend\Forms\OrderDetailForm
setup:
- setTranslator()
- setDatabase(@database)
- setLocalization(@localization)
.....
Takhle tam bude za chvíli stovky řádků jenom na vytváření formulářů :-)
Editoval doublemcz (16. 7. 2013 9:34)
- pseudonym
- Člen | 57
David Grudl napsal(a):
Pokud potřebujeme v programu opakovaně vytvořit & nakonfigurovat určitý objekt, použijeme k tomu účelu továrničku (factory). To je asi zřejmé.
Továrničku obvykle implementujeme jako samostatnou třídu. Implementovat ji tak, že vytvoříme potomka vytvářené třídy a do něj ji přidáme, by bylo porušením Single Responsibility Principe. Samozřejmě z praktických důvodů lze jakékoliv pravidlo porušit, ale pokud praktické důvody nejsou, pišme čistější kód.
A pokud tedy potřebujeme na webu vytvořit opakovaně stejný formulář, je správnější si na to vytvořit továrničku, nikoliv (zne|vy)užívat konstruktor.
Místo řešení uvedeném na https://pla.nette.org/…ho-formulare nebo https://forum.nette.org/…a-onvalidate#… bych doporučoval spíš následující:
class CreateOrEditUserFormFactory extends Nette\Object { private $db; public function __construct(Nette\Database\Connection $db) { $this->db = $db; } public function createForm($userId) { $form = new Form; ... $form->addHidden('userId', $userId); $form->addSubmit('send', 'Update Account'); $form->addSubmit('sendandview', 'Update Account and continue editing') ->onClick[] = $this->process; return $form; } public function process($button) { ... } }
A poté si do presenteru předáme službu CreateOrEditUserFormFactory…
services: CreateOrEditUserFormFactory: CreateOrEditUserFormFactory
…čímž se vyhneme používání contextu:
class MyPresenter extends Nette\Application\UI\Presenter { private $formFactory; // pro Nette 2.0.4 použijeme místo __construct metodu setContext public function __construct(CreateOrEditUserFormFactory $formFactory) { $this->formFactory = $formFactory; } protected function createComponentCreateOrEditUserForm() { return $this->formFactory->createForm($this->user->id); } }
Vyzera to presne ako to co potrebujem, len by ma zaujimalo, ako v metode
process()
zavolam redirect()
? Musim si do tejto
tovarnicky nejako cez konstruktor poslat Presenter? Alebo sa to da aj
nejako inak?
- xificurk
- Člen | 121
pseudonym napsal(a):
Vyzera to presne ako to co potrebujem, len by ma zaujimalo, ako v metode
process()
zavolamredirect()
? Musim si do tejto tovarnicky nejako cez konstruktor poslat Presenter? Alebo sa to da aj nejako inak?
$form->getPresenter()
, druhou možností je při vytváření
formuláře v createComponentCreateOrEditUserForm
na něj navěsit
další callback na onSuccess (nebo co potřebuješ).
- snake.aas
- Člen | 25
xificurk napsal(a):
pseudonym napsal(a):
Vyzera to presne ako to co potrebujem, len by ma zaujimalo, ako v metode
process()
zavolamredirect()
? Musim si do tejto tovarnicky nejako cez konstruktor poslat Presenter? Alebo sa to da aj nejako inak?
$form->getPresenter()
, druhou možností je při vytváření formuláře vcreateComponentCreateOrEditUserForm
na něj navěsit další callback na onSuccess (nebo co potřebuješ).
Pokud ale máš více callbacků, ta pozor na přesměrování při chybě.
Pokud se v prvním callbacku zneplatní formulář, druhý onSuccess se stejně
provede (a tedy přesměruje)
Jak jsem psal tady