こんにちは。よっしーです(^^)
今日は、PHP-DIにおけるinjectionの定義についてご紹介します。
背景
PHP-DIに触れる機会がありましたので、PHP-DIにおけるinjectionの定義について備忘として残しました。
詳細は下記の公式サイトをご覧ください。
はじめに
PHP-DIに何を注入し、どこに注入するかを知らせるために、いくつかのオプションがあります。
- オートワイヤリング(Autowiring)を使用する
- 属性(Attributes)を使用する
- PHP定義を使用する
- もし必要であれば、これらのオプションを同時に複数使用することもできます。
複数のソースを組み合わせる場合、適用される優先度があります。最高優先度から最低優先度までの順序は以下の通りです。
- コンテナ上の明示的な定義(つまり、$container->set() で定義されたもの)
- PHPファイルによる定義(複数の設定ファイルを追加する場合、最後のファイルが前のファイルのエントリを上書きできます)
- PHP属性
- オートワイヤリング
オートワイヤリング
オートワイヤリング(Autowiring)は、非常にシンプルなものを表す異名であり、コンテナが依存関係を自動的に作成して注入する能力を指します。
これを実現するために、PHP-DIはPHPのリフレクションを使用して、コンストラクタがどのパラメータを必要としているかを検出します。
オートワイヤリングは、コンテナのコンパイル時のパフォーマンスに影響を与えません。
以下の例を考えてみましょう:
class UserRepository
{
// ...
}
class UserRegistrationService
{
public function __construct(UserRepository $repository)
{
// ...
}
}
PHP-DIがUserRegistrationServiceを作成する必要がある場合、コンストラクタがUserRepositoryオブジェクトを必要としていること(型宣言を使用して検出)を検出します。
設定が何もない場合、PHP-DIはUserRepositoryのインスタンスを作成(まだ作成されていない場合)し、それをコンストラクタのパラメータとして渡します。同等の生のPHPコードは次のとおりです:
$repository = new UserRepository();
$service = new UserRegistrationService($repository);
想像している通り、これは非常にシンプルで、設定を必要とせず、単に動作します!
設定(Configuration)
オートワイヤリングはデフォルトで有効になっています。コンテナビルダーを使用して無効にすることができます:
$containerBuilder->useAutowiring(false);
制限(Limitations)
PHP-DIは次のような場合に解決できません:
class Database
{
public function __construct($dbHost, $dbPort)
{
// ...
}
public function setLogger(LoggerInterface $logger)
{
// ...
}
}
上記のようなクラスの場合、コンストラクタにどのパラメータを与えるべきか(オブジェクトの型宣言がないため)はわかりませんし、setLogger() メソッドも呼び出されません。これらのクラスについては、PHP定義で DI\autowire() を使用して、明示的に何を注入するかを宣言する必要があります。
PHP-DIは、Memcached、Gearman、Curl* クラスなど、PHPの内部クラスに対してもリフレクションを使用できません。SPLクラス(RecursiveIteratorIteratorなど)なども同様です。PHPエンジンまたは拡張機能が提供するクラスをインスタンス化する場合は、PHP-DIに明示的なコンストラクタパラメータを提供する必要があります。
属性(Attributes)
オートワイヤリングやPHP設定ファイルに加えて、PHP 8の属性を使用して注入を定義することもできます。
属性を使用しても、コンテナのコンパイル時のパフォーマンスに影響を与えることはありません。コンパイルされていないコンテナの場合、PHPのリフレクションが使用されますが、オーバーヘッドは最小限です。
セットアップ(Setup)
ContainerBuilderを介して属性を有効にします:
$containerBuilder->useAttributes(true);
この設定により、属性を使用して依存関係を定義できるようになります。属性を使用することで、クラスのプロパティに直接的に依存関係を定義できるため、コードがより読みやすくなることがあります。
Inject
#[Inject] は、何をどこに注入するかをPHP-DIに指示し、必要に応じて何を注入するかを定義するための属性です。
これは以下の場所で使用できます:
- コンストラクタ(コンストラクタインジェクション)
- メソッド(セッター/メソッドインジェクション)
- プロパティ(プロパティインジェクション)
注意:プロパティインジェクションはコンストラクタが実行された後に発生するため、__construct内では注入可能なプロパティはnullになります。
注意:#[Inject] は、phpdocで宣言された型を無視します。PHPコードで指定された型のみが考慮されます。
以下は、#[Inject] 属性のすべての可能な使用例です:
use DI\Attribute\Inject;
class Example
{
/**
* プロパティに型と組み合わせた属性の使用例:
*/
#[Inject]
private Foo $property1;
/**
* 注入するエントリを明示的に定義:
*/
#[Inject('db.host')]
private $property2;
/**
* 上記のものと同様:
*/
#[Inject(name: 'db.host')]
private $property3;
/**
* もちろん、コンストラクタは常に呼び出されますが、
* #[Inject] 属性はパラメータに使用して、
* 何を注入するかを指定できます。
*/
public function __construct(Foo $foo, #[Inject('db.host')] $dbHost)
{
}
/**
* #[Inject] は、メソッドを呼び出すようにPHP-DIに指示します。
* デフォルトでは、PHP-DIはサービスを注入するためにPHPの型を使用します。
*/
#[Inject]
public function method1(Foo $param)
{
}
/**
* パラメータレベルで#[Inject] を使用して、
* 何を注入するかを指定できます。
* 注意:#[Inject] は関数にも配置する必要があります。
*/
#[Inject]
public function method2(#[Inject('db.host')] $param)
{
}
/**
* 明示的なエントリの定義:
*/
#[Inject(['db.host', 'db.name'])]
public function method3($param1, $param2)
{
}
/**
* 名前によってパラメータを明示的に定義する
* (他のパラメータでは型が使用されます):
*/
#[Inject(['param2' => 'db.host'])]
public function method4(Foo $param1, $param2)
{
}
}
注意:属性クラスをインポートするために、use DI\Attribute\Inject; を記述することを忘れないでください。
属性のトラブルシューティング
- 属性クラスを use DI\Attribute\Inject; を使用してインポートすることを忘れないでください。
- #[Inject] は、Container::call() で呼び出すメソッドに使用することは意図されていません(無視されます)。
- #[Inject] は、phpdocで宣言された型を無視します。PHPコードで指定された型のみが考慮されます。
なお、#[Inject] はすべてのコンストラクタに対して暗黙的に適用されます(コンストラクタはオブジェクトを作成するために呼び出される必要があるため)。
Injectable 属性
#[Injectable] 属性を使用すると、注入可能なクラスにオプションを設定できます:
use DI\Attribute\Injectable;
#[Injectable(lazy: true)]
class Example
{
}
#[Injectable] 属性はオプションです。デフォルトでは、すべてのクラスは注入可能です。
制限事項
属性では定義できないことがいくつかあります:
- クラスではなく値の定義
- インターフェースと実装のマッピング
- 無名関数を使用したエントリの定義
そのため、これらの場合は属性とPHPでの定義を組み合わせることができます。
おわりに
今日は、PHP-DIにおけるコンテナの使用方法についてご紹介しました。
何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。
それでは、また明日お会いしましょう(^^)
コメント