DIコンテナとは?

依存性注入コンテナ(DIC)とは、オブジェクトのインスタンス化と設定を行うことができるクラスです。

意外かもしれませんが、多くの場合、依存性注入(Dependency Injection、略してDI)を利用するために、依存性注入コンテナは必要ありません。なにしろ、前の章でもDIの具体例を示しましたが、コンテナは必要なかったのですから。

しかし、多数の異なるオブジェクトを多くの依存関係で管理する必要がある場合、依存性注入コンテナは本当に便利です。おそらく、フレームワーク上で構築されたWebアプリケーションの場合がそうでしょう。

前の章では、ArticleUserController というクラスを紹介しました。この2つのクラスは、データベースとファクトリArticleFactory という依存性を持っています。そして、これらのクラスのために、これからコンテナを作成します。もちろん、このような単純な例では、コンテナを作成する意味はありません。しかし、どのように見えるか、どのように動作するかを示すために、コンテナを作成します。

以下は、上記の例のためにハードコードされた簡単なコンテナです。

class Container
{
	public function createDatabase(): Nette\Database\Connection
	{
		return new Nette\Database\Connection('mysql:', 'root', '***');
	}

	public function createArticleFactory(): ArticleFactory
	{
		return new ArticleFactory($this->createDatabase());
	}

	public function createUserController(): UserController
	{
		return new UserController($this->createArticleFactory());
	}
}

使い方は次のようになります。

$container = new Container;
$controller = $container->createUserController();

コンテナにオブジェクトを要求するだけで、オブジェクトの作成方法や依存関係については何も知る必要はない。コンテナがすべてを知っているのだ。依存関係はコンテナが自動的に注入してくれる。それがコンテナの力です。

ここまでは、コンテナがすべてをハードコードしています。そこで次のステップでは、コンテナを本当に便利なものにするためのパラメータを追加します。

class Container
{
	public function __construct(
		private array $parameters,
	) {
	}

	public function createDatabase(): Nette\Database\Connection
	{
		return new Nette\Database\Connection(
			$this->parameters['db.dsn'],
			$this->parameters['db.user'],
			$this->parameters['db.password'],
		);
	}

	// ...
}

$container = new Container([
	'db.dsn' => 'mysql:',
	'db.user' => 'root',
	'db.password' => '***',
]);

勘のいい読者なら、ある問題にお気づきかもしれません。オブジェクトUserController を取得するたびに、新しいインスタンスArticleFactory とデータベースも作成されるのです。それは絶対に避けたい。

そこで、同じインスタンスを何度も何度も返すメソッドgetService() を追加する。

class Container
{
	private array $services = [];

	public function __construct(
		private array $parameters,
	) {
	}

	public function getService(string $name): object
	{
		if (!isset($this->services[$name])) {
			// getService('Database') calls createDatabase()
			$method = 'create' . $name;
			$this->services[$name] = $this->$method();
		}
		return $this->services[$name];
	}

	// ...
}

例えば$container->getService('Database') を最初に呼び出すと、createDatabase() がデータベースオブジェクトを生成し、それを配列$services に格納して、次の呼び出しでそれを直接返します。

また、コンテナの残りの部分を変更して、getService() を使用するようにします。

class Container
{
	// ...

	public function createArticleFactory(): ArticleFactory
	{
		return new ArticleFactory($this->getService('Database'));
	}

	public function createUserController(): UserController
	{
		return new UserController($this->getService('ArticleFactory'));
	}
}

ところで、サービスという言葉は、コンテナによって管理されるあらゆるオブジェクトを指します。そのため、メソッド名はgetService() 。

完了です。これで、完全に機能するDIコンテナが完成しました。これでDIコンテナが完成です!早速使ってみましょう。

$container = new Container([
	'db.dsn' => 'mysql:',
	'db.user' => 'root',
	'db.password' => '***',
]);

$controller = $container->getService('UserController');
$database = $container->getService('Database');

見てわかるように、DICを書くのは難しいことではありません。注目すべきは、オブジェクト自身はコンテナがそれらを作成していることを知らないということです。したがって、この方法で PHP の任意のオブジェクトを、そのソースコードに影響を与えずに作成することが可能です。

手動でコンテナクラスを作成し、維持することは、むしろすぐに悪夢となります。そこで、次の章では、ほぼ自動的に生成・更新できるNette DI Container について説明します。

{{maintitle:Dependency Injection Containerとは?}

version: 3.x