ネッテDIの拡張機能作成

設定ファイルに加えてDIコンテナを生成することは、いわゆるextensionsにも影響します。extensions セクションの設定ファイルでそれらを有効にします。

これは、クラスBlogExtension で表される拡張子を、名前blog で追加する方法です。

extensions:
	blog: BlogExtension

各コンパイラ拡張はNette\DI\CompilerExtension を継承し、DIコンパイル時に呼び出される以下のメソッドを実装することができます。

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

getConfigSchema()

このメソッドが最初に呼び出されます。これは、設定パラメータの検証に使用するスキーマを定義します。

拡張機能は、拡張機能が追加されたセクションと同じ名前のセクションで設定されます。例:blog.

# same name as my extension
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),
		]);
	}
}

ドキュメントはスキーマを参照してください。さらに、どのオプションがダイナミックに動作できるかは、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']) // or setCreator()
			->addSetup('setLogger', ['@logger']);
	}
}

名前の衝突が起こらないように、拡張機能によって追加されるサービスにはその名前をプレフィックスとして付けるという慣例があります。これはprefix() によって行われます。したがって、拡張機能が ‘blog’ と呼ばれている場合、そのサービスはblog.articles と呼ばれることになります。

サービスの名前を変更する必要がある場合、後方互換性を維持するために、元の名前でエイリアスを作成することができます。同様に、これはNetteが例えばrouting.router に対して行っていることで、router という以前の名前でも利用可能です。

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

ファイルからサービスを取得する

ContainerBuilder API を使ってサービスを作成することもできますが、おなじみの 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();

		// load the configuration file for the extension
		$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 は、コンテナ作成後に初期化コードを呼び出します。このコードは、メソッド addBody() を使用してオブジェクト$this->initialization に書き込むことで作成されます。

ここでは、初期化コードを用いて、セッションの開始や、run タグを持つサービスの開始を行う例を示します。

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

		// services with tag 'run' must be created after the container is instantiated
		$builder = $this->getContainerBuilder();
		foreach ($builder->findByTag('run') as $name => $foo) {
			$this->initialization->addBody('$this->getService(?);', [$name]);
		}
	}
}
version: 3.x