Nette Documentation Preview

syntax
Tworzenie rozszerzeń dla Nette DI
*********************************

.[perex]
Na generowanie kontenera DI oprócz plików konfiguracyjnych wpływają również tzw. *rozszerzenia*. Aktywujemy je w pliku konfiguracyjnym w sekcji `extensions`.

W ten sposób dodajemy rozszerzenie reprezentowane przez klasę `BlogExtension` pod nazwą `blog`:

```neon
extensions:
	blog: BlogExtension
```

Każde rozszerzenie kompilatora dziedziczy po [api:Nette\DI\CompilerExtension] i może implementować następujące metody, które są kolejno wywoływane podczas budowania kontenera DI:

1. getConfigSchema()
2. loadConfiguration()
3. beforeCompile()
4. afterCompile()


getConfigSchema() .[method]
===========================

Ta metoda jest wywoływana jako pierwsza. Definiuje schemat do walidacji parametrów konfiguracyjnych.

Rozszerzenie konfigurujemy w sekcji, której nazwa jest taka sama jak ta, pod którą rozszerzenie zostało dodane, czyli `blog`:

```neon
# ta sama nazwa co rozszerzenie
blog:
	postsPerPage: 10
	allowComments: false
```

Tworzymy schemat opisujący wszystkie opcje konfiguracyjne, w tym ich typy, dozwolone wartości i ewentualnie wartości domyślne:

```php
use Nette\Schema\Expect;

class BlogExtension extends Nette\DI\CompilerExtension
{
	public function getConfigSchema(): Nette\Schema\Schema
	{
		return Expect::structure([
			'postsPerPage' => Expect::int(),
			'allowComments' => Expect::bool()->default(true),
		]);
	}
}
```

