Předání parametrů do továrny formuláře

ondrapech
Člen | 49
+
0
-

Ahoj, řeším takový asi dost začátečnický problém s továrnami. Aktuálně formulářové komponenty vytvářím v presenteru např.

// ...
public function createComponentOrderForm()
{
	$form = new Form();
	//...
	return $form
}
//...

jelikož to v presenteru udělá celkem dost řádků, které by mohli být v samostatné třídě, chtěl bych na ten formulář udělat továrnu. První otázka zní, kde je zvykem takové třídy mít jestli v modelech nebo třeba App/Forms nebo prostě kde mají místo ve struktuře projektu.
A druhá ta hlavní otázka. V těch komponentách potřebuju nějaké data která á presenter například id/data objednávky, identitu uživatele (role) nebo id/data prodejce, které potřebuju logicky předat do toho formuláře v továrně. Už sem našel,že závislosti jako třeba databázi si DI předává nějak samo. Ale co s těmito údaji? jak si je můžu předat?
Poradil by mi někdo prosím? :) Moc díky :)

Kamil Valenta
Člen | 752
+
+2
-

app/forms je fajn místo, protože ne vždy dopředu víš, kde v aplikaci bude form použit, takže je fajn mít je v nezávislém „úložišti“.

Závislosti adhoc si vyžádáš z DI, jiné parametry (různá ID apod.) si předávám normálně setterem.

ondrapech
Člen | 49
+
0
-

kamil_v napsal(a):

app/forms je fajn místo, protože ne vždy dopředu víš, kde v aplikaci bude form použit, takže je fajn mít je v nezávislém „úložišti“.

Závislosti adhoc si vyžádáš z DI, jiné parametry (různá ID apod.) si předávám normálně setterem.

Co to znamená normálně setterem? :) jakože si udělám nějakou set metodu nebo v parametrech create funkce nebo přes __set() si budu nastavovat třídní atribut. nevím co tím myslíš?

Editoval ondrapech (30. 7. 2019 11:02)

kocourPB
Člen | 47
+
+1
-

ondrapech napsal(a):

kamil_v napsal(a):

app/forms je fajn místo, protože ne vždy dopředu víš, kde v aplikaci bude form použit, takže je fajn mít je v nezávislém „úložišti“.

Závislosti adhoc si vyžádáš z DI, jiné parametry (různá ID apod.) si předávám normálně setterem.

Co to znamená normálně setterem? :) jakože si udělám nějakou set metodu nebo v parametrech create funkce nebo přes __set() si budu nastavovat třídní atribut. nevím co tím myslíš?

V komponente budes mat private/protected atributy a na ne si vytvoris settery. Napr nejak takto:

interface IOrderForm
{
	/** @return OrderForm */
	function create();
}

class OrderForm extends \Nette\Application\UI\Control {

	protected $product;

	// ...

	public function setProduct($product): Product
	{
		$this->product = $product;
	}

	// ...

}

a v presenteru potom zavolas

/** IOrderForm @inject */
public $orderFormFactory;

public funtion createOrderForm()
{
	$form = $this->orderFormFactory->create();
	$form->setProduct($this->product); // priklad

	// ...
	return $form;
}

Editoval kocourPB (30. 7. 2019 11:16)

jiri.pudil
Nette Blogger | 1028
+
+12
-

imo je to lepší vyžadovat v konstruktoru a předávat v metodě create(). Třída OrderForm pak nejde vytvořit „špatně“, tj. bez produktu. Nette parametry v konstruktoru podle názvu napáruje s metodou create a zbytek automaticky dohledá v DI kontejneru:

class OrderForm extends Nette\Application\UI\Control
{
	private $product;
	private $userContext;

	public function __construct(
		Product $product, // parametr předaný v create()
		Nette\Security\User $userContext // služba z kontejneru
	)
	{
		$this->product = $product;
		$this->userContext = $userContext;
	}
}

interface OrderFormFactory
{
	public function create(Product $product): OrderForm;
}

class OrderPresenter extends Nette\Application\UI\Presenter
{
	protected function createComponentOrderForm(): OrderForm
	{
		return $this->orderFormFactory->create($this->product);
	}
}
kocourPB
Člen | 47
+
0
-

jo s tym suhlasim, povinne parametry urcite setovat v konstruktoru … to som zapomenul zminit :o)

ondrapech
Člen | 49
+
0
-

jiri.pudil napsal(a):

imo je to lepší vyžadovat v konstruktoru a předávat v metodě create(). Třída OrderForm pak nejde vytvořit „špatně“, tj. bez produktu. Nette parametry v konstruktoru podle názvu napáruje s metodou create a zbytek automaticky dohledá v DI kontejneru:

class OrderForm extends Nette\Application\UI\Control
{
	private $product;
	private $userContext;

	public function __construct(
		Product $product, // parametr předaný v create()
		Nette\Security\User $userContext // služba z kontejneru
	)
	{
		$this->product = $product;
		$this->userContext = $userContext;
	}
}

interface OrderFormFactory
{
	public function create(Product $product): OrderForm;
}

