?はじめに
こんにちは!開発チームの遠藤です?
DIコンテナライブラリであるPHP-DI導入において得られた知見を共有したいと思います!
本記事では、DIやDIコンテナ、PHP-DIの詳細な説明は割愛させて頂きます?
?導入背景
開発中のプロジェクト(PHP)では、DI(Dependency Injection)を多用しており、
インスタンス生成の責務を担ってくれるDIコンテナの導入を検討していました。
?選定基準と結果
主な考慮点は以下となります。
- DIコンテナのみの機能を提供
- PSR-11 Container Interfaceに準拠
- コミットが活発
上記を踏まえた上で、PHP-DIを選定しました!
PHP-DIはマイクロフレームワークであるSlimにも導入されており、実績も◎
?DIコンテナ導入前と後のコードの比較例
先ずは、簡単なコントローラクラスとサービスクラスを作ってみます。
<?php
class CustomerController
{
private $customerService;
public function __construct(CustomerService $customerService)
{
$this->customerService = $customerService;
}
public function getHello(): void
{
$this->customerService->getHello();
}
}
<?php
class CustomerService
{
public function getHello(): void
{
echo "hello";
}
}
続いて、上記で定義したクラスをインスタンス化して使用する処理を書いてみます。
DIコンテナを用いるパターンと用いないパターンがあります。
DIコンテナを用いないパターン
<?php
require "autoload.php";
$customerService = new CustomerService();
$customerController = new CustomerController($customerService);
$customerController->getHello();
DIコンテナを用いたパターン
<?php
require "autoload.php";
// DIコンテナをセットアップ
$builder = new \DI\ContainerBuilder();
$container = $builder->build();
$target = $container->get(CustomerController::class);
$target->getHello();
DIコンテナを用いるパターンでは、$container->get()
にクラス名を渡すだけで、インスタンスを返されるようになっています!クラス名を変数で持たせれば、Factoryパターンになります!
補足として、内部ではAutowiring(インスタンス依存関係を自動解決する機能)が働いており、
newを複数回する必要がなくなっています。
DIを用いたFWであるSpring BootやLaravel、NestJSも内部ではAutowiringが動いているようです。
?Autowiringを用いる際の注意点
Autowiringが動かないケースもあるので注意が必要です。
例として、以下が挙げられます。
<?php
class CustomerController
{
private $customerService;
// CommonServiceに依存に変更
public function __construct(CommonService $customerService)
{
$this->customerService = $customerService;
}
public function getHello(): void
{
$this->customerService->getHello();
}
}
<?php
class CommonService
{
private $id;
// コンストラクタ引数にインスタンス以外を持つ
public function __construct(string $id)
{
$this->id = $id;
}
public function getHello(): void
{
echo "hello, {$this->id}";
}
}
CommonService
のようなコンストラクタ引数にインスタンス以外を持つクラス依存がある場合に
上述のmain.php
を実行するとエラーが発生します。
$container->get()
にはクラス名しか与えておらず、CommonService
のコンストラクタ引数の$id
は、
どこにも宣言していないからです。
PHP-DIの内部では、依存クラスのコンストラクタ引数がクラス名であれば、autoloadでクラスファイルを割り出して、引数なしでnewするようです。
Autowiringを上手く動かすには、
コンストラクタ引数はインスタンスのみを宣言することが必要となります!
但し、PHP-DIには上記エラーの回避方法も用意されています。
?Autowiring以外のインスタンス定義
<?php
require "autoload.php";
$builder = new \DI\ContainerBuilder();
// 定義を追加
$builder->addDefinitions([
CommonService::class => function () {
return new CommonService("hoge");
},
]);
$container = $builder->build();
$target = $container->get(CustomerController::class);
$target->getHello();
上記のように、$builder->addDefinitions()
でクラス名に紐づくインスタンス生成の定義を渡してあげれば、エラーを解消することもできます。※ 定義がないクラス名はAutowiringが適用されます
マニュアルでの定義だと記述が増えてしまい、管理の手間が増えてしまうので、
Autowiringで動くクラス設計が個人的には大切だと考えています。
?さいごに
個人的には、PHP-DIでDIコンテナを導入したことで、インスタンス管理が行いやすくなったと考えています。また、SpringBoot等のFWが内部でどのようにDIを管理しているのかも想像しやすくなりました。PHP-DIはできることも多いので、気になる方は是非、公式ドキュメントをチェックしてみてください!