Dokumentację znajdziesz na stronie [Schema |schema:]. Dodatkowo można określić, które opcje mogą być [dynamiczne|application:bootstrap#Parametry dynamiczne] za pomocą `dynamic()`, np. `Expect::int()->dynamic()`.

Do konfiguracji dostajemy się przez zmienną `$this->config`, która jest obiektem `stdClass`:

```php
class BlogExtension extends Nette\DI\CompilerExtension
{
	public function loadConfiguration()
	{
		$num = $this->config->postPerPage;
		if ($this->config->allowComments) {
			// ...
		}
	}
}
```


loadConfiguration() .[method]
=============================

Służy do dodawania usług do kontenera. Do tego służy [api:Nette\DI\ContainerBuilder]:

```php
class BlogExtension extends Nette\DI\CompilerExtension
{
	public function loadConfiguration()
	{
		$builder = $this->getContainerBuilder();
		$builder->addDefinition($this->prefix('articles'))
			->setFactory(App\Model\HomepageArticles::class, ['@connection']) // lub setCreator()
			->addSetup('setLogger', ['@logger']);
	}
}
```

Konwencją jest prefiksowanie usług dodanych przez rozszerzenie jego nazwą, aby nie dochodziło do konfliktów nazw. Robi to metoda `prefix()`, więc jeśli rozszerzenie nazywa się `blog`, usługa będzie nosić nazwę `blog.articles`.

Jeśli potrzebujemy zmienić nazwę usługi, możemy ze względu na zachowanie wstecznej kompatybilności utworzyć alias z pierwotną nazwą. Podobnie robi Nette np. w przypadku usługi `routing.router`, która jest dostępna również pod wcześniejszą nazwą `router`.

```php
$builder->addAlias('router', 'routing.router');
```


Ładowanie usług z pliku
-----------------------

Usługi możemy tworzyć nie tylko za pomocą API klasy ContainerBuilder, ale także znanym zapisem używanym w pliku konfiguracyjnym NEON w sekcji services. Prefiks `@extension` reprezentuje bieżące rozszerzenie.

```neon
services:
	articles:
		create: MyBlog\ArticlesModel(@connection)

	comments:
		create: MyBlog\CommentsModel(@connection, @extension.articles)

	articlesList:
		create: MyBlog\Components\ArticlesList(@extension.articles)
```

Usługi wczytamy:

```php
class BlogExtension extends Nette\DI\CompilerExtension
{
	public function loadConfiguration()
	{
		$builder = $this->getContainerBuilder();

		// wczytanie pliku konfiguracyjnego dla rozszerzenia
		$this->compiler->loadDefinitionsFromConfig(
			$this->loadFromFile(__DIR__ . '/blog.neon')['services'],
		);
	}
}
```


beforeCompile() .[method]
=========================

Metoda jest wywoływana w momencie, gdy kontener zawiera wszystkie usługi dodane przez poszczególne rozszerzenia w metodach `loadConfiguration` oraz przez użytkownika w plikach konfiguracyjnych. Na tym etapie budowania możemy więc modyfikować definicje usług lub uzupełniać powiązania między nimi. Do wyszukiwania usług w kontenerze według tagów można użyć metody `findByTag()`, a według klasy lub interfejsu metody `findByType()`.

```php
class BlogExtension extends Nette\DI\CompilerExtension
{
	public function beforeCompile()
	{
		$builder = $this->getContainerBuilder();

		foreach ($builder->findByTag('logaware') as $serviceName => $tagValue) {
			$builder->getDefinition($serviceName)->addSetup('setLogger');
		}
	}
}
```


afterCompile() .[method]
========================

Na tym etapie klasa kontenera jest już wygenerowana w postaci obiektu [ClassType |php-generator:#klasy], zawiera wszystkie metody tworzące usługi i jest gotowa do zapisu do cache. Wynikowy kod klasy możemy na tym etapie jeszcze zmodyfikować.

```php
class BlogExtension extends Nette\DI\CompilerExtension
{
	public function afterCompile(Nette\PhpGenerator\ClassType $class)
	{
		$method = $class->getMethod('__construct');
		// ...
	}
}
```


$initialization .[method]
=========================

Klasa Configurator po [utworzeniu kontenera |application:bootstrap#index.php] wywołuje kod inicjalizacyjny, który tworzy się zapisem do obiektu `$this->initialization` za pomocą [metody addBody() |php-generator:#ciała-metod-i-funkcji].

Pokażemy przykład, jak na przykład kodem inicjalizacyjnym uruchomić sesję lub uruchomić usługi, które mają tag `run`:

```php
class BlogExtension extends Nette\DI\CompilerExtension
{
	public function loadConfiguration()
	{
		// automatyczne uruchamianie sesji
		if ($this->config->session->autoStart) {
			$this->initialization->addBody('$this->getService("session")->start()');
		}

		// usługi z tagiem run muszą być utworzone po instancjonowaniu kontenera
		$builder = $this->getContainerBuilder();
		foreach ($builder->findByTag('run') as $name => $foo) {
			$this->initialization->addBody('$this->getService(?);', [$name]);
		}
	}
}
```

Tworzenie rozszerzeń dla Nette DI

Na generowanie kontenera DI oprócz plików konfiguracyjnych wpływają również tzw. rozszerzenia. Aktywujemy je w pliku konfiguracyjnym w sekcji extensions.

W ten sposób dodajemy rozszerzenie reprezentowane przez klasę BlogExtension pod nazwą blog:

extensions:
	blog: BlogExtension

Każde rozszerzenie kompilatora dziedziczy po Nette\DI\CompilerExtension i może implementować następujące metody, które są kolejno wywoływane podczas budowania kontenera DI:

  1. getConfigSchema()
  2. loadConfiguration()
  3. beforeCompile()
  4. afterCompile()

getConfigSchema()

Ta metoda jest wywoływana jako pierwsza. Definiuje schemat do walidacji parametrów konfiguracyjnych.

Rozszerzenie konfigurujemy w sekcji, której nazwa jest taka sama jak ta, pod którą rozszerzenie zostało dodane, czyli blog:

# ta sama nazwa co rozszerzenie
blog:
	postsPerPage: 10
	allowComments: false

Tworzymy schemat opisujący wszystkie opcje konfiguracyjne, w tym ich typy, dozwolone wartości i ewentualnie wartości domyślne:

use Nette\Schema\Expect;

class BlogExtension extends Nette\DI\CompilerExtension
{
	public function getConfigSchema(): Nette\Schema\Schema
	{
		return Expect::structure([
			'postsPerPage' => Expect::int(),
			'allowComments' => Expect::bool()->default(true),
		]);
	}
}

Dokumentację znajdziesz na stronie Schema. Dodatkowo można określić, które opcje mogą być dynamiczne za pomocą dynamic(), np. Expect::int()->dynamic().

Do konfiguracji dostajemy się przez zmienną $this->config, która jest obiektem stdClass:

class BlogExtension extends Nette\DI\CompilerExtension
{
	public function loadConfiguration()
	{
		$num = $this->config->postPerPage;
		if ($this->config->allowComments) {
			// ...
		}
	}
}

loadConfiguration()

Służy do dodawania usług do kontenera. Do tego służy Nette\DI\ContainerBuilder:

class BlogExtension extends Nette\DI\CompilerExtension
{
	public function loadConfiguration()
	{
		$builder = $this->getContainerBuilder();
		$builder->addDefinition($this->prefix('articles'))
			->setFactory(App\Model\HomepageArticles::class, ['@connection']) // lub setCreator()
			->addSetup('setLogger', ['@logger']);
	}
}

Konwencją jest prefiksowanie usług dodanych przez rozszerzenie jego nazwą, aby nie dochodziło do konfliktów nazw. Robi to metoda prefix(), więc jeśli rozszerzenie nazywa się blog, usługa będzie nosić nazwę blog.articles.

Jeśli potrzebujemy zmienić nazwę usługi, możemy ze względu na zachowanie wstecznej kompatybilności utworzyć alias z pierwotną nazwą. Podobnie robi Nette np. w przypadku usługi routing.router, która jest dostępna również pod wcześniejszą nazwą router.

$builder->addAlias('router', 'routing.router');

Ładowanie usług z pliku

Usługi możemy tworzyć nie tylko za pomocą API klasy ContainerBuilder, ale także znanym zapisem używanym w pliku konfiguracyjnym NEON w sekcji services. Prefiks @extension reprezentuje bieżące rozszerzenie.

services:
	articles:
		create: MyBlog\ArticlesModel(@connection)

	comments:
		create: MyBlog\CommentsModel(@connection, @extension.articles)

	articlesList:
		create: MyBlog\Components\ArticlesList(@extension.articles)

Usługi wczytamy:

class BlogExtension extends Nette\DI\CompilerExtension
{
	public function loadConfiguration()
	{
		$builder = $this->getContainerBuilder();

		// wczytanie pliku konfiguracyjnego dla rozszerzenia
		$this->compiler->loadDefinitionsFromConfig(
			$this->loadFromFile(__DIR__ . '/blog.neon')['services'],
		);
	}
}

beforeCompile()

Metoda jest wywoływana w momencie, gdy kontener zawiera wszystkie usługi dodane przez poszczególne rozszerzenia w metodach loadConfiguration oraz przez użytkownika w plikach konfiguracyjnych. Na tym etapie budowania możemy więc modyfikować definicje usług lub uzupełniać powiązania między nimi. Do wyszukiwania usług w kontenerze według tagów można użyć metody findByTag(), a według klasy lub interfejsu metody findByType().

class BlogExtension extends Nette\DI\CompilerExtension
{
	public function beforeCompile()
	{
		$builder = $this->getContainerBuilder();

		foreach ($builder->findByTag('logaware') as $serviceName => $tagValue) {
			$builder->getDefinition($serviceName)->addSetup('setLogger');
		}
	}
}

afterCompile()

Na tym etapie klasa kontenera jest już wygenerowana w postaci obiektu ClassType, zawiera wszystkie metody tworzące usługi i jest gotowa do zapisu do cache. Wynikowy kod klasy możemy na tym etapie jeszcze zmodyfikować.

class BlogExtension extends Nette\DI\CompilerExtension
{
	public function afterCompile(Nette\PhpGenerator\ClassType $class)
	{
		$method = $class->getMethod('__construct');
		// ...
	}
}

$initialization

Klasa Configurator po utworzeniu kontenera wywołuje kod inicjalizacyjny, który tworzy się zapisem do obiektu $this->initialization za pomocą metody addBody().

Pokażemy przykład, jak na przykład kodem inicjalizacyjnym uruchomić sesję lub uruchomić usługi, które mają tag run:

class BlogExtension extends Nette\DI\CompilerExtension
{
	public function loadConfiguration()
	{
		// automatyczne uruchamianie sesji
		if ($this->config->session->autoStart) {
			$this->initialization->addBody('$this->getService("session")->start()');
		}

		// usługi z tagiem run muszą być utworzone po instancjonowaniu kontenera
		$builder = $this->getContainerBuilder();
		foreach ($builder->findByTag('run') as $name => $foo) {
			$this->initialization->addBody('$this->getService(?);', [$name]);
		}
	}
}