Створення розширень для Nette DI

Створення контейнера DI на додаток до файлів конфігурації також впливає на так звані розширення. Ми активуємо їх у файлі конфігурації в секції extensions.

Так ми додаємо розширення, представлене класом BlogExtension з іменем blog:

extensions:
	blog: BlogExtension

Кожне розширення компілятора успадковує від Nette\DI\CompilerExtension і може реалізувати такі методи, які викликаються під час компіляції DI:

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

getConfigSchema()

Цей метод викликається першим. Він визначає схему, що використовується для перевірки параметрів конфігурації.

Розширення налаштовані в секції, ім'я якої збігається з ім'ям, під яким додано розширення, наприклад, blog.

# те саме ім'я, що й у розширення
blog:
	postsPerPage: 10
	comments: false

Ми визначимо схему, що описує всі параметри конфігурації, включно з їхніми типами, прийнятими значеннями і, можливо, значеннями за замовчуванням:

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

Див. Schema для отримання інформації. Крім того, можна вказати, які опції можуть бути динамічними за допомогою dynamic()', например `Expect::int()->dynamic().

Ми отримуємо доступ до конфігурації через $this->config, який є об'єктом stdClass:

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

loadConfiguration()

Цей метод використовується для додавання сервісів у контейнер. Це робиться за допомогою 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']) // або setCreator()
			->addSetup('setLogger', ['@logger']);
	}
}

Конвенція передбачає префіксування сервісів, доданих розширенням з його ім'ям, щоб не виникали конфлікти імен. Це робиться за допомогою prefix()', так что если расширение называется 'blog', то служба будет называться `blog.articles.

Якщо нам потрібно перейменувати сервіс, ми можемо створити псевдонім з його початковим ім'ям, щоб зберегти зворотну сумісність. Так само робить Nette для routing.router, який також доступний під ранньою назвою router.

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

Отримання сервісів із файлу

Ми можемо створювати сервіси за допомогою API ContainerBuilder, але також можемо додати їх через знайомий файл конфігурації NEON і його секцію services. Префікс @extension представляє поточне розширення.

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

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

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

Ми додамо сервіси таким чином:

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

		// завантажуємо файл конфігурації для розширення
		$this->compiler->loadDefinitionsFromConfig(
			$this->loadFromFile(__DIR__ . '/blog.neon')['services'],
		);
	}
}

beforeCompile()

Метод викликається, коли контейнер містить усі сервіси, додані окремими розширеннями в методах loadConfiguration, а також файли конфігурації користувача. На цьому етапі складання ми можемо змінювати визначення сервісу або додавати посилання між ними. Для пошуку сервісів за тегами можна використовувати метод findByTag(), або метод 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()

На цій стадії клас контейнера вже генерується як об'єкт ClassType, містить усі методи, які створюються сервісом, і готовий до кешування як файл PHP. Наразі ми можемо редагувати код класу.

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

$initialization

Configurator викликається кодом ініціалізації після створення контейнера, який створюється шляхом запису в об'єкт $this->initialization за допомогою методу addBody().

Ми покажемо приклад того, як запустити сесію або сервіси, які мають тег run за допомогою коду ініціалізації:

class BlogExtension extends Nette\DI\CompilerExtension
{
	public function loadConfiguration()
	{
		// автоматичний запуск сесії
		if ($this->config->session->autoStart) {
			$this->initialization->addBody('$this->getService("session")->start()');
		}

		// сервіси з тегом 'run' мають бути створені після того, як контейнер буде інстантовано
		$builder = $this->getContainerBuilder();
		foreach ($builder->findByTag('run') as $name => $foo) {
			$this->initialization->addBody('$this->getService(?);', [$name]);
		}
	}
}
версію: 3.x