Předání parametrů do továrny formuláře
- ondrapech
- Člen | 49
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 | 822
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
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
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 | 1032
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);
}
}
- ondrapech
- Člen | 49
jiri.pudil napsal(a):
imo je to lepší vyžadovat v konstruktoru a předávat v metodě
create()
. TřídaOrderForm
pak nejde vytvořit „špatně“, tj. bez produktu. Nette parametry v konstruktoru podle názvu napáruje s metodoucreate
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 | 822
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
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 | 1032
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ě.
- Rybajz
- Člen | 11
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 | 37
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 | 519
@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
@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 | 37
@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 | 519
@Vojtha Pokud potřebuješ jeden formulář vykreslit víckrát – pro každé eventId jeden – obal jej Multiplierem.
- m.brecher
- Generous Backer | 873
@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 | 1032
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.