章: 第1章: オブジェクト指向の深化
「テストのためにDBが必要」になっていませんか?
クラス内部で new DBConnection() を書いたとき、そのクラスは特定のDB実装と密結合します。テストのたびに本物のDBが必要になり、実行が遅く、不安定になります。
依存性の注入(DI)は、必要なオブジェクトを外から渡すことでこの問題を解消します。「作る責務」と「使う責務」を分けるのが核心です。
DIの3つの形
<?php
// コンストラクタ注入(最も推奨)
class OrderService {
public function __construct(
private readonly OrderRepository $repo
) {}
public function find(int $id): Order {
return $this->repo->findById($id);
}
}
// セッター注入(任意の依存を後から設定したいとき)
class NotificationService {
private ?Logger $logger = null;
public function setLogger(Logger $logger): void {
$this->logger = $logger;
}
}
// メソッド注入(メソッドごとに異なる依存が必要なとき)
class ReportGenerator {
public function generate(Formatter $formatter): string {
return $formatter->format($this->data);
}
}
| 注入方法 | 使いどころ | 注意点 |
| コンストラクタ注入 | ほぼすべてのケース | 必須の依存に使う |
| セッター注入 | 任意の依存 | オブジェクト生成後に依存が変わる場合 |
| メソッド注入 | リクエストごとに依存が変わる場合 | 使いすぎると設計が複雑になる |
チェックポイント: コンストラクタ注入が基本です。セッター・メソッド注入は本当に必要な場面に限定しましょう。
DI前後の比較
<?php
// NG: クラス内部でインスタンスを生成 → テストで差し替え不可
class OrderService {
private PdoOrderRepository $repo;
public function __construct() {
$this->repo = new PdoOrderRepository(new PDO('mysql:...'));
}
}
// OK: 外から注入 → テスト時にモックを渡せる
class OrderService {
public function __construct(private OrderRepository $repo) {}
}
// テストでの使用例
$mockRepo = $this->createMock(OrderRepository::class);
$service = new OrderService($mockRepo);
チェックポイント: 「このクラスのテストを書こうとしたとき、DBや外部APIのセットアップが必要になる」と感じたら、DIを使うタイミングです。
なぜDIが重要なのか
- テスタビリティ: 外部依存をモックに差し替えられるため、高速な単体テストが書ける
- 差し替え容易性: 実装(MySQL→PostgreSQL、メール→Slack)を切り替えてもビジネスロジックを変更不要
- 責務の明確化: 「依存を作る責務」と「依存を使う責務」が分離され、クラスがシンプルになる
まとめ & 次のステップ
- DIはクラス間の結合を緩くするための手法で、依存を外から渡すことが本質
- コンストラクタ注入が最も一般的で推奨される形式
- DIにより、テストでのモック差し替えと本番での実装切り替えが容易になる
次回はDIコンテナを学びます。依存関係が増えてきたとき、手動のDI管理を自動化する仕組みです。