Best practice: komponenty a struktura projektu
- tolljump
- Člen | 47
Ahojte.
Verim tomu ze tato tema sa zvrtne na rozsiehalejsiu diskusiu a nam to vyriesi dlhodobu otazku pri rieseni co najlepsich postupov v budovani projektu väcsieho rozsahu.
Co by som sa chcel poradit minimalne na tieto hlavne otazky:
- Ako strukturovat komponenty
- Ako komponentam spravne predavat zavislosti
- Ako komponenty dostat do presentra a inych komponent
Skusim jednoducho opisat nasu aktualnu situaciu:
Mame adresar „components“ v ktorom je zatial cca. 25 komponent. Budeme
ich neskor triedit o nieco lepsie.
Kazda komponenta sa sklada minimalne z vlastneho Control, ControlFactory a
.latte sablony.
Cize napr. AddToNewsletterControl.php, AddToNewsletterControlFactory.php a
AddToNewsletterControl.latte
Factory zaregistrujeme do configu pod services.
Cize napr.:
- App\Components\AddToNewsletterControlFactory
Nasledne mame vytvoreny trait v adresari „components“ ktoru sme
„laicky“ pomenovali LoadFactories.php
Tento subor obsahuje funkcie pre vytvaranie vsetkych komponent (je to
vykonovo idealne? spravne riesenie?):
// ADD TO NEWSLETTER
private $addToNewsletterControlFactory;
public function injectAddToNewsletterControlFactory(\App\Components\AddToNewsletterControlFactory $factory){
$this->addToNewsletterControlFactory = $factory;
}
protected function createComponentAddToNewsletter(){
$componentsSettings = $this->componentsSettings['addToNewsletter'];
return $this->addToNewsletterControlFactory->create($componentsSettings);
}
Tento trait mame pouzity v BasePresenter-i pomocou use. Vhodne riesenie nacitania?
K prvej otazke:
je to spravna struktura a nacitavanie komponent pre presenter?
mohli by sme vytvorit nejaku BaseComponent-u od ktorej by dedili vsetky
komponenty a nacitaval by sa do nej ten isty trait ako v presentri?
Ako inak dostat vzdy vsetky komponenty navzajom do akejkolvek dalsej
komponenty?
K druhej otazke:
Takmer kazdej komponente predavame z presentra premennu $componentsSettings.
Nieje to nic ine, ako pole s nastaveniami komponent vytiahnute z databazy. Je
trochu strukturovane podla nazvu komponenty a jej pozicie.
Myslim si ze to je škarede riesenie ako predavat kazdej komponente toto
$componentsSettings. Neislo by to nejakou triedou ktoru si injectnem do kazdej
komponenty, aj do presentrov? Tak aby sa ten dotaz do databazy vykonal len
raz.
Nasledne vsetky repositare a ine zavislosti predavame pomocou Factory a jeho
constructora v danej komponente. Napr.:
class AddToNewsletterControlFactory {
private $userRepository,
$mailingNewsletterEmailRepository;
public function __construct(
Model\User\UserRepository $userRepository,
Model\Mailing\MailingNewsletterEmailRepository $mailingNewsletterEmailRepository
){
$this->userRepository = $userRepository;
$this->mailingNewsletterEmailRepository = $mailingNewsletterEmailRepository;
}
/** @return AddToNewsletterControl */
public function create($componentSettings){
return new AddToNewsletterControl(
$componentSettings,
$this->userRepository,
$this->mailingNewsletterEmailRepository
);
}
}
Ako vidite, zase je tam ta hnusna premenna $componentSettings. Chcel by som sa jej nejako zbavit a riesit ju az priamo v komponente z nejakeho objektu ktory sa vytvori len raz a pouziva sa pre vsetky komponenty, presenter, atd…
A k tretej otazke:
To som uz zacal vyssie. Mame vsetky komponenty definovane v trait LoadFactories
ktory pomocou use dostavame do BasePresentra, cize vieme vsetky komponenty
pouzivat vo vsetkych presentroch.
Avsak teraz potrebujeme dostavat este aj komponenty do komponent a preto sa
pytam, ci je vhodne riesenie znova tento trait hodit do nejakej BaseComponent od
ktorej by dedili vsetky komponenty, alebo je na to nejake krajsie a
„programatorskejsie“ riesenie.
Trochu som sa rozpisal, ale dufam ze tymto vyriesime niekolko, aj zaciatocnickych otazok, definitivne.
Dakujem za akekolvek tipy, zdroje, navody, postrehy. Dufam ze to pomoze viacerym k budovaniu peknych a uspesnych projektov v ktorych sa po rokoch nebudu stracat ;-)
- jiri.pudil
- Nette Blogger | 1029
Ad 1, 3: podívej se na autowired component factories v Kdyby/Autowired. Není to sice zrovna čisté řešení, ale je strašně pohodlné a jednoduché na použití.
Ad 2: obalit si tu konfiguraci do služby je určitě dobrý nápad.
Bonus: továrničky, které vypadají tak jako ta výše, se dají zjednodušit na interface, Nette pak implementaci podobnou té nahoře vygeneruje za tebe:
interface AddToNewsletterControlFactory
{
/** @return AddToNewsletterControl */
function create($componentSettings);
}
- Oli
- Člen | 1215
Přijde mě, že to děláš zbytečně složitě. Taky jsem to dělal složitě, než jsem zjistil, že to jde i jednodušeji. Pokusím se nastínit, jak bych na to šel já.
- Vůbec bych nepředával nastavení komponenty presenteru. Presenteru je úplně jedno, jaký nastavení ta komponenta má a je to závislost, kterou vůbec nemusí mít. Pro předávání závislostí se podívej na tenhle článek: http://www.zeminem.cz/…nitive-guide
- Pokud nepotřebuješ nějaký speciální továrničky, tak bych to dělal tím interface a autowire, jak píše @jiri.pudil
- Pro každou komponentu bych předával vlastní nastavení. Je to lepší z hlediska znovupoužitelnosti. Pokud by jsi to měl rozdělené do nějaký ch bloků/modulů, tak nastavení pro celý modul. Mám to tak taky, kdy například articleModule je v podstatě soubor několika komponent a má jedno nastavení, protože se vždy použije jako celek.
- negeneroval bych všechny komponenty v basePresenteru. Jestliže jich máš 25 a budou přibývat, tak nepředpokládám, že je všechny potřebuješ na všech stránkách. Myslím, že pokud je nepoužiješ, tak se ani nevytvoří, ale stejně mi to nepřijde jako dobrý nápad.
Celé by to potom mohlo vypadat nějak takhle:
parameters:
addToNewsletter:
testkey1: testvalue1
testkey2: testvalue2
# ...
services:
- implement: AddToNewsletterControlFactory
arguments: [%addToNewsletter%]
interface AddToNewsletterControlFactory
{
/** @return AddToNewsletterControl */
function create( /*$componentSettings*/ ); // EDIT: tenhle parametr není potřeba
}
class AddToNewsletterControl extends Nette\Application\UI\Control
{
public function __construct($configParam, Model\User\UserRepository $userRepository, Model\Mailing\MailingNewsletterEmailRepository $mailingNewsletterEmailRepository) {}
}
public function createComponentAddToNewsletterControl($name, AddToNewsletterControl $addControl)
{
return $addControl->create();
}
Editoval Oli (28. 4. 2015 10:20)
- tolljump
- Člen | 47
Z toho vytvarania komponent som stale nejak blazon…
Nemozem do configu zadavat do parameters nejake nastavenia, pretoze tie sa
tahaju z DB a su naplnene v premennej $componentSettings.
Tym interface tiez stale trochu nerozumiem… Pretoze ja Factory pouzivam na injectovanie vsetkych repositarov (ako som ukazal v kode v prvom prispevku), no v interface to nieje mozne (alebo sa mylim?).
Ale ako vravis @Oli , tak nebude idealne ich vsetky vkladat do
BasePresentra. Ako ich teda mat kedykolvek a kdekolvek dostupne?
Mam rozne komponenty ktore potrebujem mat dostupne napriec vsetkymi
presentermi.
Rsp. co je pre mna este väcsi problem, potreboval by som vediet akukolvek
komponentu pouzivat v akejkolvek inej komponente. Je to nejakym SPRAVNYM
sposobom mozne? Nemyslim si ze predavat trait LoadFactories v ktorom mam vsetky
komponenty definovane je spravne riesenie. Pride mi to uplne skarede a
„neprogramatorske“.
V tom trait sa mi zacina hromadit mnozstvo kodu so stale sa opakujucim
zapisom
// ADD TO NEWSLETTER
private $addToNewsletterControlFactory;
public function injectAddToNewsletterControlFactory(\App\Components\AddToNewsletterControlFactory $factory){
$this->addToNewsletterControlFactory = $factory;
}
protected function createComponentAddToNewsletter(){
$componentsSettings = $this->componentsSettings['addToNewsletter'];
return $this->addToNewsletterControlFactory->create($componentsSettings);
}
Tu by som sa vratil este k tomu $componentSettings. Ako som vravel, je to
pole vytiahnute z databazy.
Cize tak ako si pisal ze kazdej komponente vyberat len jej nastavenia by
znamenalo niekolko-nasobne viac DB selectov ktore su podla mna zbytocne.
Momentalne mam raz v BasePresentri vytvorenu public premennu
$componentsSettings ktora je naplnena jednym DB dotazom.
Je mozne sa tejto premennej nejakym sposobom zbavit? Aby som mal napr. nejaky
objekt/komponentu/cokolvek co by vykonalo raz select do DB a nasledne by som
odtial tahal v komponentach uz len nastavenia tej danej komponenty?
- newPOPE
- Člen | 648
A co takto ist na to cez ComponentFactory
ktora ti komponentu
aku potrebujes vyrobi (btw: tak funguje /fungovala
createComponent($name)
v Presenteri).
ComponentFactory
Ti vyrobi komponentu aku potrebujes. Takisto si
ta ista factory moze loadnut konfiguracie z DB, cache … a riadit si ich ako
potrebuje.
Na druhej strane to este mozes urobit aj tak ze proste pouzije mechanizmus aky Nette poskytuje a to je predavanie zavislosti do component automacky pomocou DI. A nastavenia si tam tiez mozes poslat z konfigu zo sluzby ktora bude obsahovat jednotlive konfiguracie pre komponenty nieco taketo:
services:
componentConfiguration: ComponentConfiguration
-
class: MyControl(@componentConfiguration::getConfig('my'))
implement: IMyFactory
Co sa tyka predania predania dalsich zavislosti do MyControl
tak
mam pocit ze DI container sa o to postara nieco ako:
class MyControl ... {
function __construct($config, Foo $foo, Bar $bar) {
# tu by mali byt automaticky predane Foo a Bar a $config si si nastavil v config.neon
}
}
- tolljump
- Člen | 47
No to predavanie konfiguracie pre komponentu pomocou configu vyzera zaujimavo. Ale ako to bude fungovat v pripade vsetkych komponent ak pri kazdej budem mat v configu:
class: MyControl(@componentConfiguration::getConfig('my'))
class: FooControl(@componentConfiguration::getConfig('foo'))
class: BarControl(@componentConfiguration::getConfig('bar'))
Ako by vyzeral class componentConfiguration? Napr.:
__construct by volal metodu setComponentsConfig()
metoda setComponentsConfig() by spravila dotaz na databazu z ktorej by vybrala
vsetky nastavenia komponent a nastavila ich do private $componentsConfig.
Nasledne metoda getConfig($name) by vyberala z $this->componentsConfig[$name]
nastavenia danej komponenty.
Spravne?
Otazka: nebude sa takto dotazovat do databazy vzdy kazda komponenta ktorej sa bude predavat ako parameter tato trieda componentConfiguration?