生成されたファクトリー

Nette DIは、インターフェイスを元にファクトリーコードを自動生成することができるので、コードを書く手間が省けます。

ファクトリーは、オブジェクトを作成し、設定するクラスです。したがって、その依存関係も同様に渡します。ファクトリーメソッド*デザインパターンと混同しないように注意してください。

そんな工場の姿を序章で紹介しました:

class ArticleFactory
{
	public function __construct(
		private Nette\Database\Connection $db,
	) {
	}

	public function create(): Article
	{
		return new Article($this->db);
	}
}

Nette DIはファクトリーコードを自動生成することができます。インターフェイスを作成するだけで、Nette DIが実装を生成してくれます。インターフェースは、create という名前のメソッドを1つだけ持ち、戻り値の型を宣言する必要があります。

interface ArticleFactory
{
	function create(): Article;
}

つまり、ファクトリーArticleFactory は、オブジェクトを作成するメソッドcreate を持っていますArticle 。クラスArticle は、たとえば次のようなものになるでしょう。

class Article
{
	public function __construct(
		private Nette\Database\Connection $db,
	) {
	}
}

設定ファイルにファクトリーを追加します。

services:
	- ArticleFactory

Nette DIは、対応するファクトリーの実装を生成します。

このように、ファクトリーを利用するコードでは、インターフェースでオブジェクトを要求し、生成された実装をネットDIが利用します。

class UserController
{
	public function __construct(
		private ArticleFactory $articleFactory,
	) {
	}

	public function foo()
	{
		// let the factory create an object
		$article = $this->articleFactory->create();
	}
}

パラメタライズドファクトリー

ファクトリーメソッドcreate はパラメータを受け取ることができ、それをコンストラクタに渡します。例えば、記事の著者IDをクラスArticle に追加してみましょう。

class Article
{
	public function __construct(
		private Nette\Database\Connection $db,
		private int $authorId,
	) {
	}
}

また、ファクトリーにパラメータを追加します。

interface ArticleFactory
{
	function create(int $authorId): Article;
}

コンストラクタのパラメータとファクトリーのパラメータは同じ名前なので、ネットDIは自動的にこれらを渡します。

高度な定義

定義は、キーimplement を使って複数行で記述することも可能です。

services:
	articleFactory:
		implement: ArticleFactory

この長い書き方では,通常のサービスと同様に,コンストラクタの引数をarguments で,設定をsetup で追加することが可能です.

例:メソッドcreate() がパラメータ$authorId を受け付けない場合,コンストラクタArticle に渡される固定値を設定に指定することができる.

services:
	articleFactory:
		implement: ArticleFactory
		arguments:
			authorId: 123

あるいは逆に、create() がパラメータ$authorId を受け付けたが、それがコンストラクタの一部ではなく、メソッドArticle::setAuthorId() から渡された場合、セクションsetup でそれを参照することになる。

services:
	articleFactory:
		implement: ArticleFactory
		setup:
			- setAuthorId($authorId)

アクセッサー

Netteではファクトリーの他に、いわゆるアクセサーを生成することができます。アクセサーとは、DIコンテナから特定のサービスを返すget() メソッドを持つオブジェクトです。get() を複数回呼び出すと、常に同じインスタンスが返されます。

アクセッサは、依存関係にレイジーローディングをもたらします。例えば、エラーを特殊なデータベースに記録するクラスがあるとします。もしデータベース接続をコンストラクタの依存関係として渡した場合、 接続は常に作成する必要がありますが、エラーが発生したときにしか使われないので、 接続はほとんど使われないままです。 その代わりに、このクラスはアクセサを渡すことができ、そのget() メソッドが呼ばれたときだけ、データベースオブジェクトが作成されます。

アクセサを作るには?インターフェイスを書くだけで、ネットDIが実装を生成してくれます。インターフェイスは、get というメソッドを1つだけ持ち、戻り値の型を宣言しなければなりません。

interface PDOAccessor
{
	function get(): PDO;
}

アクセッサが返すサービスの定義と一緒に、設定ファイルにアクセッサを追加してください。

services:
	- PDOAccessor
	- PDO(%dsn%, %user%, %password%)

このアクセサはPDO というタイプのサービスを返します。 このようなサービスは設定に1つしかないので、アクセサはそれを返します。このタイプのサービスが複数設定されている場合、どのサービスを返すかをその名前を使って指定できます。たとえば、- PDOAccessor(@db1) 。

マルチファクトリー/アクセサー

今までのファクトリーとアクセッサは、1つのオブジェクトを作成するか返すだけでした。アクセッサと組み合わせたマルチファクトリーも作成することができます。このようなマルチファクトリークラスのインターフェイスは create<name>()get<name>()と呼ばれる複数のメソッドで構成されます。

interface MultiFactory
{
	function createArticle(): Article;
	function getDb(): PDO;
}

生成された複数のファクトリーとアクセッサを渡す代わりに、複雑なマルチファクトリーを1つだけ渡すことができます。

あるいは、複数のメソッドの代わりに、get() をパラメータ付きで使うこともできる:

interface MultiFactoryAlt
{
	function get($name): PDO;
}

この場合、MultiFactory::getArticle()MultiFactoryAlt::get('article') と同じことをする。 しかし、この代替構文にはいくつかの欠点がある。どの$name 値がサポートされているのかが明確でないことと、複数の異なる$name 値を使用する場合、インターフェイスで戻り値の型を指定できないことである。

リストを使った定義

この方法は、コンフィギュレーションで複数のファクトリーを定義するのに使える:.{data-version:3.2.0}

services:
	- MultiFactory(
		article: Article                      # defines createArticle()
		db: PDO(%dsn%, %user%, %password%)    # defines getDb()
	)

あるいは、ファクトリーの定義で、参照を使用して既存のサービスを参照できます:

services:
	article: Article
	- PDO(%dsn%, %user%, %password%)
	- MultiFactory(
		article: @article    # defines createArticle()
		db: @\PDO            # defines getDb()
	)

タグ付き定義

マルチファクトリーを定義するもう一つの方法は、タグを使うことです。

services:
	- App\Core\RouterFactory::createRouter
	- App\Model\DatabaseAccessor(
		db1: @database.db1.explorer
	)
version: 3.x