Edited at

Laravel5.5専用 レガシーシステムのDBで、SoftDeleteに対応する唯一の方法


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 からはちょっと継承方法が違ってくるらしいので、その時にまた。