Laravel Eloquentで値オブジェクトを使う

こんにちは。今回はLaravelのEloquentで値オブジェクトを実装する方法を紹介します。

値オブジェクトとは

値オブジェクトとはエンティティのフィールドに関する業務ルールを扱うオブジェクト(クラス)のことです。Laravelで言い換えると、エンティティ=Eloquentモデル。フィールド=テーブルのカラム。と位置づけられます。フィールド名のクラスを作成し、フィールド特有の業務ルールを記述します。

題材

Todoを管理するTodo Eloquentモデルを作成します。Todoモデルは以下のプロパティを持ちます。

  • todo (todoの内容)
  • due_date(期日)
  • is_finished (完了)

そしてTodoの要件として「due_date(期日)を平日にしか設定できない。」が求められてるとします。この要件はdue_dateの関心事といえます。due_dateをバリューオブジェクトにし、due_dateに設定された値が平日かどうかを確かめるisWeekDayというロジックを実装したいと思います。

作り方

値オブジェクトの作成

due_date値オブジェクトを作成します。クラス名はDueDateVOとします。コンストラクタで値を受け取り、内部変数に保持します。isWeekDay関数を作成し、値が平日かどうかを判定し、論理値を返します。

<?php

namespace App\Models\ValueObjects;

use Carbon\Carbon;

class DueDateVO
{
    private $value;

    public function __construct($value){
        $this->value = Carbon::parse($value);
    }

    // getter
    public function getValue(){
        return $this->value;
    }

    // 設定した日が平日かどうか
    public function isWeekDay():bool {
        return $this->value->isWeekDay();
    }
}

Castを作成する

値オブジェクトの作成が終わったら、TodoモデルにDueDateVOクラスを保持させます。保持させるためにCastの機能を利用します。artisan make:castコマンドでDueDateのCastオブジェクトを生成します。

$ php artisan make:cast DueDate

作成されたCastのクラスにget, setを実装します。 getでは新たにEloquentのDueDateVOを生成し、due_dateフィールドの値を設定して返します。setはDueDateVOオブジェクトを受け取り、その中の値を取り出してdue_dateフィールドに設定します。なおsetでは渡されたオブジェクトがDueDateVOであることをチェックします。

<?php

namespace App\Casts;

use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use App\Models\ValueObjects\DueDateVO;
use InvalidArgumentException;

class DueDate implements CastsAttributes
{
    /**
     * Cast the given value.
     *
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @param  string  $key
     * @param  mixed  $value
     * @param  array  $attributes
     * @return mixed
     */
    public function get($model, string $key, $value, array $attributes)
    {
        return new DueDateVO($attributes['due_date']);
    }

    /**
     * Prepare the given value for storage.
     *
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @param  string  $key
     * @param  mixed  $value
     * @param  array  $attributes
     * @return mixed
     */
    public function set($model, string $key, $value, array $attributes)
    {
        if (! $value instanceof DueDateVO) {
            throw new InvalidArgumentException('与えられた引数はDueDateのインスタンスではありません。');
        }

        return $value->getValue();
    }
}

EloquentにCastを設定する

作成したDueDateのCastオブジェクトをTodoモデルに設定します。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use App\Casts\DueDate;

class Todo extends Model
{
    use HasFactory;

    protected $casts = [
        'due_date' => DueDate::class, // DueDate Castクラスを指定
    ];
}

Tinkerで動作確認する

tinkerでDueDateVO値オブジェクトのisWeekDayメソッドが正しく動作することを確認します。

// まずはオブジェクトをインスタンス化
>>> use App\Models\Todo;
>>> use App\Models\ValueObjects\DueDateVO;
>>> $todo = new Todo();
=> App\Models\Todo {#3722}

//  DueDateに2021-11-11を設定(このときDueDateVOをNewして渡します。)
>>> $todo->due_date = new DueDateVO('2021-11-11');
=> App\Models\ValueObjects\DueDateVO {#3731}

// DueDateVOが持つisWeekDayを呼び出します。
>>> $todo->due_date->isWeekDay();
// 平日なのでtrueが返る
=> true

// 試しに休日を設定するとfalseが返ります。
>>> $todo->due_date = new DueDateVO('2021-11-14');
=> App\Models\ValueObjects\DueDateVO {#3729}
>>> $todo->due_date->isWeekDay();
=> false

まとめ

今回はCast機能を使用して値オブジェクトをEloquentモデルで扱う方法を説明しました。値オブジェクトを使うようにすると、値に関する関心事をカプセル化することができるので、コードの見通しが格段によくなり、再利用性も高まります。値オブジェクト自体はドメイン駆動開発(DDD)の考え方ですが、値オブジェクト単体でも十分に利用価値があります。是非利用してみてください。

Laravel

Posted by kobainmac