生成されたファクトリー
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
)