こんにちは。よっしーです(^^)
今日は、PHP-DIにおけるベストプラクティスについてご紹介します。
背景
PHP-DIに触れる機会がありましたので、PHP-DIにおけるベストプラクティスについて備忘として残しました。
詳細は下記の公式サイトをご覧ください。
はじめに
これは、PHP-DIと依存性注入を最適に使用するための主観的なガイドです。
すべてのケースをカバーしているわけではなく、すべての人に満足するわけでもありませんが、これは依存性注入の始め方を支援するためのキャンバスとして役立つでしょう。
この記事に説明されている内容に同意しない場合でも問題ありません。これは主観的なものであり、これらのテーマに関しては各自で自分なりの意見を持つべきです。これはPHP-DIをあなたが希望する方法で使用するのを妨げるものではありません。
コンテナと依存性注入の使用に関するルール
以下は、従うべき基本的なルールです。
- コンテナからエントリを直接取得しないでください(常に依存性注入を使用してください)
- 一般的には、コンテナから切り離されたコードを書いてください
- インターフェースに対して型ヒントを使用し、コンテナの設定で使用する実装を構成してください
コントローラの記述方法
コントローラで依存性注入を使用するのは通常、最も難しい部分です。
Symfony 2を例に挙げると(これは一般的にすべてのフレームワークに当てはまります)、以下のオプションがあります。
- コントローラにコンテナを注入し、$container->get(…)を呼び出す。
これは悪い方法であり、ルール番号1をご覧ください。
- コンストラクタに依存関係を注入する(Symfonyではサービスとしてのコントローラ)
これは、依存関係が5つ以上ある場合や、コンストラクタが15行のボイラープレートコードになる場合に痛みを伴います。
プロパティに依存関係を注入する これがお勧めする解決策です。例えば下記のようなコードになります。
class UserController
{
#[Inject]
private FormFactoryInterface $formFactory;
public function createForm($type, $data, $options)
{
// $this->formFactory->...
}
}
ご覧の通り、この解決策は非常に少ないコードを必要とし、理解しやすく、IDEのサポート(オートコンプリート、リファクタリングなど)を活用できます。
プロパティの注入は一般的には好まれませんし、それには理由があります:
- プライベートプロパティへの注入はカプセル化を破る
- 明示的な依存関係ではない:クラスがプロパティが設定されている必要があるという契約は存在しません
- 依存性を注入するためにPHP-DIの属性を使用すると、クラスはコンテナに依存している(上記の2番目のルールを参照)
ただし、アプリケーションの書き方に関する一般的なベストプラクティスに従う場合、コントローラにはビジネスロジックは含まれません(モデルへのルーティングコールとビューへの戻り値のバインディングのみです)。
したがって、
- ユニットテストを行わない(ただし、インターフェースに対する機能テストは行います)
- 他で再利用する必要はありません
- フレームワークを変更する場合、(Request、Response、テンプレートシステムなどのほとんどの依存関係が変更されるため)それを再度書き換える必要があるかもしれません
この解決策は大きな欠点がなく、多くの利点を提供するため、コントローラで属性を使用することをお勧めします。
サービスの記述方法
サービスは再利用され、テストされ、フレームワークに独立したものであるべきです。そのため、依存関係を注入するために属性を使用することはお勧めしません。
代わりに、コンストラクタの依存性注入と自動ワイヤリングを使用することをお勧めします。
class OrderService implements OrderServiceInterface
{
private $paymentService;
public function __construct(PaymentServiceInterface $paymentService)
{
$this->paymentService = $paymentService;
}
public function processOrder($order)
{
$this->paymentService->...
}
}
自動ワイヤリングを使用することで(デフォルトで有効です)、コンストラクタの各パラメータを設定でバインドする手間を省くことができます。PHP-DIはパラメータの型をチェックしてどのオブジェクトを注入する必要があるかを推測します。
一部の場合では、自動ワイヤリングだけでは不十分なことがあります。なぜなら、一部のパラメータがスカラー(文字列、整数など)だからです。この段階では、そのスカラーパラメータに何を注入するかを明示的に定義する必要があります。そのためには、次のどちらかの方法を使用できます。
- DI\create() を使用して、メソッド/クラスの全体の注入(つまり、すべてのパラメータ)を定義します。
<?php
// config.php
return [
// ...
OrderService::class => DI\create()
->constructor(DI\get(SomeOtherService::class), 'a value'),
];
- または、DI\autowire() を使用してスカラーパラメータだけを定義し、残りはPHP-DIが自動でワイヤリングするようにします。
<?php
// config.php
return [
// ...
OrderService::class => DI\autowire()
->constructorParameter('paramName', 'a value'),
];
この解決策は、すべてを再定義する必要がないため、一般的に好まれます。
余談: ルール番号3で説明したように、インターフェースに対する型ヒントをお勧めします。この場合、コンテナが使用する実装とインターフェースをマッピングするために、次のように設定でインターフェースを実装に関連付ける必要があります。
<?php
// config.php
return [
// ...
OrderServiceInterface::class => DI\get(OrderService::class),
];
ライブラリを使用する場合
ロガーやORMなどのライブラリを使用する際、必要に応じてこれらを設定する必要があります。
その場合、これらの依存関係を設定ファイルに定義することをお勧めします。また、設定がやや複雑になる場合は、匿名関数を使用することもお勧めします。
匿名関数を使用することで、実際のPHPコードを書くことができます。これは素晴らしいことです。ライブラリのドキュメントを使用でき、IDEのサポートも受けられるため、すでにPHPの開発者であるあなたにとっては言うまでもない利点です ;).
以下は、MonologというPHPのロガーを使用する例です。
<?php
// config.php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
return [
// ...
Psr\Log\LoggerInterface::class => DI\factory(function () {
$logger = new Logger('mylog');
$fileHandler = new StreamHandler('path/to/your.log', Logger::DEBUG);
$fileHandler->setFormatter(new LineFormatter());
$logger->pushHandler($fileHandler);
return $logger;
}),
];
ご覧の通り、依存性注入にはPSR-3インターフェースを使用しています。このようにして、この設定を変更するだけで、Monologを他のPSR-3ロガーにいつでも置き換えることができます。
おわりに
今日は、PHP-DIにおけるベストプラクティスについてご紹介しました。
何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。
それでは、また明日お会いしましょう(^^)
コメント