生成されるファクトリ

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

ファクトリとは、オブジェクトを作り、その依存関係を渡す役目を持つクラスです。*factory method* デザインパターンと混同しないでください。あちらはファクトリの特定の使い方を述べたもので、この話題とは関係がありません。

そうしたファクトリがどんな形になるかは、はじめの章で示しました。

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

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

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

interface ArticleFactory
{
	function create(): Article;
}

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

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

ファクトリを設定ファイルに足します。

services:
	- ArticleFactory

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

ファクトリを使うコードでは、インターフェースでそのオブジェクトを要求すれば、Nette DI が生成された実装を渡してくれます。

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

	public function foo()
	{
		// ファクトリにオブジェクトを作らせます
		$article = $this->articleFactory->create();
	}
}

パラメータ付きのファクトリ

ファクトリのメソッド create はパラメータを受け取り、それをコンストラクタに渡せます。たとえば Article クラスに記事の著者の ID を足してみましょう。

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

ファクトリにもパラメータを足します。

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

コンストラクタのパラメータ名($authorId)とファクトリのメソッドのパラメータ名が一致しているので、Nette 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 はいわゆるアクセサも生成できます。これは get() メソッドを持つオブジェクトで、DI コンテナから特定のサービスを返します。get() を繰り返し呼んでも常に同じインスタンスが返ります。

アクセサは依存関係の遅延読み込みを可能にします。エラーを専用のデータベースに記録するクラスを考えてみてください。このクラスがコンストラクタでデータベース接続を注入されるなら、エラーがめったに起きず接続がほとんど使われない場合でも、接続は常に確立されてしまいます。代わりにアクセサを受け取れば、データベースのオブジェクト(接続)はアクセサの get() メソッドが最初に呼ばれたときにはじめて作られます。

アクセサはどう作るのでしょうか。インターフェースを書くだけで、実装は Nette DI が生成します。インターフェースには get という名前のメソッドがちょうどひとつあり、パラメータを取らず、戻り値の型が宣言されている必要があります。

interface PDOAccessor
{
	function get(): PDO;
}

アクセサを、それが返すべきサービスの定義とともに設定ファイルに足します。

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

アクセサは PDO サービスを返し、設定にはそのサービスがひとつしか定義されていないので、アクセサはそのサービスを返します。その型のサービスが複数ある場合は、アクセサがどれを返すべきかを名前で指定してください。たとえば - PDOAccessor(@db1) です。

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

ここまでのファクトリとアクセサは、1 種類のオブジェクトしか作れず、また返せませんでした。しかしファクトリとアクセサの機能を組み合わせたマルチファクトリも簡単に作れます。そうした部品のインターフェースには、create<Name>()get<Name>() という名前のメソッドを複数書けます。たとえば次のようにです。

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

つまり個々のファクトリやアクセサをいくつも注入する代わりに、ひとつのより包括的な部品を注入できます。

複数のメソッドの代わりに、パラメータ付きの get() を使うこともできます。

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

このとき MultiFactory::getDb()MultiFactoryAlt::get('db') と同じことをします。ただしこの書き方には、$name に渡せる値がインターフェースのシグネチャから明らかでないという欠点があります。さらに、$name の値ごとに異なる戻り値の型をインターフェースで定義することもできません。

get($name) の代わりに、インターフェースは create($name) を宣言することもでき、こちらは呼ぶたびに新しいインスタンスを返します(get() は共有されたものを返します)。インターフェースに書けるパラメータ付きのメソッドはひとつだけです。メソッドの戻り値の型が nullable(たとえば ?PDO)なら、未知の $name に対して例外を投げる代わりに null を返します。

リストによる定義

設定では、サービスをその場に書くリストの形でマルチファクトリを定義できます。

services:
	- MultiFactory(
		article: Article()                    # createArticle() を定義します
		db: PDO(%dsn%, %user%, %password%)    # getDb() を定義します
	)

あるいは、マルチファクトリの定義の中で参照を使って既存のサービスを指すこともできます。

services:
	article: Article
	- PDO(%dsn%, %user%, %password%)
	- MultiFactory(
		article: @article    # createArticle() を定義します
		db: @\PDO            # getDb() を定義します
	)

タグによる定義

マルチファクトリを定義するもうひとつの方法はタグを使うことです。タグの値が対応するメソッドの名前を決めます。

services:
	article:
		create: Article
		tags: {multi: article}     # createArticle() を定義します
	db:
		create: PDO(%dsn%, %user%, %password%)
		tags: {multi: db}          # getDb() を定義します

	- MultiFactory(tagged: multi)
バージョン: 3.x