f3l1x

Nette - jak zapisovat služby (extension / neon)

nette dependency injection neon

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:

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\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

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_998a370549

3. 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ž:

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: 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)

Options

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)

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)

esc

Type to search. ↑ ↓ to move, ↵ to open.