LoginSignup
19
23

More than 3 years have passed since last update.

Laravelでデリートフラグを使って論理削除を実現する手順

Last updated at Posted at 2019-12-31

概要

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」かどうかで判定する処理に変更する必要があります。

トレイトのまとめ

以下が作成したトレイトのソースになります
app/Models/Traits/DeleteFlagTrait.php

<?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」かどうかで判定する処理に変更していきます。

スコープのまとめ

以下が作成したスコープのソースになります
app\Models\Scopes\DeleteFlagScope.php
<?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;
        });
    }
}

参照

19
23
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
23