Laravel 5.1 入門記 その12 (Eloquent 論理削除編)
Eloquent がサポートしている論理削除 (Soft Deletes) についてです。 今回も教材はこちら。
Eloquent: Getting Started - Laravel - The PHP Framework For Web Artisans
Soft Deletes
早速。
Laravel は論理削除をサポートする仕組みを標準で用意してます。論理削除カラムを持つテーブルに対応したモデルを書くときは以下の様になります。
<?php namespace App; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; class Flight extends Model { use SoftDeletes; /** * The attributes that should be mutated to dates. * * @var array */ protected $dates = ['deleted_at']; }
こんな感じです。論理削除対応をしたテーブルは deleted_at というカラムが必要です。 ちなみに論理削除のカラムは、「timestamp」です。数値フィールドではありません。
論理削除対応のクエリ
論理削除対応のモデルを定義して、以下の様にデータを取得すると
$flights = App\Flight::all();
実行される SQL は
select * from `flights` where deleted_at is null;
こんな感じで、自動的に deleted_at is null が出力されます。論理削除されたデータも取りたいときや無差別にデータを取りたいときにも対応していて
$flights = App\Flight::withTrashed()->get(); $flights = App\Flight::onlyTrashed()->get();
こんな書き方で取得できます。 withTrashed が論理削除済みも含む全てで、onlyTrashed が論理削除済みのデータのみです。
論理削除の方法
論理削除を実行する方法は、通常のモデルで行った方法がそのままです。
App\Flight::where('active', 1)->delete(); App\Flight::destroy([1,2,3]);
などなど。ただし、 SoftDeletes の trait を継承した段階で、このやり方では物理削除が出来なくなります。どうしても物理削除したい場合は
App\Flight::where('active', 1)->forceDelete();
と、forceDelete を使います。
論理削除を boolean / integer など数値カラムにする方法
レガシーなデータ構造だと、削除「フラグ」っていうことが多いですよね! ね!!!
Timestamp から数値系のカラムに SoftDeletes の仕組みを置き換えるときは、Illuminate\Database\Eloquent\SoftDeletes の trait で定義されている以下のメソッドをオーバーライドして置き換えをします。
- bootSoftDeletes
- forceDelete
- performDeleteOnModel
- runSoftDelete
- restore
- trashed
- withTrashed
- onlyTrashed
- restoring
- restored
- getDeletedAtColumn
- getQualifiedDeletedAtColumn
いっぱいあります。ポイントとしては Eloquent のクラスが SoftDeletes に依存する形で実装が行われているため、自前のクラスに以下の様な定義が必要です。
use Illuminate\Database\Eloquent\SoftDeletes; class BaseModel extends Model { use SoftDeletes;
ちゃんと use SoftDeletes を書いた上でオーバーライドが必要。この辺り、本当にもうちょっと何とかならなかったのかと思う。
更に、Illuminate\Database\Eloquent\SoftDeletingScope を継承してメソッドをオーバーライドしていく必要もあります。こちらは上記のモデルクラスから bootSoftDeletes などからインスタンスを作って使用するだけなので、変な癖は少ないです。が、一箇所だけ remove メソッドで非常に実装に困ったところがあったので、そこだけ載せておきます。
public function remove(Builder $builder, Model $model) { $column = $model->getQualifiedDeletedAtColumn(); $query = $builder->getQuery(); $bindings = $builder->getBindings(); $count = 0; $query->wheres = collect($query->wheres)->reject(function ($where) use ($column, &$count, &$bindings) { $result = $this->isSoftDeleteConstraint($where, $column); if ($result) { unset($bindings[$count]); } $count++; return $result; })->values()->all(); $query->setBindings(array_merge($bindings)); }
追加した論理削除を除外する条件を除去する処理で、 QueryBuilder が持っている条件から条件式の除去と、パラメータを除去する処理を行っています。 元々の SoftDeletes は is null / is not null で論理削除判断をしていたので、 Closure を使って条件を除去する際に $count などの処理は不要で、$query->setBindings の辺りも不要です。しかし、数値型のパラメータで論理削除判断を行う時は、パラメータも使用しないといけないので、こんな面倒な処理が必要になってしまいます。
withTrashed を使った時にこの処理が呼ばれるんですが、最初は気が付かなくて論理削除のパラメータが残ってしまい、結果クエリが正常に実行できないとかで、迷惑を掛けてしまった。
更に使う機会が少ないかもしれませんが、 Laravel の SoftDeletes はリレーションを定義するクラス HasManyThrough も依存してます。結局、自分がプロダクションで使った時は、ここまでの対応はやりませんでしたが…。
- Illuminate\Database\Eloquent\Relations\HasManyThrough を継承してカスタマイズ
- Illuminate\Database\Eloquent\Model の hasManyThrough メソッドをオーバーライドして上記の HasManyThrough をカスタムしたクラスの利用
などが必要になってくると思います。 果たして、ここまでして SoftDeletes の仕組みに載っかることが重要なのか分かりませんが…。
話が脱線しましたが、みんな大好き論理削除編は終了。 ちょうど、論理削除なんてない!ドメインに集中しろ!と本日の TL は盛り上がっているところではありますが…。
続いて次回は、Laravel のリレーション編です。