Laravel Eloquent SoftDelete
LaravelでEloquentを利用してる皆様、おはこんばんわんこm(__)m
最近、レガシーDBで、OR/Mに対応するときに、論理削除コードで詰まりました。
Laravelでは、論理削除のTraitを利用するときに、SoftDeletesを呼び出す必要が有りますが、
これがまた、削除時にはタイムスタンプが入るようになっています。
DBを初期構築から対応出来るプロジェクトであれば良いのですが、既存のプロジェクトや、削除を 1
0
で対応しているプロジェクトも多いですよね。
論理削除コードを0,1で対応するかどうかなどの、設計思想は置いておいて。。。
とりあえず、SoftDeleteを継承することで対応する事にします。
継承して作成するクラスは、2つだけです。
- App\Eloquent\ExSoftDeletes
- App\Eloquent\ExSoftDeletingScope
SoftDeletes Trait
Illuminate\Database\Eloquent\SoftDeletes
クラスを継承して、 App\Eloquent\ExSoftDeletes
クラスを作成します。
Eroquentでは、Build時のTraitに追加するために、SoftDeleteのbootメソッドにて、 SoftDeletingScope
が設定されます。
public static function bootSoftDeletes()
{
static::addGlobalScope(new SoftDeletingScope);
}
この、 SoftDeletingScope
を継承したクラスを作成(次項)するのと、runSoftDelete() メソッドを修正します。(注:他にも対応しなければならいメソッドがあるかも・・・)
以下、全コードです。
<?php
namespace App\Eloquent;
use Illuminate\Database\Eloquent\SoftDeletes;
trait ExSoftDeletes
{
use SoftDeletes;
/**
* Boot the soft deleting trait for a model.
*
* @return void
*/
public static function bootSoftDeletes()
{
static::addGlobalScope(new ExSoftDeletingScope);
}
/**
* Perform the actual delete query on this model instance.
*
* @return void
*/
protected function runSoftDelete()
{
$query = $this->newModelQuery()->where($this->getKeyName(), $this->getKey());
$time = 1;
$columns = [$this->getDeletedAtColumn() => 1];
$this->{$this->getDeletedAtColumn()} = $time;
if ($this->timestamps && !is_null($this->getUpdatedAtColumn())) {
$this->{$this->getUpdatedAtColumn()} = $time;
$columns[$this->getUpdatedAtColumn()] = 1;
}
$query->update($columns);
}
}
SoftDeletingScope
前出のクラス作成で説明の通り、Queryのbuild時に生成されるクラス SoftDeletingScope
を継承してメソッドをオーバーライドします。
検索条件が、whereNull、whereNotNull となっている箇所が、修正のポイントです。
この部分は、プロジェクトのDBに合わせて、修正する必要が有るでしょう。
<?php
namespace App\Eloquent;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletingScope;
class ExSoftDeletingScope extends SoftDeletingScope
{
/**
* Apply the scope to a given Eloquent query builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
public function apply(Builder $builder, Model $model)
{
// $builder->whereNull($model->getQualifiedDeletedAtColumn());
$builder->where($model->getQualifiedDeletedAtColumn(), 0);
}
/**
* Add the without-trashed extension to the builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @return void
*/
protected function addWithoutTrashed(Builder $builder)
{
$builder->macro('withoutTrashed', function (Builder $builder) {
$model = $builder->getModel();
// $builder->withoutGlobalScope($this)->whereNull(
// $model->getQualifiedDeletedAtColumn()
// );
$builder->withoutGlobalScope($this)->where(
$model->getQualifiedDeletedAtColumn(), 0
);
return $builder;
});
}
/**
* Add the only-trashed extension to the builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @return void
*/
protected function addOnlyTrashed(Builder $builder)
{
$builder->macro('onlyTrashed', function (Builder $builder) {
$model = $builder->getModel();
// $builder->withoutGlobalScope($this)->whereNotNull(
// $model->getQualifiedDeletedAtColumn()
// );
$builder->withoutGlobalScope($this)->whereNotNull(
$model->getQualifiedDeletedAtColumn(), '<>', 0
);
return $builder;
});
}
}
以上、
あまり真剣にテストしてないので、ご利用は計画的に。。。
なお、Laravel5.6 からはちょっと継承方法が違ってくるらしいので、その時にまた。