Vícenásobné použití formuláře: dědičnost nebo továrna?

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
David Grudl
Nette Core | 7567
+
+1
-

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
+
0
-

Tak udělej stable tag, kde se to bude dát použít a já to začnu radit lidem ;)

David Grudl
Nette Core | 7567
+
0
-

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
+
0
-

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 | 7567
+
0
-

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
+
0
-

No netluče. Ale na co máme DIC factories, když jsou „nepoužitelné“?

22
Člen | 1478
+
0
-
  • + 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 do presenteru?

Editoval 22 (22. 8. 2012 20:49)

Filip Procházka
Moderator | 4668
+
0
-

Tohle téma by každopádně zasloužilo připíchnout :)

Elijen
Člen | 171
+
0
-

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 do presenteru?

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? :)

22
Člen | 1478
+
0
-

@Elijen: Můžeš mi ukázat, jak se v presenteru dostaneš k factory nadefinované v neonu, aniž by ses dotknul $this->context?

Elijen
Člen | 171
+
0
-

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?

Aha, to jsem te asi spatne pochopil. To zatim nijak rozume nejde, ale o tom prece tenhle topic neni.

22
Člen | 1478
+
0
-

@Elijen: aha, já myslel, že právě o tom to je, to se totiž netýká jen továren na formuláře, ale každé továrny, co máš v DIC, viz. @Hosiplanova, poznámka o tom, že DIC factories jsou nepoužitelné.

Filip Procházka
Moderator | 4668
+
0
-

Kdyby byly „použitelné“, tak tu tohle téma vůbec není ;)

thunderbuff
Člen | 164
+
0
-

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

22
Člen | 1478
+
0
-

@thunderbuff: kde to tam vidíš?

tomees
Člen | 59
+
0
-

Co kdyz budu mit v jednom presenteru napr. 50 formularu? to bude znamenat registraci 50ti sluzeb do configu a 50 properties v presenteru?

pawouk
Člen | 172
+
0
-

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…

Filip Procházka
Moderator | 4668
+
0
-

Doporučil bych věnovat pozornost tomuto vláknu/pull requestu.

mildabre
Člen | 62
+
0
-

David Grudl napsal(a):

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.

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
+
0
-

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
+
0
-

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
+
0
-

pseudonym napsal(a):

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?

$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
+
0
-

xificurk napsal(a):

pseudonym napsal(a):

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?

$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š).

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