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