class OrderPresenter extends Nette\Application\UI\Presenter
{
	protected function createComponentOrderForm(): OrderForm
	{
		return $this->orderFormFactory->create($this->product);
	}
}

Kluci, díky za rady. Ještě mám jeden dotaz. používáte tu oba interface. Je potřeba? k čemu je to dobré? a ten patří ve struktuře projektu tam pokud bych měl OrderFormFactory v App\Forms tak interface kam?

Kamil Valenta
Člen | 752
+
0
-

Ten interface máš uvedený v anotaci, díky němu Ti IDE bude napovídat. Samotná create() se zavolá až ve chvíli, kdy je objekt skutečně inicializován, tedy líně.

Já dávám interfacy vždy do stejného adresáře, kde je rodičovská třída, která interface implementuje. Tedy továrničky formů do app/forms, továrničky gridů do app/grids, interfacy jiných tříd do app/model/…
Dělám to tak kvůli snadné přenositelnosti celků, aby stačilo zkopírovat adresář a přeneslo se tak vše, co je potřeba, bez nutnosti „vyďobávat“ interface z nějakého hromadného úložiště…

kocourPB
Člen | 47
+
+1
-

Ja som este zvyknuty ten interface komponenty (pokial obsahuje len create() metodu) pisat priamo do toho suboru s classou, takze to mam pohromade.
Pouziva to niekto podobne? Alebo je to uplna blbost a porusuje to PSR standard? Samozrejme pre interfacy s viacerymi metodami vytvaram zvlast subor.

Diky za nazory.

jiri.pudil
Nette Blogger | 1028
+
+1
-

Ten interface je dobrý k tomu, aby ti ušetřil psaní – Nette totiž jeho implementaci vygeneruje automaticky za tebe během kompilování DI kontejneru. Jinak by sis musel napsat celou factory ručně.

janpecha
Backer | 75
+
0
-

Pokud by ses chtěl tomu interface vyhnout (ve většině případů ho reálně nepotřebuješ), můžeš se inspirovat tím, jak jsou formuláře vytvářeny v sandboxu.

ondrapech
Člen | 49
+
0
-

janpecha napsal(a):

Pokud by ses chtěl tomu interface vyhnout (ve většině případů ho reálně nepotřebuješ), můžeš se inspirovat tím, jak jsou formuláře vytvářeny v sandboxu.

A jak potom takový formulář dostanu v presenteru?

nightfish
Člen | 468
+
+1
-

ondrapech napsal(a):
A jak potom takový formulář dostanu v presenteru?

Továrnu předáš do presenteru a zavoláš ji v createComponent metodě. Inspirovat se můžeš opět v sandboxu

Rybajz
Člen | 11
+
+1
-

kocourPB napsal(a):

Ja som este zvyknuty ten interface komponenty (pokial obsahuje len create() metodu) pisat priamo do toho suboru s classou, takze to mam pohromade.
Pouziva to niekto podobne? Alebo je to uplna blbost a porusuje to PSR standard? Samozrejme pre interfacy s viacerymi metodami vytvaram zvlast subor.

Diky za nazory.

Používám to stejně.

Vojtha
Člen | 36
+
0
-

Zdravím,

snažím se dostat do do konstruktoru formuláře pole obsahující parametry, nedaří se mi Nette donutit, aby správně spárovalo všechny vstupní proměnné.

Třídu mám definovanou takto:

class FormFactory extends Nette\Application\UI\Control
{
	/** @var App\Model\DatabaseManager */
	private $databaseManager;

	/** @var array */
	private $parameters;

	/** @var Nette\Security\User */
	private $user;

	/** @persistent */
	public $locale;

    /** @var Nette\Localization\ITranslator @inject */
	public $translator;

	public function __construct(User $user, DatabaseManager $databaseManager, array $parameters)
	{
		$this->databaseManager = $databaseManager;
		$this->user = $user;
		$this->parameters = $parameters;
	}

Ale pokud v presenteru zavolám

$form = $this->formFactory->createSelectUser(['eventId' => 101]);

Dostanu

Nette\DI\ServiceCreationException

Service of type App\Model\FormFactory: Parameter $parameters in FormFactory::__construct() has no class type or default value, so its value must be specified. search►

Mohu poprosit o radu? Díky!

nightfish
Člen | 468
+
0
-

@Vojtha
Voláš $this->formFactory->createSelectUser(['eventId' => 101]);, takže $parameters si nechceš předávat konstruktorem, nýbrž jako argument metody createSelectUser().

A část class FormFactory extends Nette\Application\UI\Control moc nedává smysl – buď je to továrna (která nic extendovat nemusí), nebo je to komponenta. Míchat obojí v jedné třídě není dobrý nápad.

dakur
Člen | 493
+
+1
-

@Vojtha Myslím, že jsi nepochopil princip factories. Jak píše nightfish, to, co dědí Nette\Application\UI\Control, je form a ne form factory.

Pokud nepoužiješ factory, může mít v presenteru klidně:

protected function createComponentForm(): Form
{
	return new Form($this->user, $this->databaseManager, ['eventId' => 101]);
}

To ale není moc pohodlné, protože musíš předávat ručně všechny závislosti ($user a $databaseManager) a navíc vytváříš skrytou závislost na Form (presenter ho používá, ale není to vidět v jeho konstruktoru).

Proto raději využijeme továrnu. Její zodpovědnost je vytvořit Form s tím, že služby, které jsou v DI kontejneru, vezme odtam a zbytek předá:

final class FormFactory
{

