logo
Home
About
Works
Blog
Contact

Laravel8で親を論理削除したらリレーション先も論理削除する

2022-03-06

2022-06-04

8 min read

Laravel

目次


  1. やりたいこと
  2. 例題
  3. 実装
  4. 孫の削除
  5. バリデーションについて
  6. 最後に

やりたいこと

親のデータを論理削除したら、それに関連する子や孫のデータも論理削除したい。

物理削除の場合は、migration作成時に外部キーにCASCADEを設定していれば、親削除時に自動で子や孫のデータも削除してくれます。ちなみに外部キーへの設定項目としては以下の設定があります。

設定内容親削除時の動作
CASCADEリレーション先も自動で削除
SET NULLNULLが設定される
RESTRICT削除させない(エラー)

しかし論理削除では外部キーに設定していたとしても、リレーション先を自動で削除してくれません。対象のModelで、ロジックを入れる必要があります。

例題

下記のような設計のテーブルを考えます。

親 : user(ユーザー)

キーカラム名
PKuser_id
user_name

子 : post(投稿)

キーカラム名
PKpost_id
FKuser_id
post_content

親のuserを論理削除した際に、子のpostのデータも自動で論理削除するようにします。

実装

それでは実際に親を論理削除した際リレーション先の子も論理削除するように、親のuserモデル内に以下のロジックを追記していきます。

※Laravel 8.xを使用しています。

userデータ(親)削除時に、関連するpostデータ(子)が削除されるように、boot内でdeletingを定義する

userモデル
protected static function boot()
{
    parent::boot();
static::deleting(function ($user) {
$user->post()->delete();
});
}

deletingの定義により、userデータ削除前にpostデータが削除されます。 deletingをdeletedとした場合は、userデータ削除後にpostデータが削除されます。

ちなみにbootというメソッドは、Eloquentが初回に呼び出された時に走る静的メソッドです。Eloquentとは、laravelのORM(オブジェクト関係マッピング)で、bootを使うことで初期設定などを行うことができます。

userテーブルとpostテーブルのリレーションを定義する

userモデル
public function post(){
    return $this->hasMany(Post::class, 'post_id');
}

userテーブルとpostテーブルの関係は「1:多」となるので、hasManyを使ってリレーションを定義します。

削除する関数を定義する

userモデル
use SoftDeletes;

public static function deleteData($user_id){
    $result = DB::transaction(function () use ($user_id) {
        $user = User::where('user_id', $user_id)->first();
        $user->delete();
        return true;
    });
    return $result;
}

この削除関数を呼べば、userデータ削除時に関連するpostデータが削除されます。

削除するデータは、一度first()などを用いて取得した後に削除する必要があります。直接delete()を呼び出すと、モデルイベントを発行しないため、postデータが削除されないので注意です。また、複数データの削除になるため、transactionメソッドを用いてトランザクション処理行っています。

※トランザクション処理については、こちらの「Laravelでトランザクション処理のエラーハンドリングを行う方法」でまとめています。

孫の削除

子と孫が1:nで紐づいてる孫のデータも削除したい時は、上記のように記述するとうまく動きません。

boot内のdeletingを行う際に、eachメソッドなどを用いて削除を定義する必要があります。

ここではpostテーブルに対してmediaテーブルが紐づいてると想定した例を考えてみます。

孫 : media(メディア)

キーカラム名
PKmedia_id
FKpost_id
media_path

※postテーブルのリレーションをuserモデルに記述したように、mediaテーブルのリレーションはpostモデル定義してください。

user削除時にuserに紐づくpost、postに紐づくmediaを削除する

userモデル
protected static function boot()
{
    parent::boot();
    static::deleting(function ($user) {
$user->post->each(function($post){
$post->delete();
});
}); }

これで孫までの削除ができました。

自分の解釈では、おそらくdeleteを記述することでモデルイベントを発行しているのかなと思っていますが、間違っていればご指摘いただければ幸いです。

バリデーションについて

ここまで削除の実装について見てきましたが、簡単にバリデーションのことについても触れておこうと思います。

上記の例では、指定したuser_idに紐づくデータを削除するという流れでした。 実際のシステムでは、

  • 削除対象のuser_idがテーブルに存在してるか?
  • 別テーブルで使われていないか?(使われていれば削除できないように制御する)

といったバリデーションを挟むことがあると思います。

削除のapi
Route::delete('/v1.0/user/{user_id}', [UserController::class, 'delete']);
userモデル
{
    public function validationData()
    {
        $params = $this->all();
        //パスパラメーター取得
$params = array_merge($params, ['user_id' => $this->user_id]);
return $params; } public function rules() { return [ 'user_id' => [ 'required' ,'string' ,'max:16',
Rule::exists('user', 'user_id'), //存在を確認
Rule::unique('hoge', 'user_id'), //hogeテーブルで使われていないか確認
] ]; } public function messages(){ return ['user_id.unique' => 'このデータは使用されているため削除できません。',]; } }

user_idはパスパラメーターとして付与してるため、バリデーション用のデータとして含めるようにvalidationData()メソッドで前処理を行なっています。

最後に

今回は子のデータまでの削除でしたが、複数の子のデータや孫のデータも削除したい場合は対象のモデルにリレーションを設定して、deletingに設定を足してあげれば対応可能です。

また、まとめて論理削除する際のライブラリーも調べたら出てきましたが、自分の環境ではうまくうごきませんでした(しばらく更新されていないライブラリーだったため、Laravel8では動かなかったのかも)。