BLOG

ブログ

2025/01/06 プログラミング技術系

【Laravel】データ削除時に子要素・孫要素も自動的に削除する

この記事を書いた人 T.S

初めに

Laravelにて、親要素のデータを削除したときに、リレーション先である子要素、さらに子要素の子要素(=孫要素)を自動的に削除する方法をご紹介します!本記事は、外部キー制約(foreign key constraints)を設定していないことが前提です。
※外部キー制約を利用している場合、アプリケーション側での特別な設定は必要ありません。

検証環境は以下の通りです。

  • Laravel:11.34.1
  • PHP:8.3.14
  • MySQL:8.4.0

子要素を自動削除する方法

以下のリレーションを例にします🛒

  • 親要素:店(shops
  • 子要素:商品(items
  • 関係性:1対多(1つの店が複数の商品を持つ)

親要素(店)を削除したときに、紐づく子要素(商品)も自動的に削除する設定を、「店(shops)」モデルに記述します。

protected static function boot()
{
    parent::boot();
    static::deleting(function ($shop) {
        // 子要素を一括削除
        $shop->items()->delete();
    });
}

bootメソッドについて

bootメソッドでは、対象のモデルに対するイベント(作成・更新・削除など)のルールを設定できます。今回設定するdeletingイベントは「モデルが削除される直前」に呼び出されますが、他にも以下のようなイベントがあります。

  • creating:モデルが新規作成される直前
  • created:モデルが新規作成された直後
  • updating:モデルが更新される直前
    …などなど

deletingイベントについて

deletingイベントは、Eloquentdelete()またはdestroy()メソッドが呼ばれたときに呼び出されます。今回の例では、この削除イベント内で$shop->items()->delete()を設定し、紐づく商品データを一括で削除しています。

孫要素を自動削除する方法

以下のリレーションを例にします🛒

  • 親要素:店(shops
  • 子要素:商品(items
  • 孫要素:レビュー(reviews
  • 関係性:
    • 1つの店が複数の商品を持つ(1対多)
    • 1つの商品が複数のレビューを持つ(1対多)

親要素(店)を削除したときに、紐づく子要素(商品)と、孫要素(レビュー)を自動的に削除する設定を、「店(shops)」および「商品(items)」モデルに記述します。

親モデルの設定

まずは親要素のdeletingイベントの登録です。
孫要素の自動削除を考慮する場合、子要素に対して一括削除は行わず、個別に削除を行う必要があります。

protected static function boot()
{
    parent::boot();
    static::deleting(function ($shop) {
        // 子要素のdeletingイベント発火のため、一括削除ではなく子要素のモデルインスタンスそれぞれに対し削除をする
        $shop->items->each(function ($item) {
            $item->delete();
        });
    });
}

ポイント

  • $shop->items()ではなく、$shop->itemsを使って子要素のコレクションを取得すること
  • each()を使い、子要素の各インスタンスに対してdelete()を呼び出すこと(理由は後述

子モデルの設定

次に、子要素のdeletingイベントを登録します。

protected static function boot()
{
    parent::boot();
    static::deleting(function ($item) {
        // 子要素(shopから見れば孫要素)の一括削除
        $item->reviews()->delete();
    });
}

ポイント

  • 子モデル「商品」の削除時に、紐づく孫要素を一括削除すること
    → この処理により、親モデル(店)の削除が起点となり、子要素(商品)から孫要素(レビュー)までを削除できます🙌

⚠️ 親・子・孫の自動削除時の注意点

  • Eloquentの削除イベントを発火させるにはdelete()を個別に呼び出す必要がある

deletingイベントが呼び出される条件は、Eloquentのdelete()またはdestroy()を使った場合のみです。もし、親要素のdeletingイベントで一括削除(例:$shop->items()->delete())をしてしまった場合、子要素側ではdeletingイベントが動作しません。そのため、each()で子要素を1つずつ削除する必要があります。

Eloquentを介して一括削除ステートメントを実行すると、削除されたモデルに対してdeletingおよびdeletedモデルイベントがディスパッチされません。これは、deleteステートメントの実行時にモデルが実際には取得されないためです。

引用:Laravel 11.x Eloquentの準備 – クエリを使用したモデルの削除

終わりに

今回は、Laravelにて親要素を削除した際に、子要素・孫要素も自動的に削除する方法をご紹介しました!個人的には、Laravelの機能を活用して、親から子、子から孫と連鎖的に削除していく…という流れがドミノ倒しみたいで面白いなと思っています笑

ただ、親・子・孫の同時削除処理(deletingイベントを連鎖させていく方法)では各モデルインスタンスに対してdelete()を呼び出すため、削除対象が多くなるとパフォーマンスが低下する可能性があります(指定するリレーション先が多くなればなるほど重くなります💦)
極端に処理が重くなる場合は、外部キー制約を活用する、クエリでの一括削除を行う、といった方法もぜひ検討してみてください〜!



株式会社ウイングドアは福岡のシステム開発会社です。
現在、私達と一緒に"楽しく仕事が出来る仲間"として、新卒・中途採用を絶賛募集しています!
ウイングドアの仲間達となら楽しく仕事できるかも?と興味をもった方、
お気軽にお問い合わせ下さい!

アーカイブ