Laravelは簡単に論理削除を実装できるのですが、意外と落とし穴が多くハマったところがあったので、列挙していきます。
よりスマートな回避方法があれば、突っ込んでください。
論理削除したのにuniqueのバリデーションが通らない
論理削除の場合、以下のようなバリデーションは削除済を含めて一意であることをバリデーションします。
public function rules()
{
return [
'name' => 'required|unique:countries'
];
}
対策
1. uniqueのルールをカスタマイズする
public function rules()
{
return [
'name' => [
'required',
Rule::unique('countries')->whereNull('deleted_at')
]
];
}
2. カスタムバリデーションでEloquent ORMを使う
Laravel5でカスタムバリデーションを参考にするとできる。今はめんどくさいけど、Laravel 5.5. Custom Validator Rulesによると、5.5で楽になりそう
また、exists
についても同じように、「削除したのにバリデーションが通ってしまう」現象が起きるので、同様の処置が必要です。
->delete()
メソッドを使うと孫が消えてくれない
論理削除のときにDelete on Cascade的なことをやりたいときの話
Country < User < Postのリレーションを例に使います。
まず、Userのdeletingイベントはこんな感じで書けると思います。
protected static function boot()
{
parent::boot();
self::deleting(function ($user) {
$user->posts()->delete();
});
}
Countryのdeletingイベントをこう書くとCountry削除時にPostは消えません。あら悲しい。
protected static function boot()
{
parent::boot();
self::deleting(function ($country) {
$country->users()->delete();
});
}
対策
1. さくっとぐるぐる削除
一番てっとり早いものの、できれば採用したくないやり方。
Userをひとつひとつ削除するので、Userモデルに書いたdeletingが発火してくれる。
protected static function boot()
{
parent::boot();
self::deleting(function ($country) {
$users = $country->users;
foreach($users as $user) {
$user->delete();
}
});
}
2. 削除するモデルを列挙
数が少ないならいいけど、リレーションしまっくてると書き出すのがめんどくさい
protected static function boot()
{
parent::boot();
self::deleting(function ($country) {
$user_ids = $country->users->pluck('id')->toArray();
Post::whereIn('user_id', $user_ids)->delete();
User::destroy($user_ids);
});
}
検証用につくったリポジトリ