DI pro dynamicky připojované komponenty

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Aleš
Člen | 30
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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 | 1190
+
0
-

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..