DI pro dynamicky připojované komponenty
- Aleš
- Člen | 30
DI je fajn, jenom mám menší problém s následujícím scénářem.
Mám modulární systém ve kterém dopředu nevím jaké komponenty budou
použity v právě zobrazovaném presenteru (ten definuje pouze přípojné
body.) Pokud komponenta potřebuje připojení k DB tak si ho mohla vytáhnout
přes Enviroment, momentálně nemůže, tak potřebuju nové řešení. Napadá
mě tohle
Porušit DI a vytáhnout si v presenteru celý DIContainer, přepsat si createComponent a udělat inject podobný jako ho teď dělá PresenterFactory
minus:
- pro použití služeb v presenteru budu muset přistupovat přes context (takže na první pohled nebudu přes setcontext vidět co používám)
plus:
- jako side effect se služby budou vytvářet až budou skutečně potřeba (týká se jenom presenteru)
- přehledné použití pro komponenty
Jenže to by znamenalo přepsat construct presenteru abych se dostal ke contextu (aniž bych si ho musel přepsat z private abych k němu mohl přistoupit)
Nějaké optimálnější řešení jak na tuto?
- Elijen
- Člen | 171
Nedavno jsem zkousel v BasePresenteru prepsat metodu
createComponent
tak, ze tovarnicky misto z presenteru vola
z Conteineru a celkem to funguje. Tento pristup by se ti mozna mohl hodit.
Je fakt, ze na to Nette mozna neni uplne stavene a mohlo se tim ledacos „rozbit“. Myslim, ze jsem narazil na nejakou zvlastnost v predavani parametru z presenteru, ale nakonec jsem to nejak ohl a dal neresil.
- Filip Procházka
- Moderator | 4668
Aleši, zkusil bych si presentery „generovat“.
class PresenterWriter extends Nette\Object
{
/** @var \Nette\Utils\PhpGenerator\ClassType */
private $class;
private $namespace;
public function __construct($name)
{
$class = ($i = strrpos($name, '\\') ? substr($name, $i + 1) : $name;
$this->namespace = ($i ? substr($name, 0, $i - 1) : NULL);
$this->class = new Nette\Utils\PhpGenerator\ClassType($class);
$this->class->addMethod('setContext');
}
public function addComponent($name, $service, $classType)
{
$property = $this->class->addProperty($service);
$property->visibility = 'private';
$injector = $this->class->methods['setContext'];
$injectorParam = $injector->addParameter($service);
$injectorParam->typeHint = $classType;
$injector->addBody('$this->? = $?;', array($service, $service));
$factory = $this->class->addMethod('createComponent' . ucFirst($name));
$factory->visibility = 'protected';
$factory->addBody('return $this->?;', array($service));
}
public function dump()
{
$s = 'namespace ' . $this->namespace . ";\n\n";
return $s . (string)$this->class;
}
}
A presenter si vytvoříš
$cache = new Nette\Caching\Cache($context->templateCacheStorage, 'Presenter');
$name = 'MyFooPresenter';
$loaded = $cache->load($name);
if ($loaded === NULL) {
$writer = new PresenterWriter($name);
foreach (array('foo', 'bar', 'baz') as $component) {
$classes = array_filter($context->classes, function ($v) use ($component) {
return $v === $component;
}); // najdeme všechny typy, které má komponenta
// poslední z nich by měla být samotná třída
$writer->addComponent($component, $component, end($classes));
}
$cache->save($name, $writer->dump());
$loaded = $cache->load($name);
}
require_once $loaded['file'];
Ale je to jenom nápad, asi to bude mít docela problémy s autowire, protože všechny komponenty mají stejný interface.
Možná by se to dalo dělat v afterCompile
nějakého
CompilerExtension
.
- Nox
- Člen | 378
Podle toho jak jsem pochopil původní otázku by to generování neřešilo, chápu to jako např.
<?php
protected function createComponentLoginForm($name) {
return new MyProject\UI\Forms\Login($this, $name, $this->context->authenticator);
}
?>
jenže: ->authenticator požaduje např. ->users, ->users požadují EM->getRepository ⇒ čímž se instancuje EntityManager a to není vůbec levná operace.
A jelikož se data použijí jen při odeslání, v success handleru,
iniciují se při vytváření neodeslaného formu zbytečně.
To by šlo možná řešit cachováním, u formulářů si ale nejsem jistý
… třeba proto, že aby člověk ověřil, jestli byl daný formulář
odeslaný, musí ho vytvořit. Čímž se to cachování jaksi zruší
Možná by šlo
<?php
protected function createComponentLoginForm($name) {
$form = new MyProject\UI\Forms\Login($this, $name);
if( $form->isSubmitted() )
$form->setAuthenticator($this->context->authenticator);
}
?>
ale nejsem si tím úplně jistý. Navíc ten authenticator je pro činnost formu nutný, takže by to v constructoru mělo být…
To by možná šlo pořešit přes anotace a nějakou společnou metodu, kde by handler měl napsané, které settery se před jeho spuštěním mají spustit
Editoval Nox (26. 12. 2011 11:38)
- Filip Procházka
- Moderator | 4668
Moje řešení počítá s tím, že komponenty budou v DIC :)
Ten authenticator by šel navazovat až v callbacku.
protected function createComponentLoginForm($name)
{
$form = new MyProject\UI\Forms\Login($this, $name);
$form->onSuccess[] = callback($this, 'LoginFormSubmitted');
return $form;
}
public function LoginFormSubmitted($form)
{
$form->setAuthenticator($this->context->authenticator);
$form->login();
}
- Aleš
- Člen | 30
Díky za nápady, nakonec zkusím rozvinout přístup ke kterému mě nakoplo řešení od Elijen. Co se kam připojuje budu definovat ve vlastním configu, který budu do hlavního inkludovat. V přípojných bodech budu definovat pouze komponentu, pro kterou vytvořím továrničku která už bude moc využít autowiring možností konfigurátoru. Vytváření by mělo jít upravit a pro náročnější závislosti využít lazyloadingu jak ho popisuje David.
- Felix
- Nette Core | 1245
Take ted resim neco podobneho. V aplikaci mam nekolik komponent a jejich
nastaveni v databazi, takove ovladani pres CMS(Settings). Resim jak efektivne
propojit nastaveni a komponenty. Nyni vyuzivam to, ze cele nastaveni serializuju
a ulozim do cache takze pak uz neprobihaji zadne dalsi dotazy. Nicmene, jak
efektivne|krasne vyresit zobrazovani komponent se mi zatim nepodarilo. Pri
predstave, ze mam 20 komponent a vsech budu delat
createComponentXyz()
nebo neco podobneho, neni moc lichotiva. Jak
nacist a zobrazit komponenty, ktere chci, s prislusnym nastavenim
(z db->cache) ?? To generovani presenteru se mi take moc nelibi..