【Laravel】論理削除（softDelete）するときにリレーション先のデータも削除する方法

## 概要

Laravelでは、マイグレーションファイルに外部キー制約の設定を記述しておくと、親テーブルのデータを削除したときに子テーブルのデータも同時に削除することができます。

但しこれは、DBから実際にデータを削除する「**物理削除**」の場合の話です。

DBからレコードを削除するわけではなく削除フラグを立てて削除したとみなす「**論理削除**」の場合は、マイグレーションファイルへの記述だけではリレーション先のデータを一緒に削除してくれません。

このような場合に、 **モデルの `boot` メソッド（もしくは `booted` メソッド）を使って初期設定を行えば**、あるテーブルのデータを論理削除したときにリレーション先のテーブルのデータも削除することができます。

業務でこの方法を使う機会があったので、 モデル `boot` メソッドについて勉強したこととあわせて、備忘録を書いておきたいと思います。

## 論理削除（softDelete）の設定方法

まず事前準備としてソフトデリートの設定をします。

ソフトデリート機能を組み込む手順は以下の2つです。

1.　マイグレーションファイルにソフトデリートの設定を記述する
2.　EloquentモデルにSoftDeletesトレイトを組み込む

例として `users` テーブルにソフトデリート機能を設定してみます。

### 1. マイグレーションファイルにソフトデリートの設定を記述する

Laravelの論理削除ではテーブルの `deleted_at`カラムに値が入っている場合はデータは削除されたとみなすので、 `deleted_at`カラムを追加する必要があります。

`deleted_at`カラムを追加するため、マイグレーションファイルに以下の1行を追記します。

```php
publicfunction up()
{
    Schema::table('user',function (Blueprint $table) {
        $table->softDeletes();    // 追記
    });
}
```

これでマイグレーションを実行すると、DBのテーブルに `deleted_at`カラムが作成されていることが確認できます。

### 2. EloquentモデルにSoftDeletesトレイトを組み込む

 `User` モデルファイルに以下のように追記します。

```php
use Illuminate\Database\Eloquent\SoftDeletes;    // 追記

class User extends Model
{
    use SoftDeletes;    // 追記
}
```

この記述により、deleteメソッドでモデルを削除した際はソフトデリートが実行されるようになります。

Laravelでソフトデリートを行うための設定は以上です。

## モデルのbootedメソッドとは

本題の「ソフトデリートするときにリレーション先のデータも削除する」ための実装にあたり、先に今回使用する `boooted` メソッドについて少し勉強しておきます。

 `Laravel` のモデルでは、初期起動時に `boot` メソッドを走らせて初期設定をしています。

 `booted` メソッドはモデルの初期起動後に実行されるメソッドで、モデルに対して行いたい初期設定は `booted` メソッドに書くよう `readouble` にも説明があります。


### 【補足】bootedメソッドとbootメソッドについて

今回実装方法をググっていると`boot`メソッドを使っている記事が見つかり、自分としても初期起動時のメソッドといえば`boot`メソッドのイメージがあったので、`booted`メソッドと`boot`メソッドについて少し調べてみました。

 `readouble` の説明では、モデルの初期設定を行う方法としてLaravel6までは `boot` メソッドが使われていますが、Laravel7以降では `booted` メソッドが使われていました。

・Laravel8の該当箇所

https://readouble.com/laravel/8.x/ja/eloquent.html#:~:text=%E3%81%97%E3%81%AA%E3%81%84%E3%81%9F%E3%82%81%E3%81%A7%E3%81%99%E3%80%82-,%E3%82%AF%E3%83%AD%E3%83%BC%E3%82%B8%E3%83%A3%E3%81%AE%E4%BD%BF%E7%94%A8,-%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%82%AF%E3%83%A9%E3%82%B9

・Laravel6の該当箇所

