Jednoducha extension pro automaticky generovane tovarny komponent

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

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

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)

Jan Mikeš
Člen | 771
+
0
-

Pres inject metody jsem to puvodne mel, ale u kazde factory v configu jsem musel uvadet inject: yes a to me celkem obtezovalo, protoze config pak pri vetsim poctu komponent a jejich tovaren dost nabobtnaval.

Zitra updatnu cely sandbox a placnu to na github.

David Matějka
Moderator | 6445
+
0
-

@Lexi: a nebyl by jednodussi nejaky „inject enabler“? :) tedy ze by to v beforeCompile proslo vsechny definice a nastavilo ->inject = TRUE

castamir
Člen | 629
+
0
-

dotaz: getByType nevrací služby podle rozhraní? tím by se to podle mě dost zjednodušilo…

Jan Mikeš
Člen | 771
+
0
-

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

Jan Mikeš
Člen | 771
+
0
-

@castamir: jasne, ale jednotlive komponenty nejsou registrovane jako sluzby, nejsou v containeru, pouze jejich tovarnicky, nebo premyslis nad necim jinym?

David Matějka
Moderator | 6445
+
0
-

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

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

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)

Jan Mikeš
Člen | 771
+
0
-

@matej21: aha, a autowirovani na urovni komponent funguje i ve stable, nebo pouze dev verzi? ja mel za to, ze injecty jsou pouze pro presentery a servicy zaregistrovane v configu.

David Matějka
Moderator | 6445
+
0
-

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

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

@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;
	}

}
Jan Mikeš
Člen | 771
+
0
-

Parada, tim padem to prepisu na „inject enabler“, libi se mi to vice

Tomáš Votruba
Moderator | 1114
+
0
-

@Lexi: Takto ti to možná stačí:

foreach ($builder->definitions as $definition) {
	if ($definition->implement && method_exists($definition->implement, 'create')) {
		$definition->setInject(TRUE);
	}
}