依存性の受け渡し
引数、またはDIの用語では「依存関係」は、次の主な方法でクラスに渡すことができます:
- コンストラクタによる受け渡し
- メソッド(いわゆるセッター)による受け渡し
- 変数の設定による受け渡し
- injectメソッド、アノテーション、または属性による受け渡し
次に、具体的な例で各バリアントを示します。
コンストラクタによる受け渡し
依存関係は、オブジェクトの作成時にコンストラクタの引数として渡されます:
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;
// ⛔ コンストラクタ地獄
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以降ではプロパティ$cache
をreadonly
フラグでマークします。
class MyClass
{
private Cache $cache;
public function setCache(Cache $cache): void
{
if (isset($this->cache)) {
throw new RuntimeException('The dependency has already been set');
}
$this->cache = $cache;
}
}
セッターの呼び出しは、DIコンテナの構成のsetupキーで定義します。ここでも、オートワイヤリングによる依存関係の自動受け渡しが利用されます:
services:
- create: MyClass
setup:
- setCache
変数の設定による受け渡し
依存関係は、メンバー変数に直接書き込むことによって渡されます:
class MyClass
{
public Cache $cache;
}
$obj = new MyClass;
$obj->cache = $cache;
この方法は、メンバー変数をpublic
として宣言する必要があるため、不適切と見なされます。したがって、渡された依存関係が実際に指定された型であること(PHP
7.4以前に適用)を制御できず、新しく割り当てられた依存関係に独自のコードで応答する可能性(たとえば、後続の変更を防ぐ)を失います。同時に、変数はクラスのパブリックインターフェースの一部となり、これは望ましくない場合があります。
変数の設定は、DIコンテナの構成のsetupセクションで定義します:
services:
- create: MyClass
setup:
- $cache = @\Cache
Inject
前の3つの方法はすべてのオブジェクト指向言語で一般的に適用されますが、injectメソッド、アノテーション、または属性による注入は、NetteのPresenterに特有のものです。別の章で説明されています。
どの方法を選択するか?
- コンストラクタは、クラスが機能するために不可欠な必須の依存関係に適しています
- セッターは、オプションの依存関係、または後で変更できる可能性のある依存関係に適しています
- public変数は適していません