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