nested accessor – proč byl odstraněn?
- Filip111
- Člen | 244
Narazil jsem teď po updatu na dev verzi, že se již nepoužívá nested accessor ( viz. https://forum.nette.org/…tw-bootstrap?p=2 , nechtěl jsem ten topic dál zasviňit nečím, co nesouvisí)
Prohledal jsem fórum a snažil se přijít na to proč byl odstraněn, ale nenašel jsem rozumné vysvětlení – můžete sem někdo napsat nějaký teoretický background?
Pochopil jsem, že teď už je defakto jediný rozumný přístup ke
službám v presenteru jen pomocí inject metod. Ano, ještě lze napsat
$this->context->{'cms.sluzba'}
, ale to mi přijde tak
odporný, že to snad nikdy používat nebudu.
Taky jsem zahlédl autowire namísto použití inject metod ( https://forum.nette.org/…a-steroidech
).
Má to budoucnost? Uvažuje se o tom do další verze Nette, případně do
budoucna?
Pokud budu používat dev verzi nette nebo posléze verzi 2.1, budu muset upravit/zkontrolovat většinu presenterů (cca 100) kvůli používání $this->context. Když už to budu dělat, chtěl bych to dělat pořádně (a jednoduše), ať to za měsíc zase nepředělávám.
Díky.
- Filip111
- Člen | 244
@Majkl578: Asi to z prvního postu nevyplynulo – $this->context
se chci zbavit každopádně, takže buď to nahradím anotacemi @inject
nebo @autowired.
Neuvažuje se v budoucnu o tom, že by anotace @inject byla
také lazy?
Komentáře pod commitem jsem četl, ale stejně jsem se nedozvěděl, proč byl NestedAccessor odstraněn. Byla to nějaká bad practise, presárna nebo to bylo pomalé nebo?
Díky.
- enumag
- Člen | 2118
@Filip111: Je to bad practice a např. moje komentáře pod tím commitem jsou jasná ukázka jak jsem to kvůli své neznalosti zneužíval. Dobře že je to pryč.
Anotace @inject lazy nebude protože pak to neni inject. Pro pochopení si nastuduj nějaké články o DI vs Service Locator, třeba na PHPFashion. Pokud to potřebuješ tak ten @autowired doplněk bohatě stačí.
- Filip111
- Člen | 244
Zkouším přepsat některé presentery na anotace @inject a mám dotaz:
V anotaci se uvádí pouze třída, nikoliv název služby definované v DIC. Co když mám ale dvě služby se stejnou třídou, jen předem rozdílně nakonfigurované. Např. nějaký filtr článků a katalogu:
cms.filter1:
class: web123\Filters\BaseFilter
arguments: [@session::getSection('filterStorage1')]
cms.filter2:
class: web123\Filters\BaseFilter
arguments: [@session::getSection('filterStorage2')]
Následující deklarace pak skončí chybou „Multiple services of type web123\Filters\BaseFilter found.“
/**
@var web123\Filters\BaseFilter
@inject
*/
public $catalogFilter;
Jak mám říct, že právě v tomhle presenteru potřebuji service s názvem cms.filter1?
- Filip111
- Člen | 244
@Aurielle:
To je přesně to co nechci dělat. Pokud bych měl nějaké třídy duplikovat
jen s jiným názvem, tak to už můžu rovnou zůstat u $this->context –
přijde mi to jako menší zlo :)
@Hosiplan:
A co když stejně potřebuji injectovat nějaké parametry? Jde to?
Taky jsem na to narazil, když jsem se snažil rozběhnout co nejrychleji dev
verzi. Nakonec jsem dev verzi vyhodil a dal tam 2.0.10 – bylo by to delší
dobu a neměl jsem čas.
O spoustě věcí, jsem nenašel v dokumentaci ani na fóru téměř nic,
takže počkám až bude oficiálně vydaná další verze, pak začne tyhle
problémy řešit víc lidí a nebudu úplně sám/za blbce.
Nicméně ještě bych se vrátil k otázce #p102038 – jde to nějak čistě vyřešit?
- Filip Procházka
- Moderator | 4668
Filip111 napsal(a):
A co když stejně potřebuji injectovat nějaké parametry?
Nepotřebuješ.
Jde to?
Nejde.
Nicméně ještě bych se vrátil k otázce #p102038 – jde to nějak čistě vyřešit?
Nejde to vyřešit tak jak chceš ty. Představ si, že máš služby v DIC a nemají jména. Máš pouze nějaké třídy a interfacy a když si chceš něco nechat injectnout, tak si řekneš o implementaci a DIC ti nějakou dá. Zapomeň, že služby mají jména a přemýšlej pouze v typech :)
Buďto třídy podědíš, abys měl konkrétní typy
class CatalogFilter extends web123\Filters\BaseFilter {}
class ArticlesFilter extends web123\Filters\BaseFilter {}
cms.filter1:
class: web123\CatalogFilter
arguments: [@session::getSection('filterStorage1')]
cms.filter2:
class: web123\ArticlesFilter
arguments: [@session::getSection('filterStorage2')]
Tyto si pak už snadno necháš injectnout
/**
* @var web123\CatalogFilter
* @inject
*/
public $catalogFilter;
/**
* @var web123\ArticlesFilter
* @inject
*/
public $articlesFilter;
Nebo si vytvoříš nějakého správce, po kterém budeš vyžadovat jednotlivé implementace.
class FilterManager extends Nette\Object
{
private $filters = array();
public function add($name, web123\Filters\BaseFilter $filter)
{
$this->filters[$name] = $filter;
}
public function get($name)
{
return $this->filters[$name];
}
}
cms.filterManager:
class: web123\FilterManager
setup:
- add('catalog', web123\Filters\BaseFilter(@session::getSection('filterStorage1')))
- add('articles', web123\Filters\BaseFilter(@session::getSection('filterStorage2')))
Potom si ho v presenteru necháš injectnout
/**
* @var web123\FilterManager
* @inject
*/
public $filterManager;
A vždy si vytáhneš objekt který tě zajímá
public function actionDefault()
{
$catalogFilter = $this->filterManager->get('catalog');
// ...
}
Osobně mi přijde druhé řešení lepší :)
- Filip111
- Člen | 244
@Filip Procházka:
Dík za rozsáhlejší odpověď – první řešení se mi nelíbí. Budu
úplně zbytečně vytvářet dvě (a více) nové třídy, které budou defakto
úplně stejné, jen se budou lišit v nastavení session úložiště. Budou
se tedy lišit jen konfigurací, a to do nich podle mě nepatří – od toho
máme (alespoň až dosud jsme měli) neon.
Druhé řešení není řešení, jen obcházení problému. Rozhodně to není čisté – když můžu předat jednu konkrétní službu, proč předávat celý FilterManager?
Narazil jsem na další podobnou situaci, kde mi to opět přidělává
spoustu práce. Ačkoliv tady asi budete argumentovat, že každá fasáda má
mít vlastní třídu. Mám definovaná repository a nad nimi jsou fasády.
80% repository jsou jen jednoduché číselníky
seznam/editace/vložení/úprava, takže pro ně používám jedinou fasádu,
která to vše zvládá.
Pro složitější objekty pak fasádu podědím a dopíši, co potřebuji.
Jednotlivé jednoduché fasády (web123\Model\Facade) jsou nakonfigurované
v neonu a vždy dostávají nějaké repository, které obhospodařují. Viz.
první služba, druhá služba má již vlastní fasádu.
carsFacade: web123\Model\Facade(
@doctrine.entityManager::getRepository('web123\Model\Food\CarRoute')
)
customerFacade: web123\Model\Food\CustomerFacade(
@doctrine.entityManager::getRepository('web123\Model\Customer')
)
S inject metodami mám zase smolíka a aby vše fungovalo, budu muset otrocky vytvářet pro každé sebehloupější repository vlastní fasádu. DRY and KISS jde k šípku.
- David Grudl
- Nette Core | 8228
Filip111 napsal(a):
…Asi to z prvního postu nevyplynulo – $this->context se chci zbavit každopádně…
…ale stejně jsem se nedozvěděl, proč byl NestedAccessor odstraněn. Byla to nějaká bad practise, presárna nebo to bylo pomalé nebo?
Stačí si uvědomit, že NestedAccessor === $this->context
- Filip Procházka
- Moderator | 4668
Filip111 napsal(a):
řešení, jen obcházení problému. Rozhodně to není čisté – když můžu předat jednu konkrétní službu, proč předávat celý FilterManager?
Je to naprosto validní řešení a nevidím důvod, proč by to nemělo být čisté.
Problém je v tom, že jsi nám dal málo informací, tak jsem ti nastínil obecné řešení. Kdyby jsi byl konkrétnější, mohl bych ti poradit ať to uděláš třeba podle Visitor nebo Strategy patternu.
Takový manager jak jsem ukázal je skutečně primitivní řešení a někomu by se mohlo zdát, že je to až service locator. Ale to je jen proto, že je to řešení bez kontextu.
Obcházení problému to také není. Je to jasný signál – když máš dvě implementace stejné věci, tak potřebuješ prostředníka, který vybere tu správnou, která se hodí.
cc @enumag
Pro složitější objekty pak fasádu podědím a dopíši, co potřebuji. Jednotlivé jednoduché fasády (web123\Model\Facade) jsou nakonfigurované v neonu a vždy dostávají nějaké repository, které obhospodařují. Viz. první služba, druhá služba má již vlastní fasádu.
V téhle chvíli je asi nejjednodušší řešení je podědit. Pokud potřebuješ více instancí stejného typu, můžeš si to implementovat jako obecnou factory, nebo vytvořit již zmiňovaný manager (který ale samozřejmě bude dělat víc věcí, než jen obálku na pole objektů).
Z pragmatických důvodů ale může být užitečné, aby šlo do presenteru injectnout jeden z více stejných typů… jak ty sám zmiňuješ ty facade. Nelíbí se mi to, ale budiž. Již padl návrh, že by měly presentery svoji sekci v configuraci, kde by sis do nich mohl předat závislosti. Rozhodně ale není čisté vybírat si jednu konkrétní implementaci přímo v presenteru, to se musí dít v Composition rootu, tedy pomocí konfigurace.