	public function __construct(
		private User $user,
		private DatabaseManager $databaseManager,
	) {}

	public function createSelectUser(array $parameters): Form
	{
		return new Form($this->user, $this->databaseManager, $parameters);
	}

}

Nette navíc nabízí automatické vygenerování továrny, pokud z ní místo class uděláš interface. Jen myslím, že se ta metoda pak musí jmenovat create:

interface FormFactory
{
	public function create(array $parameters): Form;
}

V presenteru to pak vypadá takto:

public function __construct(
	private FormFactory $formFactory,
) {}

protected function createComponentForm(): Form
{
	return $this->formFactory->create(['eventId' => 101]);
}

Jo a ten FormFactory je třeba zaregistrovat do DI:

services:
	- Tvuj\Namespace\FormFactory

Editoval dakur (27. 9. 2022 8:01)

Vojtha
Člen | 36
+
0
-

@nightfish a především @dakur, díky moc! Už to jede.

Ještě mám jeden problém, kde tápu nad čistým řešením. Ten eventId=101 je samozřejmě jen testovací, ideálně bych potřeboval ten formulář vykreslovat vícekrát pro různé eventId – proto toto celé cvičení :-) . Jen mi není jasné, jak do

protected function createComponentForm($eventId): Form

ten parametr předat z presenteru, protože tato funkce se volá z template ve chvíli, kdy se dostane k {control form}. Nebo se pletu? Díky.

nightfish
Člen | 468
+
+1
-

@Vojtha Pokud potřebuješ jeden formulář vykreslit víckrát – pro každé eventId jeden – obal jej Multiplierem.

Vojtha
Člen | 36
+
0
-

Díky, to jsem potřeboval :-)

m.brecher
Generous Backer | 717
+
0
-

@dakur Ahoj, rád bych si upřesnil třídu Form v kódu který Jsi uvedl výše:

protected function createComponentForm(): Form
{
	return new Form($this->user, $this->databaseManager, ['eventId' => 101]);
}

předpokládám, že třída Form není Nette\Application\UI\Form, ale zjednodušený zápis třeba OrderForm který dědí z Nette\Application\UI\Control – tak jak před 3 lety v této diskuzi radil @jiripudil :

class OrderForm extends Nette\Application\UI\Control
{
	private $product;
	private $userContext;

	public function __construct(
		Product $product, // parametr předaný v create()
		Nette\Security\User $userContext // služba z kontejneru
	)
	{
		$this->product = $product;
		$this->userContext = $userContext;
	}
}

interface OrderFormFactory
{
	public function create(Product $product): OrderForm;
}

class OrderPresenter extends Nette\Application\UI\Presenter
{
	protected function createComponentOrderForm(): OrderForm
	{
		return $this->orderFormFactory->create($this->product);
	}
}

Předpokládám, že formulář který se vytvoří z vlastní třídy z Nette\Application\UI\Control bude někde vevnitř třídu Nette\Application\UI\Form obsahovat. A zde bych měl jeden dotaz:

Proč se Nette\Application\UI\Form obaluje do Nette\Application\UI\Control, něco se tím získá a co? Já jsem si udělal factory, která vytváří formulář přímo z Nette\Application\UI\Form a nejde na to oklikou přes Nette\Application\UI\Control a zdá se, že to takto celkem stačí.

Jakým způsobem se třída Nette\Application\UI\Form implementuje do obalující komponenty? Pomocí addComponent(), nebo createComponentForm()?

Takže já odhaduji, že kompletní kód by mohl vypadat asi takto:

class OrderForm extends Nette\Application\UI\Control
{
	public function __construct(
		private Product $product,
		private Nette\Security\User $user,
	)
	{}

	public function createComponentForm(): Nette\Application\UI\Form
	{
		$form = new Nette\Application\UI\Form;

		$form->addText(.....);

    	.......

		return $form;
	}
}

Případně, kdyby bylo možné dodat link na nějaký komplexní vysvětlující článek ke konceptu formuláře zabaleného do vlastní komponenty. Díky

jiri.pudil
Nette Blogger | 1028
+
+2
-

Výhodu vidím především v jasném oddělení zodpovědností. Veškerou logiku vytvoření a zpracování formuláře a všechny závislosti, které jsou k tomu potřebné, máš zapouzdřené v komponentě. V presenteru už si vyžádáš jenom jeho továrnu. Závislosti díky tomu nejsou roztahané po presenteru, což zjednodušuje i znovupoužitelnost formuláře v dalších presenterech, pokud by taková potřeba nastala.