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…
Jaké jsou možnosti
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:
- nette/security
- nette/caching
Většinou je dobré se držet principu konvence před konfigurací.
Zápisy
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
NEON je velmi důmyslný nástroj, ve kterém lze snadno definovat vše co potřebujeme.
Potřebujeme službu?
services:
- App\MyServicePotř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
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:
- loadConfiguration
- volá se v 1. kroku
- co dělat: registrovat a nastavovat svoje služby
- beforeCompile
- volá se ve 2. kroku
- co dělat: upravovat již zaregistrované služby (pokud víte co děláte!!!)
- afterCompile
- volá se ve 3. kroku
- co dělat: vkládat dekorující části PHP kódu, zejména do metody
initialize(pokud víte co děláte!)
Tipy a zajímavosti
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_998a3705493. Kde se berou defaultní extensions
Nette definuje všechny defaultní extension v Configuratoru.
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ž:
- se změní jméno
- dojde k expiraci a
autoRebuildje TRUE - upravíme zaindexovaný soubor
Př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, 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.
Přehled
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.
Simple
NEON (code)
services:
a1: TestClass
a2:
class: TestClass
a3:
create: TestClassExtension (code)
$builder = $this->getContainerBuilder();
$builder->addDefinition('a1')
->setClass('TestClass');
$builder->addDefinition('a2')
->setClass('TestClass');
$builder->addDefinition('a3')
->setFactory('TestClass');Zkompilovaný kód (code)
Options
NEON (code)
services:
b1:
class: TestClass
autowired: off
b2:
class: TestClass
inject: onExtension (code)
$builder = $this->getContainerBuilder();
$builder->addDefinition('b1')
->setClass('TestClass')
->setAutowired(FALSE);
$builder->addDefinition('b2')
->setClass('TestClass')
->setInject(TRUE);Zkompilovaný kód (code)
Arguments
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)
Zkompilovaný kód (code)
Tags
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)
Arguments + parameters
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)
Zkompilovaný kód (code)
Implements (interfaces)
NEON (code)
Extension (code)
Zkompilovaný kód (code)
References
NEON (code)
Extension (code)
Zkompilovaný kód (code)
Setup
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)
Zkompilovaný kód (code)