この記事では、Laravelで循環依存が発生する原因と回避方法を具体例を交えて解説します。
Laravelでは依存性注入(DI)が便利な反面、気づかないうちに依存関係が複雑化して「循環依存」が発生することがあります。
これは、サービス同士やクラス同士がお互いを呼び合ってしまうことで、サービスコンテナが依存を解決できずにエラーを起こす原因となります。
循環依存は実務でも頻発することがあり、意外と初心者が陥りやすい落とし穴です。
循環依存とは?
クラスAがクラスBを使っていて、さらにクラスBもクラスAを使っている状態です。
Laravelのように「コンストラクタインジェクションが一般的なフレームワーク」では起こりやすいです。
// AService.php
class AService {
public function __construct(BService $bService) { ... }
}
// BService.php
class BService {
public function __construct(AService $aService) { ... }
}
このようなケースでは、サービスコンテナが「どちらを先に解決すればいいか分からない」ため、次のようなエラーが発生します。
Target class [AService] does not exist.
よくある発生パターン
- サービス同士が機能を呼び合う
- RepositoryとServiceが互いに依存してしまう
- 共通化を意識しすぎて両方向から呼び出す構造になる
なぜダメなのか?
- Laravelの仕組みでは解決できない
- 保守が難しくなる(責務が不明確になる)
- テストしにくくなる
回避方法
- クラスの責務を明確化する
- 共通処理はユーティリティやヘルパークラスに切り出す
- 必要に応じて依存を動的に呼び出す
例えば、resolve() を使って「必要な時だけ依存を取得する」ようにすることも可能です。
class UserService {
public function handle() {
$postService = resolve(PostService::class);
$postService->doSomething();
}
}
さらに安全にするには?
- Larastan / PHPStan を導入して依存関係を静的に解析
- 設計レビューの段階で「双方向の依存になっていないか」を確認
まとめ
- 循環依存はLaravelでよくある落とし穴
- 設計段階でクラスの責務を分けるのが最も重要
-
resolve()は必要な時だけ依存を取得する方法として有効 - 静的解析ツールを使えば未然に防止できる
設計段階で責務を分け、依存方向を一方通行に保つことが、循環依存を防ぐ最も重要なポイントです。