【Laravel9対応】Eloquent の Accessors / Mutators

この記事では Eloquent の Accessors / Mutators について解説していきます。

Accessors / Mutators は Laravel9 でAPIが変更されました。
この記事では Laravel9 以降の Accessors / Mutators について解説しています。

この記事は以下の公式ドキュメントをもとにしています。

Accessors / Mutators とは

Accessors / Mutators は他の言語やライブラリでは計算プロパティや算出プロパティと呼ばれているものです。

Accessors / Mutators を使用するとプロパティの取得や変更前に処理を挟むことができます。

例えば、

  • プロパティの変更時に値を暗号化する
  • プロパティの取得時に暗号化した値を復号して取得する
  • プロパティの変更時にオブジェクトをJSONにしてDBに保存する
  • プロパティの取得時にDBから取得したJSONをオブジェクトに変換する

などの使い方が考えられます。

ロボット
ここからは実際に Accessors / Mutators の使い方を見ていきましょう。

プリミティブの Accessors / Mutators

まずはプリミティブの Accessors / Mutators から見ていきましょう。

ここでは、

  • 値の取得時に文字列を小文字にする
  • 値の変更時に文字列を大文字にする

処理を実装していきます。

プリミティブの Accessors

値の取得時に文字列を小文字にする処理を実装していきます。
class Cat extends Model
{
    use HasFactory;

    protected $fillable = ["name"];

    public function name(): Attribute
    {
        return new Attribute(
            get: fn (string $value) => Str::lower($value)
        );
    }
}

このようにAccessor は

  • 戻り値を Illuminate\Database\Eloquent\Casts\Attribute にする
  • Attribute の get に値を取得する処理を実装する

ことで定義できます。

また、Accessor の関数名を DB のカラムのキャメルケースとすることで自動的に get に渡すコールバックの第1引数に DB に保存されている値を渡してくれます。

例えば、以下のようにすることで

DB のカラム Accessor の関数名
name name
first_name firstName

第1引数にそれぞれ name と first_name の値を渡してくれます。

今回は全て小文字にして取得する処理を実装しているので、第1引数の$valueStr::lowerを適用しています。

プリミティブの Mutators

値の変更時に文字列を大文字にする処理を実装していきます。
class Cat extends Model
{
    use HasFactory;

    protected $fillable = ["name"];

    public function name(): Attribute
    {
        return new Attribute(
            get: fn (string $value) => Str::lower($value),
            set: fn (string $value) => Str::upper($value)
        );
    }
}

Mutator は Accessor を定義したときに使用したAttributeのコンストラクタの set に関数を渡すことで定義できます。

set に渡す関数の第1引数には 代入された値が渡ってくるので、この値を処理することで自由に値を変更できます。

今回は文字列を大文字に変換するため、$valueStr::upperを適用しています。

オブジェクトの Accessors / Mutators

Accessors / Mutators はオブジェクトにも対応しています。

基本的にはプリミティブと同様ですが、オブジェクトの場合はキャッシュされる点が異なります。

まずは Accessor から見ていきましょう。

オブジェクトの Accessors

ここまで使用してきた Cat モデルの Owner を取得する処理を実装していきます。
class Cat extends Model
{
    use HasFactory;

    protected $fillable = ["owner_name", "owner_age"];

    public function owner(): Attribute
    {
        return new Attribute(
            get: fn ($value, array $attributes) => new Owner(
                    name: $attributes["owner_name"],
                    age: $attributes["owner_age"]
                )
        );
    }
}

オブジェクトの Accessor はこのように基本的にはプリミティブの Accessor と同じように定義します。

1点だけ違うのは、$attributesを利用している箇所です。

getに渡す関数の第2引数には、このモデルのDBに保存されている値が配列として渡されます。

例えば以下のようなデータが DB に保存されている場合、

DB のカラム DB に保存されている値
name tama
owner_name owner
owner_age 24

$attributesは以下のようになります。