https://readouble.com/laravel/6.x/ja/eloquent.html#:~:text=%E9%98%B2%E3%81%92%E3%81%BE%E3%81%99%E3%80%82-,%E3%82%B0%E3%83%AD%E3%83%BC%E3%83%90%E3%83%AB%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%97%E3%81%AE%E9%81%A9%E7%94%A8,-%E3%83%A2%E3%83%87%E3%83%AB%E3%81%AB%E3%82%B0%E3%83%AD%E3%83%BC%E3%83%90%E3%83%AB

試しにモデルファイルに以下のように書き、 `booted()` のところにマウスをホバーさせてVSCodeのヒントを表示してみます。

```jsx
// モデルファイルに記述
/**
 * @return void
 */
public static function booted(): void
{
    //
}

// booted() にマウスをホバーさせると、以下の説明が表示される
Illuminate\Database\Eloquent\Model::booted

Perform any actions required after the model boots.

<?php
protected static function booted() { }
```

`booted`メソッドについては、 `Perform any actions required after the model boots.` と説明されています。

 `boot()` を書いた場合も確認します。

```jsx
// モデルファイルに記述
/**
 * @return void
 */
public static function boot(): void
{
    //
}

// boot() にマウスをホバーさせると、以下の説明が出る
Illuminate\Database\Eloquent\Model::boot

Bootstrap the model and its traits.

<?php
protected static function boot() { }
@return void
```

`boot` メソッドは `Bootstrap the model and its traits.` と説明がありました。

※実際のLaravelのソースコードだと、以下のファイルに `boot` メソッドも `booted` メソッドもありました。　
興味のある方はこちらもご覧ください。

https://github.com/laravel/framework/blob/9.x/src/Illuminate/Database/Eloquent/Model.php

メソッド名の通りですが、 `booted`メソッドはモデルの初期起動後に実行されているということですね。

私は業務ではLaravel8を使っており、 `boot` メソッドでも `booted` メソッドでも同じように設定を行うことは出来ましたが、現在の `readouble` の説明に従って `booted`メソッドを使っていきたいと思います。

## Modelのbootedメソッドにクロージャを書く

前置きが長くなりましたが、実際にモデルの `booted` メソッドに「データを論理削除したらリレーション先のデータも削除する」設定を記述します。

### 前提

- `users` テーブル（親）と**1対多**で紐づく `posts` テーブルがあるとします。
- `users` テーブルからレコードを論理削除したら、 リレーション先の`posts` テーブルからもレコードを論理削除するように設定します。

### 実装方法

 `User.php`にリレーションと `booted`メソッドを追記します。

```php
class User extends Model
{
    use HasFactory, SoftDeletes;

    /**
     * @return HasMany
     */
    public function posts(): HasMany
    {
        return $this->hasMany(Post::class);
    }

    /**
     * @return void
     */
    public static function booted(): void
    {
        static::deleted(function ($user) {
            $user->posts()->delete();
        });
    }
}
```

これにより、 `users` テーブルのデータ削除時に、リレーション先の `posts` テーブルからも同時に削除してくれます。

※ `posts` テーブルに対してもソフトデリートの設定を行っている場合は `posts` テーブルも論理削除、何も設定していなければ `posts` テーブルからは物理削除になります。

## 最後に

論理削除はDBのレコードを削除するわけでは無く、SQLの命令文でいうと `UPDATE` にあたるので、論理削除の場合はDB的には削除と見なされないということは、よく考えると当たり前でもありますが改めて勉強になりました。

またモデルの `boot` メソッドや `booted` メソッドの使い方や、これらが出来ることについても良い学びになりました。

Laravelのライフサイクルの理解を深めれば、もっともっと実装の引き出しが増えるんだろうなと思うので、コツコツ勉強していきたいと思います。

## 参考記事


https://readouble.com/laravel/8.x/ja/eloquent.html

https://yaba-blog.com/larave-soft-delete/#toc3

https://zenn.dev/naoki_oshiumi/articles/e16c9fbb4dfa3d

https://qiita.com/niisan-tokyo/items/d3be588b53df8fa0278c