📘
【Laravel × Vue】「データが無限にネストされる」現象とその解決法
どんな現象?
とあるプロジェクトで
案件テーブル:projects
├── 原価計算表:estimates
├── 現場管理表:progress_reports
という構成のデータをLaravel bladeからvueコンポーネントへ渡すさいに開発メンバーから共有された現象についてです。
LaravelでAPIやVue.js用にデータを返すとき、
「project → estimate 」という親子関係でデータを渡すように設定したつもりが
"project": {
"id": 268,
"company_id": 1,
"name": "案件名",
"status": 1,
"created_at": "2025-08-27T19:26:42.000000Z",
"estimate": {
"id": 240,
"company_id": 1,
"project_id": 268,
"estimate_cost": 260000,
"created_at": "2025-10-27T14:34:32.000000Z",
"project": {
"id": 268,
"company_id": 1,
"name": "案件名",
"status": 1,
"created_at": "2025-08-27T19:26:42.000000Z",
}
},
}
「project → estimate → project → estimate ...」
のように
同じデータが何重にも入れ子(ネスト)になってしまい、ループ状態に、、
レスポンスがとても大きくなったり、フロントでエラーになってしまっていた。。。
何が起きていたの?
- 「親子リレーションが双方向」:
- Laravelの「リレーション」という機能で、
たとえば「Project(案件)」と「Estimate(見積)」を
お互いに参照できるようにしていました(親子関係が双方向)。
- Laravelの「リレーション」という機能で、
- 「子で親を参照するアクセサ」:
- さらに「Estimate」モデルの中で、
「親のProjectの値を使って計算する」アクセサ(getXxxAttribute)を作っていました。
- さらに「Estimate」モデルの中で、
- 「APIでJSON返却」
- その状態で、APIでデータを返すときに
Project::with('estimate')->get()のようにリレーションごと取得し、
そのままresponse()->json($projects)で返していました。
- その状態で、APIでデータを返すときに
「親子リレーションが双方向」+「子で親を参照するアクセサ」+「APIでJSON返却」
というこの3つの特定の条件が重なった場合のみ発生していた様子。。
どこでハマった?
- Bladeで
@dd($projects[0]->estimate)などで中身を見ても
普通のデータ構造に見えるので、
なぜAPIレスポンスやVueのpropsで
「project → estimate → project → ...」と
無限にネストされるのか分かりませんでした。
なぜ無限ループになるの?
- ProjectのestimateリレーションをAPIで返す
- Estimateの「計算用アクセサ」が親のProjectを参照する
- そのProjectのestimateリレーションをまたAPIで返す…
- これがずっと繰り返されてしまう
どうやって解決するの?
Estimateモデルに下記を1行追加するだけ!
[Estimate.php]
protected $hidden = ['project'];
これで「APIやtoJson/toArrayでデータを返すときだけ」
estimateの中のprojectリレーションが除外され、
無限ループが防げます。
コード内やBlade、アクセサでは普通に $this->project で親にアクセスできます。
まとめ
この3つが揃うと、データが無限にネストされることがある
protected $hidden = ['除外したいリレーション名']; を使えば解決できます!
「APIのレスポンスがやたら大きい」「Vueでpropsが変」などで困ったら、
まずはこのパターンを疑ってみてください!
備考
chatgptによると
Laravel公式でも推奨されている一般的な解決策とのことですが、一応他にも解決策はあるそうで他の選択肢として「APIリソース(Resourceクラス)で明示的に出力項目を制御する」という方法もあるようす。
現場によって判断すべきなのかなぁと。。。!
Discussion