はじめに
サイトで規約違反をしたユーザーを一時的に凍結する機能を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()
さいごに
ソフトデリートのコードを応用するだけで、簡単にアカウントの凍結を実装することができる。是非試してみてほしい。
てつおみ
ありがとうございます、参考にさせて頂きました。
一点、「Modelで使用するトレイトの作成」で
use App\Scopes\FrozenScope;
の箇所は
use App\FrozenAccount;
になるかと思いますのでコメントさせて頂きます。
kaoken
どうも、コメントありがとうです。
`use App\Scopes\FrozenScope;`はただ単に消し忘れでした^^;
`FrozenAccount`は、同じ`App`層なので、そのままにしておきます。