Laravel8で親を論理削除したらリレーション先も論理削除する
2022-03-06
2022-06-04
8 min read
Laravel
親のデータを論理削除したら、それに関連する子や孫のデータも論理削除したい。
物理削除
の場合は、migration作成時に外部キーにCASCADE
を設定していれば、親削除時に自動で子や孫のデータも削除してくれます。ちなみに外部キーへの設定項目としては以下の設定があります。
設定内容 | 親削除時の動作 |
---|---|
CASCADE | リレーション先も自動で削除 |
SET NULL | NULLが設定される |
RESTRICT | 削除させない(エラー) |
しかし論理削除
では外部キーに設定していたとしても、リレーション先を自動で削除してくれません。対象のModelで、ロジックを入れる必要があります。
下記のような設計のテーブルを考えます。
親 : user(ユーザー)
キー | カラム名 |
---|---|
PK | user_id |
user_name |
子 : post(投稿)
キー | カラム名 |
---|---|
PK | post_id |
FK | user_id |
post_content |
親のuserを論理削除した際に、子のpostのデータも自動で論理削除するようにします。
それでは実際に親を論理削除した際リレーション先の子も論理削除するように、親のuserモデル内に以下のロジックを追記していきます。
※Laravel 8.xを使用しています。
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を使うことで初期設定などを行うことができます。
public function post(){
return $this->hasMany(Post::class, 'post_id');
}
userテーブルとpostテーブルの関係は「1:多」となるので、hasMany
を使ってリレーションを定義します。
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(メディア)
キー | カラム名 |
---|---|
PK | media_id |
FK | post_id |
media_path |
※postテーブルのリレーションをuserモデルに記述したように、mediaテーブルのリレーションはpostモデル定義してください。
protected static function boot()
{
parent::boot();
static::deleting(function ($user) {
$user->post->each(function($post){
$post->delete();
});
});
}
これで孫までの削除ができました。
自分の解釈では、おそらくdeleteを記述することでモデルイベントを発行しているのかなと思っていますが、間違っていればご指摘いただければ幸いです。
ここまで削除の実装について見てきましたが、簡単にバリデーションのことについても触れておこうと思います。
上記の例では、指定したuser_idに紐づくデータを削除するという流れでした。 実際のシステムでは、
- 削除対象のuser_idがテーブルに存在してるか?
- 別テーブルで使われていないか?(使われていれば削除できないように制御する)
といったバリデーションを挟むことがあると思います。
Route::delete('/v1.0/user/{user_id}', [UserController::class, 'delete']);
{
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では動かなかったのかも)。