DIコンテナとは?

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

驚かれるかもしれませんが、多くの場合、依存性注入(略してDI)の利点を活用するために依存性注入コンテナは必要ありません。導入章でも、DIの具体例を示しましたが、コンテナは必要ありませんでした。

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

前の章で、ArticleUserController クラスを紹介しました。どちらもデータベースと 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') は 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コンテナができました!そして、それを使用できます:

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

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

ご覧のとおり、DICを書くことは複雑ではありません。オブジェクト自体は、何らかのコンテナによって作成されていることを知らないという点を思い出す価値があります。その結果、PHPの任意のオブジェクトを、そのソースコードに介入することなくこのように作成することが可能です。

コンテナクラスの手動での作成とメンテナンスは、かなり早く悪夢になる可能性があります。したがって、次の章では、ほぼ自動的に生成および更新できるNette DIコンテナについて話します。

バージョン: 3.x