blog.yuzu441.com

Eloquentで自作traitを作ってis_activeカラムに対する処理を管理する

概要

SoftDeletsのようにis_activeを管理するActiveState traitを作成して、設定したモデルは自動的にis_active = trueが挿入されるようにしたい

注意

Model Event?がまだ理解できておらず今回は実装していない。もし必要であれば適宜呼び出しを実装する必要がある

実装

SoftDeletesの仕組みを知る

SoftDeletese.phpの仕組みを知る。LaravelのSoftDeletesが論理削除レコードを無視してくれる仕組み #Eloquent - Qiita で大枠を理解してあとはひたすら以下のコードを見るのと実装して動かしてみていた

要はtraitが読み込まれたら?GlobalScopeにScopeを設定して、withTrashedのようなメソッドが呼ばれた時は登録していたGlobalScopeを外してやると実現できそう

scope実装

一応SoftDeletes.phpconst DELETED_ATのようにconst IS_ACTIVEでカラムを指定できるように作る

イマイチまだどこから呼ばれているのかというか、どういう原理なのかわかっていないがextendが呼ばれたらメソッド呼びまくってる実装が面白い

<?php
// app/models/scopes/ActiveSteteScope.php

declare(strict_types=1);

namespace App\Models\Scopes;

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

class ActiveStateScope implements Scope
{
    /**
     * All of the extensions to be added to the builder.
     *
     * @var string[]
     */
    protected $extensions = ['WithInactive', 'WithoutInactive'];

    /**
     * Apply the scope to a given Eloquent query builder.
     *
     * @param Builder $builder
     * @param Model $model
     * @return void
     */
    public function apply(Builder $builder, Model $model): void
    {
        $builder->where($model->getQualifiedIsActiveColumn(), '=', 1);
    }

    /**
     * Extend the query builder with the needed functions.
     *
     * @param Builder $builder
     * @return void
     */
    public function extend(Builder $builder)
    {
        foreach ($this->extensions as $extension) {
            $this->{"add{$extension}"}($builder);
        }
    }

    /**
     * Add the with-inactive extension to the builder.
     *
     * @param Builder $builder
     * @return void
     */
    public function addWithInactive(Builder $builder): void
    {
        $builder->macro('withInactive', function (Builder $builder, $withInactive = true) {
            if (!$withInactive) {
                return $builder->withoutInactive();
            }
            return $builder->withoutGlobalScope($this);
        });
    }

    /**
     * Add the without-inactive extension to the builder.
     *
     * @param Builder $builder
     * @return void
     */
    public function addWithoutInactive(Builder $builder): void
    {
        $builder->macro('withoutInactive', function (Builder $builder) {
            $model = $builder->getModel();

            $builder->withoutGlobalScope($this)
                ->where($model->getQualifiedCreatedAtColumn(), '=', 1);

            return $builder;
        });
    }
}

traitの実装

モデルのインスタンスが呼び出すメソッド$model->activate()とかの処理を書く

唯一static::IS_ACTIVEがエラーになるのだけが解決できず、いい感じの解決方法がわからなかった。。

<?php
// app/traits/ActiveStete.php

namespace App\Traits;

use App\Models\Scopes\ActiveStateScope;

/**
 * @method static \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder withInactive(bool $withInactive = true)
 * @method static \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder withoutInactive()
 */
trait ActiveState
{
    public static function bootActiveState(): void
    {
        static::addGlobalScope(new ActiveStateScope());
    }

    public function initializeActiveState(): void
    {
      // initialize何かあれば
      // SoftDeletes.phpだとカラムcのcast設定をしている
      // $this->casts[$this->getDeletedAtColumn()] = 'datetime';
    }

    /**
     * Determine if the model instance is active.
     *
     * @return bool
     */
    public function activate(): bool
    {
        $this->{$this->getIsActiveColumn()} = true;
        return $this->save();
    }

    /**
     * Determine if the model instance is inactive.
     *
     * @return bool
     */
    public function deactivate(): bool
    {
        $this->{$this->getIsActiveColumn()} = false;
        return $this->save();
    }

    /**
     * Get the name of the "is_active" column.
     *
     * @return string
     */
    public function getIsActiveColumn(): string
    {
        return defined('static::IS_ACTIVE') ? static::IS_ACTIVE : 'is_active';
    }

    /**
     * Get the fully qualified "is active" column.
     *
     * @return string
     */
    public function getQualifiedIsActiveColumn(): string
    {
        return $this->qualifyColumn($this->getIsActiveColumn());
    }
}

作成中にあった疑問

  • macroとは
  • extendが呼ばれることはログで確認しているが、誰がどのタイミングで呼んでいるのか
    • 調べきれなかった…わかったら書く
  • static:IS_ACTIVEとしている所が警告が出ていてどうやって消すのかわからない
    • これも調べきれなかった

感想

コードとにらめっこして動作確認してる分には動作しているように見えるが、エッジケースでどういうパターンがあるのかわからず網羅しきれていないが、なんとか形にはできた

今回laravel, eloquentに関して色々調べたが、googleくんが公式ドキュメントよりもブログ記事より優先して表示するようにしてるので、公式を優先して欲しい…

とりあえず動かす事はできたので、composer packageとしてpublishしてみたいな

tags: php