$attributes = [
    "name" => "tama",
    "owner_name" => "owner",
    "owner_age" => 24
];

オブジェクトの Accessor は、この$attributesからオブジェクトを生成して返すことで定義できます。

オブジェクトの Mutators

プリミティブの Mutator は単純に値を変更して返すだけでしたが、オブジェクトの Mutator の場合は、オブジェクトのどのプロパティをどのカラムに対応するのかをマッピングする必要があります。

ここまで使用してきた Cat モデルの Owner の変更時に DB のカラムにマッピングする処理を実装していきます。
class Cat extends Model
{
    use HasFactory;

    protected $fillable = ["owner_name", "owner_age"];

    public function owner(): Attribute
    {
        return new Attribute(
            get: fn ($value, array $attributes) => new Owner(
                    name: $attributes["owner_name"],
                    age: $attributes["owner_age"]
                ),
            set: fn (Owner $owner) => [
                    "owner_name" => $owner->getName(),
                    "owner_age" => $owner->getAge()
                ]
        );
    }
}

オブジェクトのプロパティを DB のカラムにマッピングするには、このように set に渡す関数から配列を返します。

配列のキーが DB のカラム、配列の値が DB の値に対応しています。

このようにマッピングした値は、saveを実行した際に、自動的に DB に反映されます。

ただし、自動的に DB に反映されるためには、以下の条件があります。

  • Mutator が定義されていること
  • オブジェクトのキャッシュが有効になっていること

次はこのオブジェクトのキャッシュを見ていきましょう。

オブジェクトのキャッシュ

実はオブジェクトのキャッシュはデフォルトで有効になっています。

つまり、先ほど見たCatsaveを実行すると自動的に DB に owner_name と owner_age の値が反映されます。

$cat = Cat::all()->first();
$cat->owner->setName("new_owner");
$cat->owner->setAge(99);
$cat->save();
  • Mutator が定義されていないと自動的に DB に反映されません。

また、オブジェクトのキャッシュが有効になっていると、毎回最初に取得したオブジェクトのインスタンスを返してくれるため、オブジェクトを変更した場合でも変更後のオブジェクトを取得できます。

$cat = Cat::all()->first();
echo $cat->owner->getName();  // owner
echo $cat->owner->getAge();   // 24
$cat->owner->setName("new_owner");
$cat->owner->setAge(99);
echo $cat->owner->getName();  // new_owner
echo $cat->owner->getAge();   // 99

逆に毎回新しいオブジェクトを取得したい場合や、saveを実行した際にオブジェクトへの変更を DB に反映したくない場合は以下のように Accessor の withoutObjectCachingを実行して、オブジェクトのキャッシュを無効にします。

class Cat extends Model
{
    use HasFactory;

    protected $fillable = ["owner_name", "owner_age"];

    public function owner(): Attribute
    {
        return (new Attribute(
            get: fn ($value, array $attributes) => new Owner(
                    name: $attributes["owner_name"],
                    age: $attributes["owner_age"]
                ),
            set: fn (Owner $owner) => [
                    "owner_name" => $owner->getName(),
                    "owner_age" => $owner->getAge()
                ]
        ))->withoutObjectCaching();
    }
}

まとめ

ここまで Laravel9 以降の Accessors / Muattors について見てきました。

Laravel9 以降の Accessors / Muattors では戻り値を IlluminateDatabaseEloquentCastsAttributeにした関数を宣言することで定義できます。

また、関数名を DB のカラムのキャメルケースにすることで get の第1引数に自動的に DB の値を割り当ててくれます。

オブジェクトの Accessors / Mutators の場合には第2引数に渡される$attributesからオブジェクトを作成することで定義することができました。

また、set で DB のカラムとのマッピングを定義してオブジェクトのキャッシュが有効になっていれば、saveを実行した際に、オブジェクトへの変更を自動的に DB に反映してくれることも確認しました。

Accessors / Mutators の解説は以上です。

ここまでご覧いただきありがとうございました。