概要
Laravelでは論理削除を実現するためのSoftDeletes
トレイトが用意されています。
論理削除の判定に利用されるdeleted_at
カラムを別のカラムに変更するのはクラス定数のDELETED_AT
を変更すればできるのですが判定はそのカラムがnullかどうかに限られます。
今回はDBの構造は変更せず、過去の遺産として残っているデリートフラグを使って論理削除を実現する手順を整理していきます。
前提条件
今回対象としているデリートフラグはカラム名をdelete_flag
とし、デフォルトが「0」で、論理削除の場合は「1」に設定されるものとして記述していきます。
トレイトの作成
まずはデリートフラグによる論理削除用トレイトDeleteFlagTrait.php
を作成します
トレイトの継承
SoftDeletes
トレイトに用意されたメソッドを流用したいのでトレイト継承しています。
// ソフトデリートを継承する
use \Illuminate\Database\Eloquent\SoftDeletes;
起動処理を変更
Laravelではモデルに追加したトレイトに対しモデルのbootメソッドでboot+トレイト名
メソッドが呼ばれるようになっています。
SoftDeletes
トレイトを継承していることよにりSoftDeletes
も「モデルに追加されたトレイト」として扱われるためbootSoftDeletes
メソッドが呼び出されることになります。
bootSoftDeletes
メソッドでは起動処理としてグローバルスコープを設定していますが、このスコープは利用しないので呼び出されないようにしておき、代わりにbootDeleteFlagTrait
を作成してデリートフラグ用のスコープをグローバルスコープとして設定します。
デリートフラグ用スコープについては後述
/**
* ソフトデリートの起動処理
*/
public static function bootSoftDeletes()
{
// デフォルトの処理を無効にするため空のメソッドを記述
}
/**
* 起動処理
*/
public static function bootDeleteFlagTrait()
{
// グローバルスコープを設定する
static::addGlobalScope(new DeleteFlagScope());
}
初期化処理の変更
起動処理と同様、モデルに追加されたトレイトのinitialize+トレイト名
メソッドが初期化処理として呼び出されます。
SoftDeletes
トレイトのinitializeSoftDeletes
メソッドではdeleted_at
カラムの属性をDateTime/Carbon
インスタンスにするためdates
プロパティに追加していますが、デリートフラグは日時を扱うカラムではないのでこの処理は不要になります。
他に初期化処理として追加する必要はないので何も処理されないようにだけしておきます。
/**
* ソフトデリートの初期化処理
*/
public function initializeSoftDeletes()
{
// デフォルトの処理を無効にするため空のメソッドを記述
}
デリートフラグのカラム名を指定する
既存のメソッドを継承しているためgetDeletedAtColumn
メソッドでデリートフラグのカラム名を返すように変更する必要があります。
もしモデルごとにカラム名が違う場合、このメソッドを修正する必要があります。
/**
* デリートフラグのカラム名を返す
*
* @return string
*/
public function getDeletedAtColumn()
{
return 'delete_flag';
}
削除の判定処理を変更
SoftDeletes
トレイトではdeleted_at
カラムがnullかどうかで判定している処理を、delete_flag
が「1」かどうかで判定する処理に変更する必要があります。
トレイトのまとめ
以下が作成したトレイトのソースになります
<?php
namespace App\Models\Traits;
use App\Models\Scopes\DeleteFlagScope;
trait DeleteFlagTrait
{
// ソフトデリートを継承する
use \Illuminate\Database\Eloquent\SoftDeletes;
/**
* ソフトデリートの起動処理
*/
public static function bootSoftDeletes()
{
// デフォルトの処理を無効にするため空のメソッドを記述
}
/**
* ソフトデリートの初期化処理
*/
public function initializeSoftDeletes()
{
// デフォルトの処理を無効にするため空のメソッドを記述
}
/**
* 起動処理
*/
public static function bootDeleteFlagTrait()
{
// グローバルスコープを設定する
static::addGlobalScope(new DeleteFlagScope());
}
/**
* 論理削除処理
*/
protected function runSoftDelete()
{
$query = $this->setKeysForSaveQuery($this->newModelQuery());
$columns = [$this->getDeletedAtColumn() => 1];
$this->{$this->getDeletedAtColumn()} = 1;
$query->update($columns);
}
/**
* 論理削除復帰処理
*/
public function restore()
{
if ($this->fireModelEvent('restoring') === false) {
return false;
}
$this->{$this->getDeletedAtColumn()} = 0;
$this->exists = true;
$result = $this->save();
$this->fireModelEvent('restored', false);
return $result;
}
/**
* 論理削除されているかどうか確認する
*
* @return bool
*/
public function trashed()
{
return (1 == $this->{$this->getDeletedAtColumn()});
}
/**
* デリートフラグのカラム名を返す
*
* @return string
*/
public function getDeletedAtColumn()
{
return 'delete_flag';
}
}
スコープの作成
続けて先ほど設定したデリートフラグ用スコープDeleteFlagScope.php
を作成します。
スコープの継承
こちらもSoftDeletes
トレイト用のスコープのメソッドを流用したいので継承しています。
スコープの設定
デリートフラグが「0(=削除されていない)」ものだけを抽出するようにスコープを作成します。
/**
* デリートフラグがOFFのものだけ取得するスコープ
*/
public function apply(Builder $builder, Model $model)
{
$builder->where($model->getQualifiedDeletedAtColumn(), 0);
}
マクロの変更
SoftDeletes
トレイト同様、deleted_at
カラムがnullかどうかで判定している処理を、delete_flag
が「1」かどうかで判定する処理に変更していきます。
スコープのまとめ
以下が作成したスコープのソースになります
<?php
namespace App\Models\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletingScope;
class DeleteFlagScope extends SoftDeletingScope
{
/**
* デリートフラグがOFFのものだけ取得するスコープ
*/
public function apply(Builder $builder, Model $model)
{
$builder->where($model->getQualifiedDeletedAtColumn(), 0);
}
public function extend(Builder $builder)
{
foreach ($this->extensions as $extension) {
$this->{"add{$extension}"}($builder);
}
$builder->onDelete(function (Builder $builder) {
$column = $this->getDeletedAtColumn($builder);
return $builder->update([
$column => 1,
]);
});
}
protected function addRestore(Builder $builder)
{
$builder->macro('restore', function (Builder $builder) {
$builder->withTrashed();
return $builder->update([$builder->getModel()->getDeletedAtColumn() => 0]);
});
}
protected function addWithoutTrashed(Builder $builder)
{
$builder->macro('withoutTrashed', function (Builder $builder) {
$model = $builder->getModel();
$builder
->withoutGlobalScope($this)
->where($model->getQualifiedDeletedAtColumn(), 0);
return $builder;
});
}
protected function addOnlyTrashed(Builder $builder)
{
$builder->macro('onlyTrashed', function (Builder $builder) {
$model = $builder->getModel();
$builder
->withoutGlobalScope($this)
->where($model->getQualifiedDeletedAtColumn(), 1);
return $builder;
});
}
}
参照