Jaké jsou možnosti registrovat služby do dependency injection containeru
. Porovnání zápisů přes Config a přes Extension.
Nette\DI
a Nette\DI\Container
jsou, jak už všichni víme, skvělé třídy, které nám ušetří spoustu
a spoustu času. Jako fakt. Denně do DIC
registrujeme služby, načítáme configy, přidáváme parametry. Přes DIC vytváříme služby, továrničky
na služby, továrničky na cokoli...
Existují 2 základní postupy. Přes Config, zpravidla config.neon nebo přes Extension.
Pro jednoduché prototypování a přepisování již existujících služeb se více hodí registrovat přes Config.
Pro samostatné standalone rozšíření, komponenty, addony apod. je ideální použít vlastní Extension
Příklad můžeme vidět hned u Nette, které je rozdělené do samostatných balíčků jako například:
Většinou je dobré se držet principu konvence před konfigurací.
Pro účely snadného porovnání zápisů jsem vytvořil balíček planette/cookbook-dependency-injection.
Najdete tam zápisy přes neon a totožné přes extension. Můžete tak snadno porovnat, jestli děláte vše správně, případně narazit na postup, který neznáte (hlavně začátečníci).
Nástroj vytváří 2 containery a přes nette\tester
je porovnává.
NEON je velmi důmyslný nástroj, ve kterém lze snadno definovat vše co potřebujeme.
Potřebujeme službu?
services:
- App\MyService
Potřebujeme službě předat argumenty?
services:
- App\MyService(path/to/folder)
services:
my:
class: App\MyService
arguments: [path/to/folder]
Potřebujeme službu vytvořit s parametry?
services:
my:
class: App\MyService
parameters: [a]
arguments: [%a%]
services:
my:
class: App\MyService(%a%)
parameters: [a]
Extension nebo-li CompilerExtension nám poskytuje API, přes které dokážeme nadefinovat věškerou funkčnost.
Hlavním nástrojem je zde ContainerBuilder.
V extension ho získáme přes $this->getContainerBuilder()
.
Třída obsahuje 3 základní metody:
initialize
(pokud víte co děláte!)1. Jak vytvořit container
Nejjednodušeji container vytvoříte přes ContainerLoader takto:
$loader = new ContainerLoader('path/to/temp', $autoRebuild = TRUE);
$class = $loader->load('mycontainer', function (Compiler $compiler) {
$compiler->addExtension('my', new MyExtension());
//
$compiler->loadConfig('my.config.neon');
});
// Container_998a370549
$container = new $class;
2. Kde se bere název pro container
Může za to metoda ContainerLoader::getClassName($key), což v předchozím případě vrátí Container_998a370549.
$key = 'mycontainer';
$name = 'Container_' . substr(md5(serialize($key)), 0, 10);
// Container_998a370549
3. Kde se berou defaultní extensions
Nette definuje všechny defaultní extension v Configuratoru.
public $defaultExtensions = [
'php' => Nette\DI\Extensions\PhpExtension::class,
'constants' => Nette\DI\Extensions\ConstantsExtension::class,
'extensions' => Nette\DI\Extensions\ExtensionsExtension::class,
'application' => [Nette\Bridges\ApplicationDI\ApplicationExtension::class, ['%debugMode%', ['%appDir%'], '%tempDir%/cache']],
'decorator' => Nette\DI\Extensions\DecoratorExtension::class,
'cache' => [Nette\Bridges\CacheDI\CacheExtension::class, ['%tempDir%']],
'database' => [Nette\Bridges\DatabaseDI\DatabaseExtension::class, ['%debugMode%']],
'di' => [Nette\DI\Extensions\DIExtension::class, ['%debugMode%']],
'forms' => Nette\Bridges\FormsDI\FormsExtension::class,
'http' => Nette\Bridges\HttpDI\HttpExtension::class,
'latte' => [Nette\Bridges\ApplicationDI\LatteExtension::class, ['%tempDir%/cache/latte', '%debugMode%']],
'mail' => Nette\Bridges\MailDI\MailExtension::class,
'reflection' => [Nette\Bridges\ReflectionDI\ReflectionExtension::class, ['%debugMode%']],
'routing' => [Nette\Bridges\ApplicationDI\RoutingExtension::class, ['%debugMode%']],
'security' => [Nette\Bridges\SecurityDI\SecurityExtension::class, ['%debugMode%']],
'session' => [Nette\Bridges\HttpDI\SessionExtension::class, ['%debugMode%']],
'tracy' => [Tracy\Bridges\Nette\TracyExtension::class, ['%debugMode%']],
'inject' => Nette\DI\Extensions\InjectExtension::class,
];
4. Kdy se přegeneruje container
To záleží na konkrétním použití. Generování ovlivňuje název containeru a příznak autoRebuild.
K přegenerování dojde když:
autoRebuild
je TRUEPři vytváření containeru se vytvoří i soubor *.meta, který nese informaci o použitých souborech.
Pokud se nějaký soubor v průběhu změní, kontrolní součet nebude sedět a dojde k přegenerování.
Pokud používáte Nette\Configurator, tak ten vytváří název containeru podle parametrů a souborů (configů).
5. Neznámý počet parametrů
Tuto vlastnost využijeme hlavně v metodě CompilerExtension::afterCompile.
Uvažujme tento kód:
$initialize = $class->getMethod('initialize');
$initialize->addBody('My\\Tracy\\Bar::init(?);', [1, 2]);
$initialize->addBody('My\\Tracy\\Bar::init(?*);', [[1, 2]]);
Výstup bude vypadat takto:
public function initialize()
{
My\Tracy\Bar::init(1);
My\Tracy\Bar::init(1, 2);
}
Rozdíl je v placeholderu ?*
, který nám parametry expanduje jako pole argumentů.
Dalo by se to přirovnat k call_user_func_array
.
Kompletní přehled naleznete na GitHubu v repozitáři planette/cookbook-dependency-injection.
Tento přehled je pro Nette 2.3 a 2.4. Nette 3.0 má nějaké nové vychytávky.
NEON (code)
services:
a1: TestClass
a2:
class: TestClass
a3:
create: TestClass
Extension (code)
$builder = $this->getContainerBuilder();
$builder->addDefinition('a1')
->setClass('TestClass');
$builder->addDefinition('a2')
->setClass('TestClass');
$builder->addDefinition('a3')
->setFactory('TestClass');
Zkompilovaný kód (code)
NEON (code)
services:
b1:
class: TestClass
autowired: off
b2:
class: TestClass
inject: on
Extension (code)
$builder = $this->getContainerBuilder();
$builder->addDefinition('b1')
->setClass('TestClass')
->setAutowired(FALSE);
$builder->addDefinition('b2')
->setClass('TestClass')
->setInject(TRUE);
Zkompilovaný kód (code)
NEON (code)
services:
c1a: TestClass2(1, 2)
c1b:
class: TestClass2
arguments: [1, 2]
c2a: TestClass2(1)
c2b:
class: TestClass2
arguments: [a: 1]
c3a: TestClass2(b: 2)
c3b:
class: TestClass2
arguments: [b: 2]
Extension (code)
$builder = $this->getContainerBuilder();
$builder->addDefinition('c1a')
->setClass('TestClass2')
->setArguments([1, 2]);
$builder->addDefinition('c1b')
->setClass('TestClass2', [1, 2]);
$builder->addDefinition('c2a')
->setClass('TestClass2')
->setArguments([1]);
$builder->addDefinition('c2b')
->setClass('TestClass2', [1]);
$builder->addDefinition('c3a')
->setClass('TestClass2')
->setArguments(['b' => 2]);
$builder->addDefinition('c3b')
->setClass('TestClass2', ['b' => 2]);
Zkompilovaný kód (code)
NEON (code)
services:
d1:
class: TestClass
tags: [t1]
d2:
class: TestClass
tags: [t1: foobar]
Extension (code)
$builder = $this->getContainerBuilder();
$builder->addDefinition('d1')
->setClass('TestClass')
->addTag('t1');
$builder->addDefinition('d2')
->setClass('TestClass')
->setTags(['t1' => 'foobar']);
Zkompilovaný kód (code)
NEON (code)
services:
e1:
class: TestClass2
parameters: [a]
arguments: [%a%]
e2:
class: TestClass2
parameters: [a: NULL, b: 1]
arguments: [%a%, %b%]
e3:
class: TestClass2(%a%)
parameters: [a]
e4:
class: TestClass2(b: %a%)
parameters: [a]
Extension (code)
$builder = $this->getContainerBuilder();
// $->setClass()->setArguments() <==> $->setFactory()
$builder->addDefinition('e1')
->setClass('TestClass2')
->setArguments([$builder->literal('$a')])
->setParameters(['a']);
$builder->addDefinition('e2')
->setClass('TestClass2', [$builder->literal('$a'), $builder->literal('$b')])
->setParameters(['a' => NULL, 'b' => 1]);
$builder->addDefinition('e3')
->setClass('TestClass2')
->setArguments([$builder->literal('$a')])
->setParameters(['a']);
$builder->addDefinition('e4')
->setClass('TestClass2')
->setArguments([NULL, $builder->literal('$a')])
->setParameters(['a']);
Zkompilovaný kód (code)
NEON (code)
services:
f1:
implement: ITestInterface
f2:
class: stdClass
implement: ITestInterface
f3a:
implement: ITestInterface2
arguments: [1, 2]
f3b:
implement: ITestInterface2
arguments: [b: 2]
f4a:
implement: ITestInterface3
parameters: [c]
arguments: [%c%]
f4b:
implement: ITestInterface3
parameters: [c]
arguments: [1]
f5s: TestClass2
f5:
factory: @f5s
implement: ITestInterfaceGet
f6s:
class: TestClass
f6:
factory: @f6s
implement: ITestInterface
f7s:
class: TestClass2
f7:
factory: @f7s
implement: ITestInterface3
parameters: [c: 1]
arguments: [%c%]
Extension (code)
$builder = $this->getContainerBuilder();
$builder->addDefinition('f1')
->setImplement('ITestInterface');
$builder->addDefinition('f2')
->setClass('stdClass')
->setImplement('ITestInterface');
$builder->addDefinition('f3a')
->setImplement('ITestInterface2')
->setArguments([1, 2]);
$builder->addDefinition('f3b')
->setImplement('ITestInterface2')
->setArguments(['b' => 2]);
$builder->addDefinition('f4a')
->setImplement('ITestInterface3')
->setArguments([$builder->literal('$c')])
->setParameters(['c']);
$builder->addDefinition('f4b')
->setImplement('ITestInterface3')
->setArguments([1])
->setParameters(['c']);
$builder->addDefinition('f5s')
->setClass('TestClass2');
$builder->addDefinition('f5')
->setFactory('@f5s')
->setImplement('ITestInterfaceGet');
$builder->addDefinition('f6s')
->setClass('TestClass');
$builder->addDefinition('f6')
->setFactory('@f6s')
->setImplement('ITestInterface');
$builder->addDefinition('f7s')
->setClass('TestClass2');
$builder->addDefinition('f7')
->setFactory('@f7s')
->setImplement('ITestInterface3')
->setArguments([$builder->literal('$c')])
->setParameters(['c' => 1]);
Zkompilovaný kód (code)
NEON (code)
services:
g1:
class: TestClass2
parameters: [a: NULL, b: NULL]
arguments: [%a%, %b%]
g2: @g1
g3:
factory: @g1
arguments: [1]
g4:
factory: @g1
parameters: [b]
arguments: [b: %b%]
g5a:
class: stdClass
factory: @g1::foo()
g5b:
class: stdClass
factory: @g1::foo
parameters: [bar]
arguments: [%bar%]
g5c:
class: stdClass
factory: @g1::foo(%bar%)
parameters: [bar]
g5d:
class: stdClass
factory: @g1(%bar1%)::foo(%bar2%)
parameters: [bar1, bar2]
Extension (code)
$builder = $this->getContainerBuilder();
$builder->addDefinition('g1')
->setClass('TestClass2')
->setArguments([$builder->literal('$a'), $builder->literal('$b')])
->setParameters(['a' => NULL, 'b' => NULL]);
$builder->addDefinition('g2')
->setFactory('@g1');
$builder->addDefinition('g3')
->setFactory('@g1')
->setArguments([1]);
$builder->addDefinition('g4')
->setFactory('@g1')
->setArguments(['b' => $builder->literal('$b')])
->setParameters(['b']);
$builder->addDefinition('g5a')
->setClass('stdClass')
->setFactory('@g1::foo');
$builder->addDefinition('g5b')
->setClass('stdClass')
->setFactory('@g1::foo')
->setArguments([$builder->literal('$bar')])
->setParameters(['bar']);
$builder->addDefinition('g5c')
->setClass('stdClass')
->setFactory('@g1::foo', [$builder->literal('$bar')])
->setParameters(['bar']);
$builder->addDefinition('g5d')
->setClass('stdClass')
->setFactory(new Statement([
new Statement('@g1', [$builder->literal('$bar1')]),
'foo'
], [$builder->literal('$bar2')])
)->setParameters(['bar1', 'bar2']);
Zkompilovaný kód (code)
NEON (code)
services:
h1:
class: stdClass
setup:
- $a(1)
- [@self, $a](1)
- @self::$a(1)
- foo(1)
- [@self, foo](1)
- @self::foo(1)
h2:
class: stdClass
setup:
- "$service->hello(?)"(@h1)
- "$service->hi(?)"(@container)
- "My\\Tracy\\Bar::init(?)"(@self)
Extension (code)
$builder = $this->getContainerBuilder();
$builder->addDefinition('h1')
->setClass('stdClass')
->addSetup('$a', [1])
->addSetup(new Statement(['@self', '$a'], [1]))
->addSetup('@self::$a', [1])
->addSetup('foo', [1])
->addSetup(new Statement(['@self', 'foo'], [1]))
->addSetup('@self::foo', [1]);
$builder->addDefinition('h2')
->setClass('stdClass')
->addSetup(new Statement('$service->hello(?)', ['@h1']))
->addSetup(new Statement('$service->hi(?)', ['@container']))
->addSetup(new Statement('My\\Tracy\\Bar::init(?)', ['@self']));
}
Zkompilovaný kód (code)