Jednoducha extension pro automaticky generovane tovarny komponent

- Jan Mikeš
 - Člen | 771
 
Zdravim, nemam moc zkusenosti se psanim extensions, tak bych se chtel zeptat vas znalejsich, jestli jsem nenapsal uplnou hovadinu..
Mam TemplateFactory, ktera mi generuje veskere sablony (presenteru, komponentam i maily) a dela par veci navic.
Cilem bylo automaticky dodavat factory do vsech komponent, bez nutnosti uvadet zavislosti v kazde tovarne a komponente.
namespace App\RenderableComponent;
use Nette,
	Nette\Configurator,
	Nette\DI\Compiler,
	Nette\DI\CompilerExtension;
if (!class_exists('Nette\DI\CompilerExtension')) {
	class_alias('Nette\Config\Compiler', 'Nette\DI\Compiler');
	class_alias('Nette\Config\Configurator', 'Nette\Configurator');
	class_alias('Nette\Config\CompilerExtension', 'Nette\DI\CompilerExtension');
}
final class Extension extends CompilerExtension
{
	const EXTENSION_NAME = "renderableComponent";
	public function beforeCompile()
	{
		$builder = $this->getContainerBuilder();
		$definitions = $builder->getDefinitions();
		$templateFactory = $builder->getDefinition("templateFactory");
		foreach ($definitions as $definition) {
			if (!$definition->implement || !method_exists($definition->implement, "create")) {
				continue;
			}
			$methodReflection = Nette\Reflection\Method::from($definition->implement, "create");
			$returnAnnotation = $methodReflection->getAnnotation("return");
			if ($returnAnnotation) {
				$classReflection = new Nette\Reflection\ClassType($returnAnnotation);
				if ($classReflection->implementsInterface("App\RenderableComponent\IComponent")) {
					$definition->addSetup("setTemplateFactory", array($templateFactory));
				}
			}
		}
	}
	public function install(Configurator $configurator)
	{
		$self = $this;
		$configurator->onCompile[] = function ($configurator, Compiler $compiler) use ($self) {
			$compiler->addExtension($self::EXTENSION_NAME, $self);
		};
	}
}
Vse funguje tak jak ma, jen me zajima jestli to dava smysl nebo to je uplna blbost a da se to udelat jinak?
V pripade zajmu muzu cely koncept zpristupnit.
Editoval Lexi (21. 2. 2014 19:37)

- enumag
 - Člen | 2118
 
Tohle řešení se mi líbí. Lze to použít i pro jiné služby než TemplateFactory které by komponenty mohly potřebovat v budoucnu jako generátor odkazů a ověřovač bezpečnostních anotací.
Hodíš to na GitHub?
EDIT: Neřeší totéž lépe inject metody/anotace?
Editoval enumag (24. 2. 2014 8:04)

- David Matějka
 - Moderator | 6445
 
@Lexi: a nebyl by jednodussi nejaky „inject enabler“? :) tedy ze
by to v beforeCompile proslo vsechny definice a
nastavilo ->inject = TRUE

- Jan Mikeš
 - Člen | 771
 
@matej21: kdyz nad tim tak premyslim, asi by to moc nepomohlo, protoze by mi to neumoznilo generovat automaticky factory services pomoci interfacu:
- App\Factories\ISomeControlFactory
Protoze bych si musel vytvaret pro kazdou tovarnu tridu, ktera by dedila
nejakou BaseFactory, ve ktere by byly inject metody a nakonec bych
vsechny zavislosti musel predavat rucne – v konecnem dusledku by to
znamenalo vice otravy a vice psani – tak jsem to mel puvodne a jsem rad, ze
jsem se toho zbavil.

- David Matějka
 - Moderator | 6445
 
@Lexi: proc? kdybys tam mel v tech komponentach injectTemplateFactory, tak by to melo nette autowirovat samo. (respektive to dela, sam to takhle pouzivam.. jedu ale na dev, kde je inject zapnuty pro vse)

- Jan Mikeš
 - Člen | 771
 
@matej21: to ano, nevim jestli se ale chapeme. Pokud pouziju automatickou tovarnu vygenerovanou z interfacu, pak mi staci tento kod:
<?php
interface ISomeControlFactory
{
    /** @return \App\Components\SomeControl */
    function create();
}
A tato extension zaridi, ze pokud trida controlu implementuje IComponent,
pak do ni dostane TemplateFactory
S tvym navrhem bych nemohl pouzit pouze interface ale musim si napsat sam tovarnu:
<?php
class SomeControlFactory extends BaseFactory
{
	// public $templateFactory; // tento mam jiz z baseFactory
	public $dependency;
	// konstruktor by se dal v vynechat a pouzivat ciste @inject
	public function __construct(AnotherDependency $dependency)
	{
		$this->dependency = $dependency;
	}
	function create()
	{
		return new SomeControl($this->templateFactory, $this->dependency);
	}
}
Rozdil je evidentni.
Pokud jsem te nepochopil tak me pls oprav.

- David Matějka
 - Moderator | 6445
 
mel bys factory
interface ISomeControlFactory
{
    /** @return \App\Components\SomeControl */
    function create();
}
a komponentu
class SomeControl extends BaseControl
{
	public function injectTemplateFactory(TemplateFactory $templateFactory)
	{
		$this->templateFactory = $templateFactory;
	}
}
(nebo by klidne ta metoda mohla byt v BaseControl, to uz je jedno)
pak staci na te sluzbe zapnout inject a nette to autowiruje
Editoval matej21 (25. 2. 2014 12:19)

- David Matějka
 - Moderator | 6445
 
@Lexi: ve stable funguji inject* metody a @inject anotace pouze
u presenteru. pokud to chces povilit i u sluzeb (a tedy i komponent,
respektive jejich tovarnicek), musis na dane sluzbe zapnout
inject: true. prave proto navrhuji onen „inject
enabler“ :)

- Jan Mikeš
 - Člen | 771
 
@mate21: aha, hmm, tak v tom pripade by mozna tve reseni bylo lepsi. Pokud tedy vysledna komponenta (ne jeji tovarna), bude mit inject metodu a ja pomoci extension zaridim ze na automaticky generovane factory bude zapnuty inject, tak opravdu tovarna po vytvoreni objektu na nem zavola vsechny injecty? Asi to dnes ve volnem case vyzkousim a dam vedet jak to dopadlo, protoze celou dobu jsem mel za to, ze injecty bezi max na urovni services.

- David Matějka
 - Moderator | 6445
 
@Lexi: jj ta vygenerovana tovarna potom vola vsechny injecty, kdyz se kouknes do vygenerovanyho containeru, tak muze vypadat treba takhle:
final class ... implements .....
{
	private $container;
	public function __construct(Nette\DI\Container $container)
	{
		$this->container = $container;
	}
	public function create()
	{
		$service = new ...\VoucherForm($this->container->getService('app.voucherDao'), $this->container->getService('app.cartStorage'));
		if (!$service instanceof ...\VoucherForm) {
			throw new Nette\UnexpectedValueException('Unable to create service \'app.voucherFormFactory\', value returned by factory is not ....\\VoucherForm type.');
		}
		$service->injectTemplateFactory($this->container->getService('hell.templateFactory'));
		...
		return $service;
	}
}
				
- Tomáš Votruba
 - Moderator | 1114
 
@Lexi: Takto ti to možná stačí:
foreach ($builder->definitions as $definition) {
	if ($definition->implement && method_exists($definition->implement, 'create')) {
		$definition->setInject(TRUE);
	}
}