Loading...
LaravelModelPHPScope

Laravelでモデルにアカウント凍結を実装する

はじめに

サイトで規約違反をしたユーザーを一時的に凍結する機能をLaravelのモデルに実装する方法を書く。
モデルは、仮にUserとする。ここではLaravel5.4を使用している。 下記のコードは、ソフトデリートの機能をそのまま使用しただけである。

Scopeの作成

App\Scopes内に、FrozenScope.phpを作成する。
<?php

namespace App\Scopes;

use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;

class FrozenScope implements Scope
{
    /**
     * すべての拡張機能がビルダーに追加される
     *
     * @var array
     */
    protected $extensions = ['FrozenRestore','WithFrozen','OnlyFrozen'];

    /**
     * Eloquentクエリビルダへ適用するスコープ
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $builder
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @return void
     */
    public function apply(Builder $builder, Model $model)
    {
        $builder->whereNull($model->getQualifiedFrozenAtColumn());
    }

    /**
     * 必要な機能を使用してクエリビルダを拡張。
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $builder
     * @return void
     */
    public function extend(Builder $builder)
    {
        foreach ($this->extensions as $extension) {
            $this->{"add{$extension}"}($builder);
        }
    }

    /**
     * 凍結済みモデルを復活できるように、Builderに追加
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $builder
     * @return void
     */
    protected function addFrozenRestore(Builder $builder)
    {
        $builder->macro('frozenRestore', function (Builder $builder) {
            $builder->withFrozen();

            return $builder->update([$builder->getModel()->getFrozenAtColumn() => null]);
        });
    }

    /**
     * 凍結済みモデルも取得できるように、Builderに追加
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $builder
     * @return void
     */
    protected function addWithFrozen(Builder $builder)
    {
        $builder->macro('withFrozen', function (Builder $builder) {
            return $builder->withoutGlobalScope($this);
        });
    }

    /**
     * 凍結済みモデルのみ取得できるように、をBuilderに追加
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $builder
     * @return void
     */
    protected function addOnlyFrozen(Builder $builder)
    {
        $builder->macro('onlyFrozen', function (Builder $builder) {
            $model = $builder->getModel();

            $builder->withoutGlobalScope($this)->whereNotNull(
                $model->getQualifiedFrozenAtColumn()
            );

            return $builder;
        });
    }
}
Illuminate\Database\Eloquent\SoftDeletingScopeとほぼ同じコードであることが確認できる。

Modelの設定例

User Migration

class CreateUserTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        if( !Schema::hasTable('user') ) {
            Schema::create('user', function (Blueprint $table) {
                // 
                // その他項目
                // 
                $table->timestamp('frozen_at')->nullable();
            }
         }
     }
     // その他メソッドが続く
}
$table->timestamp('frozen_at')->nullable();up()メソッド内に追加する。
個々の、モデルに合わせて変更すること!

Modelで使用するトレイトの作成

App\内に、FrozenAccount.phpを作成。
<?php
namespace App;

use App\Scopes\FrozenScope;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;

trait FrozenAccount
{
    /**
     * モデルの凍結トレイトを起動
     *
     * @return void
     */
    public static function bootFrozen()
    {
        static::addGlobalScope(new FrozenScope);
    }


    /**
     * 凍結済みモデルインスタンスを復元
     *
     * @return bool|null
     */
    public function frozenRestore()
    {
        if ($this->fireModelEvent('frozenRestoring') === false)return false;
        $this->{$this->getFrozenAtColumn()} = null;
        $this->exists = true;
        $result = $this->save();
        $this->fireModelEvent('frozenRestored', false);

        return $result;
    }

    /**
     * モデルインスタンスが凍結済みかどうかを判定
     *
     * @return bool
     */
    public function isFrozen()
    {
        return ! is_null($this->{$this->getFrozenAtColumn()});
    }

    /**
     * モデルインスタンスを凍結状態にする
     *
     * @return bool|null
     */
    public function frozen()
    {
        if ($this->fireModelEvent('frozenStart') === false) return false;
        $this->{$this->getFrozenAtColumn()} = Carbon::now();
        $this->exists = true;
        $result = $this->save();
        $this->fireModelEvent('frozenEnd', false);
        return $result;
    }

    /**
     * ディスパッチャーに凍結開始モデルイベントを登録
     *
     * @param  \Closure|string  $callback
     * @return void
     */
    public static function frozenStart($callback)
    {
        static::registerModelEvent('frozenStart', $callback);
    }

    /**
     * ディスパッチャーに凍結終了モデルイベントを登録
     *
     * @param  \Closure|string  $callback
     * @return void
     */
    public static function frozenEnd($callback)
    {
        static::registerModelEvent('frozenEnd', $callback);
    }

    /**
     * ディスパッチャーに復元モデルイベントを登録
     *
     * @param  \Closure|string  $callback
     * @return void
     */
    public static function frozenRestoring($callback)
    {
        static::registerModelEvent('frozenRestoring', $callback);
    }

    /**
     * 復元されたモデルイベントをディスパッチャーに登録
     *
     * @param  \Closure|string  $callback
     * @return void
     */
    public static function frozenRestored($callback)
    {
        static::registerModelEvent('frozenRestored', $callback);
    }

    /**
     * "frozen_at at"列の名前を取得します。
     *
     * @return string
     */
    public function getFrozenAtColumn()
    {
        return defined('static::FROZEN_AT') ? static::FROZEN_AT : 'frozen_at';
    }

    /**
     * 完全修飾された"frozen_at at"カラムを取得
     *
     * @return string
     */
    public function getQualifiedFrozenAtColumn()
    {
        return $this->getTable().'.'.$this->getFrozenAtColumn();
    }
}
Illuminate\Database\Eloquent\SoftDeletesとほぼ同じコードであることが確認できる。

User モデル

<?php
namespace App;

class User extends Model
{
    use FrozenAccount;

    protected $dates = [
        'frozen_at'             // 凍結された日付
    ];

    /**
     * モデルの "初期起動" メソッドモデル
     *
     * @return void
     */
    protected static function boot()
    {
       parent::boot();
       static::bootFrozen();
    }
}
$dates配列に'frozen_at'を追加し、boot()メソッド内に、static::bootFrozen();を追加する。

使用例

凍結済みモデルかどうかの例

if($user->isFrozen()){

}
isFrozen()

凍結モデルにする例

$user->frozen();
frozen()

凍結済みモデルを取得する例

$user = User::withFrozen()
            ->where('id',1)
            ->get();
withFrozen()

凍結済みモデルのみを取得する例

$c = User::onlyFrozen()
         ->count();
onlyFrozen()

凍結済みモデルを復元する例

$user = User::onlyFrozen()->frozenRestore();
frozenRestore()

さいごに

ソフトデリートのコードを応用するだけで、簡単にアカウントの凍結を実装することができる。
是非試してみてほしい。
2 comments
  1. てつおみ

    ありがとうございます、参考にさせて頂きました。

    一点、「Modelで使用するトレイトの作成」で
    use App\Scopes\FrozenScope;
    の箇所は
    use App\FrozenAccount;
    になるかと思いますのでコメントさせて頂きます。

    1. kaoken

      どうも、コメントありがとうです。
      `use App\Scopes\FrozenScope;`はただ単に消し忘れでした^^;
      `FrozenAccount`は、同じ`App`層なので、そのままにしておきます。

コメントを残す

%d人のブロガーが「いいね」をつけました。