Komponenty a Presentery v DI Containeru
- Filip Procházka
- Moderator | 4668
Ahoj, docela mě vzrušuje představa, že bych měl komponenty a presentery v DI Containeru. Je tu ovšem několik technických nedostatků, které mi brání vůbec začít to zkoušet implementovat.
- Jména – Problém s presentery. Ideální stav? Mít
službu
@presenter
, v ní nečekaně presenter a ten si pak vytáhnout/vytvořit pomocíIPresenterFactory
aApplication
už si s tím nějak poradí. No ale teď co když potřebuji použít forward? Mohl bych původní presenter vyhodit z Containeru a nahradit ho novým, ale to není moc košér. Jak pojmenovat službu pro presenter, když jich mám více? Snad bych i překousel@presenter2
, byt je to krapet trapne. - Závislosti – jak nadefinuju závislosti pro více presenterů? To bych musel mít továrničku pro každý jeden presenter a to je trošku hardcore. Částečně to řeší autowire, ale stejně ty presentery musím mít nějak registrované, protože „současná rozbitá“ i „před-chvílí nerozbitá“ implementace DIC mi „brání“ splodit instanci třídy, která není registrovaná v containeru. Protože až se vybuildí statickej PHP soubor, co container sestavuje, tak už nejsou k dispozici data o rozhraních a třídách, nehledě na to, že je to zabetonované v Nette\Configurator::loadConfig() (což je detail).
Mít v Containeru presentery a komponenty není účel, účelem je ulehčit si předávání závislostí. Asi by bylo dobré mít možnost, registrovat je tam v run-time (což malinko odporuje DI, ale asi bych to překousl).
Nápady?
- Honza Marek
- Člen | 1664
Služba @presenter s aktuálním presenterem je blbost. Měl bys mít právě těch milion služeb @frontHomepagePresenter, @frontArticlePresenter a presenter factory by si je měla umět vytáhnout buď podle jmenné konvence nebo podle tagu. Že každý presenter má svoje vlastní závislosti je zcela normální a tudíž by měl být každý nakonfigurovaný v containeru zvlášť, byť pomocí autowiringu.
- Filip Procházka
- Moderator | 4668
Dobrý, to dává smysl. Ale není trochu hardcore mít všechny presentery v containeru?
To máš opravdu config o 1000 řádcích? (Přeháním, ale když tam nebudou jen presentery ale i modely a komponenty, tak to může klidně být ještě víc)
Editoval HosipLan (2. 11. 2011 14:33)
- Tharos
- Člen | 1030
Nechci do toho moc vstupovat, protože s implementací této myšlenky mám výrazně méně zkušeností, než Honza, nicméně… :)
HosipLan napsal(a):
Ale není trochu hardcore mít všechny presentery v containeru?
Proč? Mně to naopak přijde super. Veškeré závislosti v celém systému máš na jednom místě a můžeš je také řídit z jednoho místa.
Mně spíš přijde nešikovné, že v Nette se DI kontejner konfiguruje ve stejném configu, ve kterém se většinou definují například i connection stringy k databázi a podobně. To jsou relativně vzdáleně související věci a zrovna připojení k databázi tam pak může být třeba úplně utopené „někde dole“…
Pokud bys měl ale oddělený konfigurační soubor pro kontejner, který by skutečně řešil pouze služby, určitě by Ti nepřišlo hloupé, že bude obsahovat 1000 řádků, když skutečně v systému budeš mít 1000 služeb…
Editoval Tharos (2. 11. 2011 15:38)
- Honza Marek
- Člen | 1664
Přijde mi rozumné dát konfiguraci presenterů do vedlejšího souboru (právě kvůli rozsahu konfiguráku). To ale nevím jak je možné v Nette, v Mediu používáme symfonní container. Další věc, co se hodí, tak je společné nastavování setterů třeba pro všechny front presentery. V symfony to lze provést pomocí dědění definic služeb, v nette v php továrničkách by to šlo pomocí nějaké pomocné konfigurační metody. V nette neonu nevím.
- Filip Procházka
- Moderator | 4668
Ještě zkusím pár dní odolávat a asi mi nezbude nic jiného, než ho opravdu použít. Což mě upřímně mrzí.
Disclaimer: autor tohoto příspěvku uvedl v letošním sčítání lidu jako víru Nette Framework
- Honza Marek
- Člen | 1664
services:
presenter:
abstract: true
calls:
- [setMenuFactory, ["@menu_factory"]]
- [setTemplateConfigurator, ["@template_configurator"]]
...
# front presenter podědí settery z abstraktní služby presenter
front.homepage_presenter:
parent: presenter
class: MyApp\FrontModule\HomepagePresenter
arguments: ["@article_model", ...]
- David Grudl
- Nette Core | 8229
Aha, já si to blbě přečetl, dědění definicí služeb, jasně.
Pokud bys chtěl sestavovat presentery s plným využitím DI a konfiguráků, tak potom, jak píše Honza Marek, je jediná možnost mít každý presenter jako samostatnou službu. Každý záznam v konfiguraci je pak vlastně továrničkou, byť napsanou v jiném jazyce než PHP.
Na rozdělení konfigurace do více souborů chci buď doplnit sekci
include
do konfiguráku nebo nahradit loadConfig()
za
možnost vícenásobného volání addConfig()
(a nebo obojí).
Zároveň se nadtřída Configurator rozpadne do více samostatných tříd,
každá z nich bude zpracovávat samostatnou část konfigurace.
- Tharos
- Člen | 1030
David Grudl napsal(a):
Zároveň se nadtřída Configurator rozpadne do více samostatných tříd, každá z nich bude zpracovávat samostatnou část konfigurace.
Tak to je výborná zprává, protože zejména božská metoda™
loadConfig
mně osobně dost trápila. Už jenom kvůli tomu
zadrátovanému parsování služeb z neonu…
Editoval Tharos (3. 11. 2011 12:54)
- David Grudl
- Nette Core | 8229
BTW, stále nejsem schopen se rozhodnout. Mám konfigurák
config.neon
, kde bude sekce includes
se seznamem
dalších konfiguračních souborů. Otázka zní: mají inkludované
konfiguráky přepisovat hodnoty prvního konfiguráku nebo naopak má hlavní
konfigurák přepisovat hodnoty z inkludovaných?
Tj. config.neon
:
includes:
- routes.neon
- services.neon
- parameters.neon
parameters:
a: 10
a parameters.neon
:
parameters:
a: 20
Kolik bude $a
?
- pave.kucera
- Člen | 122
Inkludované soubory by podle mě měly přepsat hodnoty z dříve načtených souborů, tedy 20. Vidím tam paralelu s čistým php, kde inkludované soubory taktéž přepíší už definované proměnné, pokud jim přiřadí hodnotu.
a.php
<?php
$a = 10;
include 'b.php';
dump($a);
?>
b.php
<?php
$a = 20;
?>
- David Grudl
- Nette Core | 8229
Nebo mohu napsat:
<?php
include 'b.php';
$a = 10;
dump($a);
?>
To asi moc nevypovídá. Logiku mi spíš dává výsledek 10
,
tj. inkludovat a přepsat si, než přepsat si inkludováním. Z hlediska
znovupoužitelnosti.
- Filip Procházka
- Moderator | 4668
Hodnoty z configu by měly mít větší váhu, než hodnoty z
include
. Čili a = 10
@22: koukáš na to ze špatné strany, výchozí
jsou ty, které includuji (parameters.neon
).
Editoval HosipLan (10. 11. 2011 18:02)
- Milo
- Nette Core | 1283
A co tak, že to co bylo později vítězí?
includes:
- routes.neon
- services.neon
- parameters.neon
parameters:
a: 10
// a = 10
parameters:
a: 10
includes:
- routes.neon
- services.neon
- parameters.neon
// a = 20
Případně přidat nějak flag, obdoba !important
z CSS. To
už se to ale dost komplikuje.
- Pavel Kouřil
- Člen | 128
Já bych to bral tak, že config.neon bude obsahovat výchozí nastavení a includnuté soubory budou tyto hodnoty přepisovat. Protože includování není nutné (narozdíl od toho mít config.neon) a tím pádem vám to volitelně přepíše na vlastní hodnoty a nastavení, pokud potřebujete upravovat nevýchozí nastavení.
Na druhou stranu jsem schopný se smířit s obojím, protože si dokážu představit, že by specifické lokální nastavení (které by se neverzovaly gitem apod.) nijak nepřepisovaly v hlavním neonu :)
tl;dr
preferoval bych a = 20
- Patrik Votoček
- Člen | 2221
David Grudl napsal(a):
Otázka zní: mají inkludované konfiguráky přepisovat hodnoty prvního konfiguráku nebo naopak má hlavní konfigurák přepisovat hodnoty z inkludovaných?
Co takhle obojí. Include vs. Extends (funkční jako v latte).
- Patrik Votoček
- Člen | 2221
Pajka napsal(a):
Já bych to bral tak, že config.neon bude obsahovat výchozí nastavení a includnuté soubory budou tyto hodnoty přepisovat. Protože includování není nutné (narozdíl od toho mít config.neon) a tím pádem vám to volitelně přepíše na vlastní hodnoty a nastavení, pokud potřebujete upravovat nevýchozí nastavení.
Co když ale vznikne něco jako default-services.neon?
Na druhou stranu jsem schopný se smířit s obojím, protože si dokážu představit, že by specifické lokální nastavení (které by se neverzovaly gitem apod.) nijak nepřepisovaly v hlavním neonu :)
přesně tak
- Filip Procházka
- Moderator | 4668
@Milo: zbytečně moc případů jak to nakombinovat a zbytečně složité.
@Patrik Votoček: To zní rozumně, ale stejně zbytečně složité, ne?
- Šaman
- Člen | 2666
Mě by dávalo smysl buď že pozdější volání přepisuje dřívější
(je jen na mě, jestli defaultní hodnoty napíšu před include sekci, nebo
naopak vnutím nějaké hodnoty až po includování), ale pokud by se mělo
stanovit pravidlo kdo má přednost, tak hlavní config by měl mít možnost
přepisovat nastavení v includovaných souborech. Takže
a = 10
.
Ještě mě napadá vypůjčit si syntax z latte:
a: 10;
default b: 15;
- 22
- Člen | 1478
HosipLan napsal(a):
Hodnoty z configu by měly mít větší váhu, než hodnoty z
include
. Čilia = 10
@22: koukáš na to ze špatné strany, výchozí jsou ty, které includuji (
parameters.neon
).
no tak jestli je to tak, tak pak 10, ale spíš mi přijde víc praktické mi config.noen, jako výchozí hodnotu a includované konfigy ho nějakým způsobem modifikují/rozšiřují..
- Šaman
- Člen | 2666
22 napsal(a):
no tak jestli je to tak, tak pak 10, ale spíš mi přijde víc praktické mi config.noen, jako výchozí hodnotu a includované konfigy ho nějakým způsobem modifikují/rozšiřují..
To je věc názoru. IMHO config.neon ručí za správné nakonfigurování programu a pokud potřebuji nějakou hodnotu přepsat, musí k tomu mít možnost. Já includy chápu tak, že každý pokrývá nějakou oblast/modul/komponentu a ten hlavní konfig to celé sestaví (případně přepíše některé hodnoty) a vrátí aplikaci.
Taky se bude lépe přepisovat hodnota v hlavním configu (třeba kvůli ručním testům, fuj:), než dohledávat ve kterém includovaném souboru je definovaná, případně ze kterého souboru ta proměnná platí (pokud by se definovala ve více souborech).
- norbe
- Backer | 405
Osobně se mi asi nejvíc líbí priorita hlavního konfigu (důvod viz příspěvek od Šamana).
Když už jsme u toho, nezvažuje se možnost includovat soubory pomocí masky? Například
includes:
- %appDir%/*services.neon
by mohlo vložit všechny služby ze všech modulů…
Editoval norbe (10. 11. 2011 21:14)
- Vojtěch Dobeš
- Gold Partner | 1316
Vzhledem k tomu, že specifické parametry (heslo k DB) budu chtít mít
v souboru, který hodím do .gitignore
, tak to pro mě znamená,
že je nemůžu mít v config.neon
(protože ten musí obsahovat
definici includes:
a tudíž sám o sobě být v
.gitignore
nemůže). Pak by tedy měla existovat možnost jak
z inkludovaného přepsat hlavní, nemýlím se?
Řešením by bylo neinkludovat v configu, ale v bootstrapu. Pak bych mohl
gitignornout
hlavní konfigurák:
$container = $configurator->setConfig('config.neon') # v .gitignore (obsahuje jen moje spec. nastavení)
->include('routes.neon')
->include('services.neon')
->include('parameters.neon')
->load('common');
Ale to se už asi nechávám unést fantazií :)
Editoval vojtech.dobes (11. 11. 2011 2:04)
- Vojtěch Dobeš
- Gold Partner | 1316
mkoubik: pak se ale ztrácí výhoda přepisování
konfiguráků a musím v config.neon
myslet na to, že nesmím
uvést database.password
- Patrik Votoček
- Člen | 2221
vojtech.dobes napsal(a):
Řešením by bylo neinkludovat v configu, ale v bootstrapu. Pak bych mohl
gitignornout
hlavní konfigurák…
To mě připadá už zbytečné. Config ve kterém je include by měl mít vyšší prioritu. Pak bych to řešil nějak takto:
$config = __DIR__ . "/environment.neon";
$configurator->loadConfig(file_exists($config) ? $config : NULL);
config.neon
includes:
- defaultServices.neon
- defaultParams.neon
- modules.neon
production:
database:
driver: mysql
user: app
database: app
environment.neon
includes:
- config.neon
production:
database:
password: T0pS3cr3t
<OT>Prořád přemýšlím jak by pomocí toho environment.neon šlo určovat sekci, která se má načíst a zapínat / vypínat „development“ laděnku.</OT>
- janci
- Člen | 5
Mne sa tiež celkom pozdáva, keby bolo to riešenie co navrhol @Patrik Votoček, Extends a Include, tak by si kazdy vedel nastavit ten config podla toho ako potrebuje. Lebo su pripady, kedy niektorá časť aplikácie môže chcieť preťažiť defaultnú konfiguráciu a naopak, kedy je vhodnejšie niektorú časť tam proste len includnut a tým napríklad rozšíriť služby o nové služby.
- David Grudl
- Nette Core | 8229
Nevím, co přesně znamená ta defaultní konfigurace, ale pokud to potřebujete, tak si ji inkludnete hned jako první a problém je vyřešen.
- bene
- Člen | 82
Nevím jak v include
, ale co se týče
->addConfig('default.neon')->addConfig('app.neon')
, tak
očekávám, že defaultní config rozšiřuji, popřípadě přepisuji
defaultní hodnoty. Teoreticky se to dá napsat opačně
->addConfig('app.neon')->addConfig('default.neon')
, ale
příjde mi to méně logické.
Možná bych se na include
díval stejně.
Popřípadě možná mít include
a extends
jak
někdo zmínil. Ale bude to zbytečně komlikované a budou se v tom
dělat chyby.
Ve finále to povede stejně k tomu, že v
config.neon
bude:
include defaultServices.neon
include appServices.neon
include defaultConfig.neon
include appConfig.neon
Pokud budu řešit konfiguraci v závislosti na nějakých modulech
aplikace, nezbyde mi stejně nic jiného než použít
->addConfig('module.neon')
.
Editoval bene (12. 11. 2011 14:34)
- pekelnik
- Člen | 462
Co tohle? :)
Nevím jakto že to tady ještě nikdo nezmínil ale nemělo by to být tak, že pokud vložím konfiguraci na začátek souboru – mohu dále v souboru vloženou konfiguraci změnit?
Pokud chci mít vložené hodnoty „nahoře“ vložím konfiguraci nakonec…
Dělá to tak třeba Apache…
- Configuration file is parsed top to bottom
- Included files are parsed right at their include location. In other words within the configuration file.
- For directives which are called more than once, the last call overrides the previous calls.
- For a directive called more than once in a directory tree, the lowest call in the directory tree takes precedence.
- Vojtěch Dobeš
- Gold Partner | 1316
Aneb jak otázka zužuje prostor pro odpověď :). Tohle mi přijde velmi rozumné – odpověď tedy zní: inkludovaný přepisuje dosavadní část inkludujícího konfiguráku.
- jansfabik
- Člen | 193
vojtech.dobes: To se mi nelíbí. Co kdybych chtěl v jednom souboru použít obojí?
includes:
- someService.neon
- anotherService.neon
parameters:
database:
host: localhost
username: root
password: 12345
database: xyz
includes: # <-- tohle přepíše pův. seznam
- credentials.neon
Myslím, že ideální bylo přepisování zakázat, čímž by se předešlo různým WTF záležitostem.
Např. Změním v config.neon
hodnotu password
, ale
nic se nestane (protože soubor credentials.neon
to heslo
přepíše). Správné by bylo, kdyby tam taková hodnota vůbec nebyla, aby to
nemohlo nikoho mást.
- Filip Procházka
- Moderator | 4668
Ono je u tohohle hloupé (nebo spíš chytré?), že neon vám nedovolí dvě sekce se stejným názvem, prostě tou druhou přepíšeš tu první.
Můžete vyzkoušet na: http://ne-on.org/
- wdolek
- Člen | 331
K predchozi diskusi bych se vyjadril tak, ze (IMHO):
- paklize mam hodnotu v
my_included_conf.neon
, vmain_conf.neon
je tato hodnota prepsana (s ohledem na to, ze kdyz uz neco takoveho delam, je to zrejme jen docasne reseni) – hlavni konfiguracni soubor prebiji vse* - nezalezi na poradi (kdyby ano, je to „Pure WTF™“ – umim si predstavit, jak si nekdo pastne do sveho konfiguraku neco duleziteho, a nevsimne si, ze nechal nejaky jiny dulezity radek nad/pod)
* a kdyz uz by to muselo byt opacne – vetsi vahu by mela specificke
konfigurace – musela by zde byt moznost addÜberConfig
k prebiti specifictejsich konfiguraku.
A urcite bych se v tomto pripade neinspiroval konfiguraci Apache – precejen nastaveni serveru je pomerne staticke – clovek se v tom az zas tolik nevrta. Na rozdil od nastaveni aplikace, kdy casem pridava nove a nove veci.
Editoval wdolek (19. 11. 2011 18:12)
- Vojtěch Dobeš
- Gold Partner | 1316
Přijde mi to jako zajímavá a důležitá feature, ale začínám se v tom ztrácet. Nechtěli byste házet třeba na https://gist.github.com vaše use-case? Protože o ty podle mě jde… házím vlaštovku: https://gist.github.com/1378723
Mám trochu strach, jestli nevymýšlím nesmyslné scénáře, a zajímalo by mě, jaké jiné pro využívání konfiguračních souborů existují.
- Majkl578
- Moderator | 1364
Rozhodně by include mělo fungovat jako asistované copy&paste. Tzn.
vzal by se obsah toho includovaného souboru a vložil by se do inkludujícího.
(Jinými slovy – později definované přepisuje dříve definované.)
Přijde mi to jako běžné a logické chování.
Mimochodem, include bych nepovažoval za sekci, ale za něco jako informaci preprocesoru v C.
- wdolek
- Člen | 331
Jeste bych mel takovou ideu: kdyz uz mame „zahardcodeny“
config.neon
, proc nemit jeste treba local.neon
. Prave
local.neon
by mel tu vlastnost, ze by prebijel vse. (toto neni
z me hlavy)
Mel bych tedy:
config.neon
ktery obsahuje spolecnou / obecnou konfiguracifoobar.neon
ktery obsahuje konfiguraci „foobar“local.neon
ktery obsahuje zmeny cehokoliv pro pripad debugaze/testovani neceho/rychle povypinani featur apod.
local.neon
by se nacital automaticky s jinou konfiguraci.
- Ondřej Mirtes
- Člen | 1536
Koukám, že se tahle diskuse hodně odklonila od původního tématu.
Dneska jsem kamaráda učil základy Nette a z toho způsobu, jak se
v čistém Nette píšou presentery, jsem byl značně rozladěný. Pokud chci,
aby mi aplikace dodržovala DI, musím si všechny služby v presenteru
vytahovat přes zdlouhavé
$this->getContext()->getService('...')
. Že to z DI
kontejneru dělá Service Locator a že mi v takovém momentě nefunguje
napovídání v IDE snad netřeba zdůrazňovat.
Presenter by si měl všechny služby, které využívá, vyžádat
v konstruktoru podle zásad DI. Napovídání se pak rozeběhne,
v blbějších IDE se k privátní proměnné ještě napíše
@var typ
. Taky získáme přehled o všech závislostech, které
presenter má, pokud pominu služby jako HttpRequest, User apod., které jsou
k dispozici přes metody v předkovi Nette\Application\UI\Presenter díky
contextu.
V config.neon bych vedle pole services
navrhoval založit
ještě pole presenters
:
presenters:
Front:
Homepage:
class: FrontModule\HomepagePresenter
arguments: [@articlesModel, @user]
Error:
class: FrontModule\ErrorPresenter
Article:
class FrontModule\ArticlePresenter
Zde by se mohla uplatnit trocha magie – pokud by jméno třídy souhlasilo
se současnou konvencí v Nette, class
by se mohla vynechat a
PresenterFactory by si ji sama odvodila.
- Patrik Votoček
- Člen | 2221
Davíde nějaké novinky / pokrok kdy by se to mohlo oběvit na githubu? Mám v Nella Frameworku (privatni / pracovní verzi) již nějaký čas naimplementovaný configurator rozdělený do více tříd. Nicméně nechce se mě to dávat na github, když se má odehrát „velká“ změna přímo v Nette.
- David Grudl
- Nette Core | 8229
Ondřej Mirtes napsal(a):
Presenter by si měl všechny služby, které využívá, vyžádat v konstruktoru podle zásad DI.
To, co navrhuješ, lze realizovat vcelku triviální úpravou PresenterFactory. Problém vidím v předávání služeb přes konstruktor. UI\Presenter využívá HttpRequest, HttpResponce, User, Session, PresenterFactory, Router a templateCacheStorage. To máme 7 parametrů v konstruktoru. Každý potomek tedy musí převzít 7+x parametrů, 7 předat do parent::__construct a ostatní si uložit. Dovedu si představit, že časem projde UI\Presenter refactoringem, počet služeb se sníží případně objeví nějaká nová. A všechny, kdo použijí DI přístup, tak narazí na děsný BC break.
Samozřejmě by se těch 7 služeb mohlo zamést pod jeden Nette\DI\Container, ale to jaksi není DI, že. Eventuálně použít potomka Nette\Application\UI\PresenterContainer, který by alespoň formálně určoval obsah containeru, ale stále je to ServiceLocator.
Rozumnější se mi jeví zavést method setter, takže presentery by místo konstruktoru vytvářeli např. metodu setContext() a v ní specifikovali požadované služby. Tipuju, že taková metoda bude vždy jen přemisťovat argumenty do proměnných objektu, takže by se možná dalo rovnou uvažovat o nějakém property setter. (A předsudky stranou, mezi method setter a property setter je rozdíl jen jako mezi public a protected).
- Tharos
- Člen | 1030
Možná budu za puristu, ale… nemůžu se ubránit dojmu, že tady je prostě prapůvodní problém v tom, že v Nette toho má Presenter na starost příliš mnoho. Troufám si tvrdit, že každá třída, která ke své činnosti potřebuje 7+x dalších instancí, je vážným adeptem k refaktorování… Jenomže to je v tomhle případě asi nereálné, protože to by byl BC break „na druhou“…
Ale kdyby hlavní novinkou Nette 3.0 byl kompletně refaktorovaný návrh plně v duchu SOLID designu, tenhle problém by úplně odpadl. :)
Editoval Tharos (13. 12. 2011 21:37)
- David Grudl
- Nette Core | 8229
Tharos napsal(a):
Možná budu za puristu, ale… nemůžu se ubránit dojmu, že tady je prostě prapůvodní problém v tom, že v Nette toho má Presenter na starost příliš mnoho. Troufám si tvrdit, že každá třída, která ke své činnosti potřebuje 7+x dalších instancí, je vážným adeptem k refaktorování…
Jasně, ale tohle s věcí vůbec nesouvisí a navíc to v příspěvku zmiňuju.
HosipLan napsal(a):
Davide, a co něco takového?
Nerozumím, kam tím míříš.