依存関係の受け渡し
引数(DI用語では「依存関係」)は、主に次のような方法でクラスに渡すことができます。
- コンストラクタによる引数渡し
- メソッド(セッターと呼ばれる)による引数渡し
- プロパティの設定によるもの
- メソッド、アノテーション、属性によるもの * インジェクションによるもの
ここでは、さまざまなバリエーションを具体的な例で説明します。
コンストラクタ・インジェクション
依存関係は、オブジェクトが作成されるときにコンストラクタに引数として渡されます。
class MyClass
{
private Cache $cache;
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
}
$obj = new MyClass($cache);
この形式は、クラスが機能するために絶対に必要な必須の依存関係を指定するのに便利です。なぜなら、その依存関係がなければインスタンスを作成することができないからです。
PHP 8.0 以降、機能的に同等な短い形式の記法を使用することができます。
// PHP 8.0
class MyClass
{
public function __construct(
private Cache $cache,
) {
}
}
PHP 8.1 以降では、プロパティにフラグreadonly
を指定して、
そのプロパティの内容を変更しないことを宣言することができます。
// PHP 8.1
class MyClass
{
public function __construct(
private readonly Cache $cache,
) {
}
}
DI コンテナは、自動配線によって依存関係をコンストラクタに渡します。この方法で渡すことができない引数(文字列、数値、ブール値など)は、設定に書きます。
コンストラクター地獄
コンストラクタ地獄とは、コンストラクタが依存性を要求する親クラスを子が継承し、その子も依存性を要求する状況を指す言葉である。また、親クラスの依存関係を引き継がなければなりません。
abstract class BaseClass
{
private Cache $cache;
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
}
final class MyClass extends BaseClass
{
private Database $db;
// ⛔ CONSTRUCTOR HELL
public function __construct(Cache $cache, Database $db)
{
parent::__construct($cache);
$this->db = $db;
}
}
問題は、新しい依存関係が追加されたときなど、BaseClass
クラスのコンストラクタを変更したいときに発生します。そうすると、子クラスのコンストラクタもすべて変更しなければならない。そのため、このような修正は地獄のようなものです。
これを防ぐには?解決策は、継承よりもコンポジションを優先させることだ。
そこで、コードを別の方法で設計してみよう。抽象的な
Base*
クラスは避けよう。MyClass
がBaseClass
を継承して機能を得る代わりに、その機能は依存関係として渡されます:
final class SomeFunctionality
{
private Cache $cache;
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
}
final class MyClass
{
private SomeFunctionality $sf;
private Database $db;
public function __construct(SomeFunctionality $sf, Database $db) // ✅
{
$this->sf = $sf;
$this->db = $db;
}
}
セッター・インジェクション
依存関係は、プライベート・プロパティに格納するメソッドを呼び出すことで渡されます。これらのメソッドの通常の命名規則は、set*()
という形式です。そのため、セッターと呼ばれていますが、もちろん、他の名前で呼ぶこともできます。
class MyClass
{
private Cache $cache;
public function setCache(Cache $cache): void
{
$this->cache = $cache;
}
}
$obj = new MyClass;
$obj->setCache($cache);
このメソッドは、オブジェクトが実際にそれを受け取る(つまり、ユーザーがメソッドを呼び出す)ことが保証されていないため、クラス機能に必要のないオプションの依存関係に対して便利です。
同時に、このメソッドによって、依存関係を変更するためにセッターを繰り返し呼び出すことができます。これが望ましくない場合は、メソッドにチェックを加えるか、
PHP 8.1 以降はreadonly
フラグでプロパティ$cache
をマークします。
class MyClass
{
private Cache $cache;
public function setCache(Cache $cache): void
{
if ($this->cache) {
throw new RuntimeException('The dependency has already been set');
}
$this->cache = $cache;
}
}
setter の呼び出しは、DI コンテナの設定セクション setup で定義します。また、ここでも autowiring による依存関係の自動的な受け渡しが行われています。
services:
- create: MyClass
setup:
- setCache
プロパティ・インジェクション
依存関係は直接プロパティに渡されます。
class MyClass
{
public Cache $cache;
}
$obj = new MyClass;
$obj->cache = $cache;
なぜなら、プロパティはpublic
として宣言されなければならないからです。
したがって、渡された依存関係が実際に指定された型になるかどうかを制御することはできませんし
(これは PHP 7.4
以前も同様でした)、新しく割り当てられた依存関係に独自のコードで対応する能力、
たとえばその後の変更を防止する能力も失われています。同時に、このプロパティはクラスのパブリックインターフェイスの一部となり、
望ましくないかもしれません。
変数の設定は、setupセクションのDIコンテナ構成で定義されます。
services:
- create: MyClass
setup:
- $cache = @\Cache
インジェクト
前の3つの方法は、一般的にすべてのオブジェクト指向言語で有効ですが、メソッド、アノテーション、inject属性による注入は、Netteプレゼンターに特有の方法です。これらは別章で説明します。
どの方法を選択するか?
- コンストラクタは、クラスが機能するために必要な必須の依存関係に対して適しています。
- 一方、セッターはオプションの依存関係、または変更可能な依存関係に適しています。
- パブリック変数は推奨しません。