本文へ移動
Laravel Tips

アクセサ・ミューテタ・キャスト

モデルの属性を読むときや書くときに値を変えるアクセサ・ミューテタと、型を自動で変えるキャストの種類、自分専用のキャストの作り方を説明します。

アクセサ、ミューテタ、キャストは、モデルの属性(カラムの値)を、読むときや書くときに、自動で変えるしくみです。たとえば、データベースに入れるときは値を暗号化して、読むときは自動で元に戻したい場合に使えます。また、データベースにある JSON の文字列を、読むときに配列にしたい場合にも使えます。

  • アクセサ:属性を読むときに、値を変える
  • ミューテタ:属性に書くときに、値を変える
  • キャスト:属性を、決まった型(数・真偽値・日付など)に自動で変える

アクセサとミューテタ#

アクセサを書く#

アクセサは、属性を読むときに、値を変えます。アクセサを書くには、モデルに protected のメソッドを作ります。メソッドの名前は、元の属性(データベースのカラム)の名前を「キャメルケース」(firstName のように単語をつなげる書き方)にしたものにします。

次の例では、first_name 属性のアクセサを書きます。first_name の値を読もうとすると、Eloquent が自動でこのアクセサを呼びます。属性のアクセサやミューテタのメソッドは、すべて、戻り値の型に Illuminate\Database\Eloquent\Casts\Attribute を書く必要があります。

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Get the user's first name.
     */
    protected function firstName(): Attribute
    {
        return Attribute::make(
            get: fn (string $value) => ucfirst($value),
        );
    }
}

アクセサは、どれも Attribute を返します。Attribute には、属性の「読み方」と「書き方」を決めます。書き方は省いてもかまいません。この例では、読み方だけを決めています。そのために、Attribute のコンストラクタ(作るときに呼ばれるメソッド)に get 引数を渡しています。

見てのとおり、カラムの元の値がアクセサに渡されるので、その値を変えて返せます。アクセサの値を読むには、モデルの first_name 属性を読むだけです。

php
use App\Models\User;

$user = User::find(1);

$firstName = $user->first_name;

補足

こうして計算した値を、モデルの配列や JSON に入れたいときは、足す設定が必要です。

複数の属性から1つの値を作る#

アクセサで、複数の属性を、1つの「値オブジェクト」(意味のある値をまとめた入れ物)にしたいことがあります。そのときは、get のクロージャ(名前のない関数)で、2つ目の引数に $attributes を受け取ります。モデルのいまの属性の配列が、自動で渡されます。

php
use App\Support\Address;
use Illuminate\Database\Eloquent\Casts\Attribute;

/**
 * Interact with the user's address.
 */
protected function address(): Attribute
{
    return Attribute::make(
        get: fn (mixed $value, array $attributes) => new Address(
            $attributes['address_line_one'],
            $attributes['address_line_two'],
        ),
    );
}

アクセサのキャッシュ#

アクセサが値オブジェクトを返すときは、その値オブジェクトを書き換えると、モデルを保存する前に、変更が自動でモデルに写されます。これができるのは、Eloquent がアクセサの返したオブジェクトを覚えておき、何度呼ばれても同じオブジェクトを返すからです。

php
use App\Models\User;

$user = User::find(1);

$user->address->lineOne = 'Updated Address Line 1 Value';
$user->address->lineTwo = 'Updated Address Line 2 Value';

$user->save();

文字や真偽値のような基本の値でも、キャッシュ(覚えておくこと)をしてほしいことがあります。とくに、計算に時間がかかるときです。そのときは、アクセサを書くときに shouldCache を呼びます。

php
protected function hash(): Attribute
{
    return Attribute::make(
        get: fn (string $value) => bcrypt(gzuncompress($value)),
    )->shouldCache();
}

反対に、オブジェクトを覚えておく動きを止めたいときは、Attribute を作るときに withoutObjectCaching を呼びます。

php
/**
 * Interact with the user's address.
 */
protected function address(): Attribute
{
    return Attribute::make(
        get: fn (mixed $value, array $attributes) => new Address(
            $attributes['address_line_one'],
            $attributes['address_line_two'],
        ),
    )->withoutObjectCaching();
}

ミューテタを書く#

ミューテタは、属性に値を書くときに、値を変えます。ミューテタを書くには、Attribute を作るときに set 引数を渡します。first_name のミューテタを書いてみます。モデルの first_name に値を入れようとすると、このミューテタが自動で呼ばれます。

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Interact with the user's first name.
     */
    protected function firstName(): Attribute
    {
        return Attribute::make(
            get: fn (string $value) => ucfirst($value),
            set: fn (string $value) => strtolower($value),
        );
    }
}

ミューテタのクロージャは、属性に入れようとしている値を受け取るので、その値を変えて、変えた値を返せます。ミューテタを使うには、Eloquent のモデルの first_name に値を入れるだけです。

php
use App\Models\User;

$user = User::find(1);

$user->first_name = 'Sally';

この例では、set のコールバックが、値 Sally で呼ばれます。ミューテタは、名前に strtolower を使い、その結果を、モデルの内部の $attributes の配列に入れます。

複数の属性に書く#

ミューテタで、元になっているモデルの複数の属性に、値を入れたいことがあります。そのときは、set のクロージャから、配列を返します。配列のキーは、モデルの元の属性(データベースのカラム)の名前にします。

php
use App\Support\Address;
use Illuminate\Database\Eloquent\Casts\Attribute;

/**
 * Interact with the user's address.
 */
protected function address(): Attribute
{
    return Attribute::make(
        get: fn (mixed $value, array $attributes) => new Address(
            $attributes['address_line_one'],
            $attributes['address_line_two'],
        ),
        set: fn (Address $value) => [
            'address_line_one' => $value->lineOne,
            'address_line_two' => $value->lineTwo,
        ],
    );
}
書き方 働き
Attribute::make(get: ...) 属性を読むときの変え方を決める(アクセサ)
Attribute::make(set: ...) 属性に書くときの変え方を決める(ミューテタ)
->shouldCache() アクセサの結果を、基本の値でも覚えておく
->withoutObjectCaching() オブジェクトを覚えておく動きを止める

属性のキャスト#

キャストを使うと、アクセサやミューテタと同じようなことが、属性ごとのメソッドを書かずにできます。モデルの casts メソッドに書くだけで、属性をよく使う型に変えられます。

casts メソッドは、配列を返します。キーは変える属性の名前で、値は変えたい型です。使えるキャストの型は、次のとおりです。

型 変わる先
array JSON を PHP の配列に
AsFluent::class Illuminate\Support\Fluent のオブジェクトに
AsStringable::class Illuminate\Support\Stringable のオブジェクトに
AsUri::class URI のオブジェクトに
AsVector::class データベースのベクトルのカラムを、PHP の配列に
boolean 真偽値に
collection Laravel のコレクションに
date 日付(Carbon)に
datetime 日時(Carbon)に
immutable_date 変えられない日付に
immutable_datetime 変えられない日時に
decimal:<precision> 小数点以下の桁数を決めた数に
double 倍精度の小数に
encrypted 暗号化して保存し、読むときに元に戻す
encrypted:array 暗号化する array
encrypted:collection 暗号化する collection
encrypted:object 暗号化する object
float 小数に
hashed ハッシュ(元に戻せない形)にして保存する
integer 整数に
object オブジェクトに
real 小数に
string 文字に
timestamp UNIX タイムスタンプ(秒の数)に

キャストを試すため、データベースに整数(0 か 1)で入っている is_admin 属性を、真偽値に変えてみます。

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Get the attributes that should be cast.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'is_admin' => 'boolean',
        ];
    }
}

キャストを書くと、is_admin は、データベースに整数で入っていても、読むときに、いつも真偽値になります。

php
$user = App\Models\User::find(1);

if ($user->is_admin) {
    // ...
}

プログラムの途中で、一時的にキャストを足したいときは、mergeCasts を使います。モデルにすでにあるキャストに、これらの設定が足されます。

php
$user->mergeCasts([
    'is_admin' => 'integer',
    'options' => 'object',
]);

注意

null の属性は、キャストされません。また、リレーションと同じ名前のキャスト(や属性)を書いてはいけません。モデルの主キーにも、キャストを付けてはいけません。

Stringable にキャストする#

Illuminate\Database\Eloquent\Casts\AsStringable を使うと、モデルの属性を、Illuminate\Support\Stringable のオブジェクト(文字を、メソッドをつなげて扱える入れ物)にキャストできます。

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Casts\AsStringable;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Get the attributes that should be cast.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'directory' => AsStringable::class,
        ];
    }
}

配列と JSON のキャスト#

array キャストは、JSON の形で保存されているカラムを扱うときに、とくに便利です。たとえば、データベースに、JSON が入った JSON か TEXT のカラムがあるとき、その属性に array キャストを付けると、モデルから読むときに、自動で PHP の配列になります。

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Get the attributes that should be cast.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'options' => 'array',
        ];
    }
}

キャストを書くと、options を読むとき、JSON から PHP の配列に、自動で変わります。options に値を入れるときは、渡した配列が、自動で JSON にされて保存されます。

php
use App\Models\User;

$user = User::find(1);

$options = $user->options;

$options['key'] = 'value';

$user->options = $options;

$user->save();

JSON の属性の1つの項目だけを、もっと短く更新したいときは、その属性を一括で入れてよいことにして、update に -> を使います。

php
$user = User::find(1);

$user->update(['options->key' => 'value']);

JSON と Unicode#

配列の属性を JSON として保存するとき、日本語などの Unicode の文字をエスケープ(\u3042 のような記号の形に置きかえること)せずにそのまま残したいときは、json:unicode キャストを使います。

php
/**
 * Get the attributes that should be cast.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'options' => 'json:unicode',
    ];
}

ArrayObject とコレクションのキャスト#

ふつうの array キャストは、多くのアプリには十分ですが、弱点もあります。array キャストは基本の型を返すので、配列の中の1つの項目を、直接書き換えられません。たとえば、次のコードは PHP のエラーになります。

php
$user = User::find(1);

$user->options['key'] = $value;

これを解決するため、Laravel には AsArrayObject キャストがあります。JSON の属性を、ArrayObject クラス(配列のように使えるオブジェクト)に変えます。このキャストは、あとで説明する「自分専用のキャスト」のしくみで作られています。書き換えたオブジェクトを Laravel がうまく覚えて変換するので、項目を1つずつ書き換えても PHP のエラーになりません。使うには、属性に付けるだけです。

php
use Illuminate\Database\Eloquent\Casts\AsArrayObject;

/**
 * Get the attributes that should be cast.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'options' => AsArrayObject::class,
    ];
}

同じように、JSON の属性を Laravel のコレクションにキャストする AsCollection もあります。

php
use Illuminate\Database\Eloquent\Casts\AsCollection;

/**
 * Get the attributes that should be cast.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'options' => AsCollection::class,
    ];
}

既定では、AsArrayObject や AsCollection でキャストした属性に null を入れると、データベースには、JSON の null が保存されます。null を、SQL 本来の NULL として保存したいときは、キャストを書くときに nullable を呼びます。

php
use Illuminate\Database\Eloquent\Casts\AsCollection;

/**
 * Get the attributes that should be cast.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'options' => AsCollection::nullable(),
    ];
}

nullable は、自分専用のコレクションのクラスとも組み合わせられます。

php
'options' => AsCollection::nullable(OptionCollection::class),

AsArrayObject にも、同じ動きの nullable があります。

AsCollection で、Laravel の基本のコレクションではなく、自分のコレクションのクラスを作らせたいときは、キャストの引数に、コレクションのクラスの名前を渡します。

php
use App\Collections\OptionCollection;
use Illuminate\Database\Eloquent\Casts\AsCollection;

/**
 * Get the attributes that should be cast.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'options' => AsCollection::using(OptionCollection::class),
    ];
}

of を使うと、コレクションの1つ1つの項目を、決めたクラスのオブジェクトに変えられます。変えるときは、コレクションの mapInto メソッドが使われます。

php
use App\ValueObjects\Option;
use Illuminate\Database\Eloquent\Casts\AsCollection;

/**
 * Get the attributes that should be cast.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'options' => AsCollection::of(Option::class)
    ];
}

項目をオブジェクトに変えるときは、そのオブジェクトに、Illuminate\Contracts\Support\Arrayable と JsonSerializable のインターフェイス(守るべき決まり)を持たせます。この2つで、オブジェクトを JSON としてデータベースにどう保存するかを決めます。

php
<?php

namespace App\ValueObjects;

use Illuminate\Contracts\Support\Arrayable;
use JsonSerializable;

class Option implements Arrayable, JsonSerializable
{
    public string $name;
    public mixed $value;
    public bool $isLocked;

    /**
     * Create a new Option instance.
     */
    public function __construct(array $data)
    {
        $this->name = $data['name'];
        $this->value = $data['value'];
        $this->isLocked = $data['is_locked'];
    }

    /**
     * Get the instance as an array.
     *
     * @return array{name: string, data: string, is_locked: bool}
     */
    public function toArray(): array
    {
        return [
            'name' => $this->name,
            'value' => $this->value,
            'is_locked' => $this->isLocked,
        ];
    }

    /**
     * Specify the data which should be serialized to JSON.
     *
     * @return array{name: string, data: string, is_locked: bool}
     */
    public function jsonSerialize(): array
    {
        return $this->toArray();
    }
}
キャスト 働き
array JSON を配列に(中の項目は直接書き換えられない)
json:unicode Unicode の文字をエスケープせずに、JSON で保存する
AsArrayObject::class JSON を ArrayObject に(中の項目も書き換えられる)
AsCollection::class JSON をコレクションに
::nullable() null を、SQL の NULL として保存する
::using(...) 自分のコレクションのクラスを使う
::of(...) 項目を、決めたクラスに変える

ベクトルのキャスト#

Illuminate\Database\Eloquent\Casts\AsVector を使うと、データベースのベクトルのカラム(意味を数の並びにしたもの)を、PHP の配列に、行き来してキャストできます。

php
use Illuminate\Database\Eloquent\Casts\AsVector;

/**
 * Get the attributes that should be cast.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'embedding' => AsVector::class,
    ];
}

属性に値を入れるとき、このキャストは、PHP の配列か、Laravel のコレクションのような Arrayable を受け取ります。属性を読むときは、小数の配列を返します。

バイナリのキャスト#

Eloquent のモデルに、自動で増える ID のカラムに加えて、バイナリ型の uuid や ulid のカラムがあるときは、AsBinary キャストで、値と、そのバイナリの形を、自動で行き来できます。

php
use Illuminate\Database\Eloquent\Casts\AsBinary;

/**
 * Get the attributes that should be cast.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'uuid' => AsBinary::uuid(),
        'ulid' => AsBinary::ulid(),
    ];
}

モデルにキャストを書いたら、UUID / ULID の属性に、オブジェクトか文字を入れられます。Eloquent が、自動でバイナリの形にします。属性を読むと、いつも、ふつうの文字が返ります。

php
use Illuminate\Support\Str;

$user->uuid = Str::uuid();

return $user->uuid;

// "6e8cdeed-2f32-40bd-b109-1e4405be2140"

日付のキャスト#

既定では、Eloquent は、created_at と updated_at を、Carbon のオブジェクトにキャストします。Carbon は、PHP の DateTime を受け継いだ、便利なメソッドがたくさんあるクラスです。ほかの日付の属性も、モデルの casts に日付のキャストを書けば、キャストできます。ふつうは、datetime か immutable_datetime の型を使います。

date や datetime のキャストを書くとき、日付の形も決められます。この形は、モデルを配列や JSON に変えるときに使われます。

php
/**
 * Get the attributes that should be cast.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'created_at' => 'datetime:Y-m-d',
    ];
}

日付としてキャストしたカラムには、UNIX タイムスタンプ(1970年1月1日からの秒数)、日付の文字(Y-m-d)、日時の文字、DateTime / Carbon のオブジェクトのどれでも、入れられます。日付の値は、正しく変えられて、データベースに保存されます。

モデルのすべての日付の、既定の変換の形を変えたいときは、モデルに serializeDate を書きます。この設定は、データベースに保存するときの日付の形には、影響しません。

php
/**
 * Prepare a date for array / JSON serialization.
 */
protected function serializeDate(DateTimeInterface $date): string
{
    return $date->format('Y-m-d');
}

モデルの日付を、データベースに実際に保存するときの形を決めたいときは、モデルの Table 属性の dateFormat 引数を使います。

php
use Illuminate\Database\Eloquent\Attributes\Table;

#[Table(dateFormat: 'U')]
class Flight extends Model
{
    // ...
}

日付のキャスト・変換とタイムゾーン#

既定では、date と datetime のキャストは、日付を UTC(世界の基準の時刻)の ISO-8601 の形の文字(YYYY-MM-DDTHH:MM:SS.uuuuuuZ)に変換します。アプリの timezone 設定が何であっても同じです。この形をそのまま使い、timezone 設定も既定の UTC のままにして、日付を UTC で保存することが強くすすめられています。アプリ全体で UTC にそろえておくと、PHP や JavaScript のほかの日付のライブラリと、いちばんうまくやりとりできるからです。

datetime:Y-m-d H:i:s のように、date や datetime に自分で形を付けたときは、変換に Carbon のオブジェクトが持つタイムゾーンが使われます。ふつうは、アプリの timezone 設定のタイムゾーンです。ただし、created_at や updated_at のような timestamp のカラムは例外です。timezone 設定に関係なく、いつも UTC の時刻で書き出されます。

Enum のキャスト#

Eloquent では、属性の値を PHP の Enum(決まった選択肢だけを持つ型)にもキャストできます。モデルの casts に、属性と Enum を書きます。

php
use App\Enums\ServerStatus;

/**
 * Get the attributes that should be cast.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'status' => ServerStatus::class,
    ];
}

モデルにキャストを書くと、決めた属性は、使うときに、自動で Enum と行き来してキャストされます。

php
if ($server->status == ServerStatus::Provisioned) {
    $server->status = ServerStatus::Ready;

    $server->save();
}

Enum の配列をキャストする#

1つのカラムに、Enum の値の配列を保存したいことがあります。Laravel の AsEnumArrayObject か AsEnumCollection のキャストを使います。

php
use App\Enums\ServerStatus;
use Illuminate\Database\Eloquent\Casts\AsEnumCollection;

/**
 * Get the attributes that should be cast.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'statuses' => AsEnumCollection::of(ServerStatus::class),
    ];
}

暗号化のキャスト#

encrypted キャストは、Laravel の暗号化(鍵があれば元に戻せる形に変えること)の機能で、モデルの属性の値を暗号化します。encrypted:array・encrypted:collection・encrypted:object・AsEncryptedArrayObject・AsEncryptedCollection もあります。使い方は、encrypted の付かない同じ名前のキャストと同じです。ちがうのは、データベースに暗号化して保存されることです。

暗号化した文字の長さは、前もって分からず、元の文字より長くなります。そのため、対応するカラムは TEXT 型か、それより大きい型にしてください。また、データベースの値は暗号化されているので、暗号化した属性の値を、問い合わせたり検索したりはできません。

鍵を入れ替える#

Laravel は、アプリの app の設定ファイルの key の値(ふつうは、環境変数 APP_KEY の値)を使って、文字を暗号化します。アプリの暗号化の鍵を入れ替える必要があるときは、順を追って入れ替えられます。

問い合わせのときにキャストする#

問い合わせを動かすときに、キャストしたいことがあります。表から、そのままの値を取り出すときなどです。たとえば、次の問い合わせです。

php
use App\Models\Post;
use App\Models\User;

$users = User::select([
    'users.*',
    'last_posted_at' => Post::selectRaw('MAX(created_at)')
        ->whereColumn('user_id', 'users.id')
])->get();

この問い合わせの結果の last_posted_at は、ただの文字です。この属性に、問い合わせを動かすときに、datetime キャストを付けられたら便利です。withCasts を使えば、できます。

php
$users = User::select([
    'users.*',
    'last_posted_at' => Post::selectRaw('MAX(created_at)')
        ->whereColumn('user_id', 'users.id')
])->withCasts([
    'last_posted_at' => 'datetime'
])->get();

自分専用のキャスト#

Laravel には、いろいろな便利なキャストの型が最初からありますが、自分で型を作りたいこともあります。キャストを作るには、make:cast という Artisan コマンドを使います。新しいキャストのクラスは、app/Casts に置かれます。

bash
php artisan make:cast AsJson

自分専用のキャストのクラスは、どれも CastsAttributes インターフェイスを満たします。そのため、get と set の2つのメソッドを書く必要があります。get は、データベースにある元の値を、キャストした値に変えます。set はその反対で、キャストした値を、データベースに保存できる形に戻します。例として、最初からある json キャストを、自分専用のキャストとして作り直してみます。

php
<?php

namespace App\Casts;

use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;

class AsJson implements CastsAttributes
{
    /**
     * Cast the given value.
     *
     * @param  array<string, mixed>  $attributes
     * @return array<string, mixed>
     */
    public function get(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): array {
        return json_decode($value, true);
    }

    /**
     * Prepare the given value for storage.
     *
     * @param  array<string, mixed>  $attributes
     */
    public function set(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): string {
        return json_encode($value);
    }
}

自分専用のキャストを書いたら、クラスの名前で、モデルの属性に付けられます。

php
<?php

namespace App\Models;

use App\Casts\AsJson;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Get the attributes that should be cast.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'options' => AsJson::class,
        ];
    }
}

値オブジェクトへのキャスト#

キャストの先は、基本の型だけではありません。オブジェクトにもキャストできます。書き方は、基本の型のときとほとんど同じです。ちがうのは、値オブジェクトが複数のカラムにまたがるときです。そのときの set は、「カラムの名前 => 保存する値」の配列を返します。値オブジェクトが1つのカラムにしか関係しないなら、保存する値をそのまま返します。

例として、モデルの複数の値を、1つの Address(住所)の値オブジェクトにキャストする、自分専用のキャストのクラスを書きます。Address には、lineOne と lineTwo という、2つの公開プロパティがあるとします。

php
<?php

namespace App\Casts;

use App\ValueObjects\Address;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
use InvalidArgumentException;

class AsAddress implements CastsAttributes
{
    /**
     * Cast the given value.
     *
     * @param  array<string, mixed>  $attributes
     */
    public function get(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): Address {
        return new Address(
            $attributes['address_line_one'],
            $attributes['address_line_two']
        );
    }

    /**
     * Prepare the given value for storage.
     *
     * @param  array<string, mixed>  $attributes
     * @return array<string, string>
     */
    public function set(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): array {
        if (! $value instanceof Address) {
            throw new InvalidArgumentException('The given value is not an Address instance.');
        }

        return [
            'address_line_one' => $value->lineOne,
            'address_line_two' => $value->lineTwo,
        ];
    }
}

値オブジェクトにキャストしたときも、その値オブジェクトを書き換えると、モデルを保存する前に、変更が自動でモデルに写されます。

php
use App\Models\User;

$user = User::find(1);

$user->address->lineOne = 'Updated Address Value';

$user->save();

補足

値オブジェクトを持つ Eloquent のモデルを、JSON や配列にしたいときは、値オブジェクトに、Illuminate\Contracts\Support\Arrayable と JsonSerializable のインターフェイスを持たせます。

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

値オブジェクトにキャストした属性が読まれると、Eloquent が覚えておきます。そのため、同じ属性を、もう一度読むと、同じオブジェクトが返ります。

自分専用のキャストのクラスで、このオブジェクトを覚えておく動きを止めたいときは、そのクラスに、公開の withoutObjectCaching プロパティを書きます。

php
class AsAddress implements CastsAttributes
{
    public bool $withoutObjectCaching = true;

    // ...
}

配列や JSON への変換#

Eloquent のモデルを toArray や toJson で、配列や JSON にするとき、自分専用のキャストの値オブジェクトも、Illuminate\Contracts\Support\Arrayable と JsonSerializable を持っていれば、ふつうは一緒に変換されます。ただし、ほかの会社が作ったライブラリの値オブジェクトには、これらのインターフェイスを足せないことがあります。

そのときは、自分専用のキャストのクラスに、値オブジェクトの変換を任せられます。クラスに Illuminate\Contracts\Database\Eloquent\SerializesCastableAttributes インターフェイスを持たせ、serialize メソッドを書きます。serialize は、値オブジェクトを変換した形を返します。

php
/**
 * Get the serialized representation of the value.
 *
 * @param  array<string, mixed>  $attributes
 */
public function serialize(
    Model $model,
    string $key,
    mixed $value,
    array $attributes,
): string {
    return (string) $value;
}

書くときだけ変えるキャスト(インバウンド)#

モデルに値を入れるときだけ値を変えて、モデルから読むときは何もしない、自分専用のキャストを書きたいことがあります。

書くときだけのキャストは、CastsInboundAttributes インターフェイスを持たせます。必要なのは set だけです。make:cast に --inbound を付けると、書くときだけのキャストのクラスが作られます。

bash
php artisan make:cast AsHash --inbound

書くときだけのキャストの典型は、「ハッシュ」(元に戻せない形に変えること)のキャストです。たとえば、入ってくる値を、決めたアルゴリズムでハッシュにするキャストを書けます。

php
<?php

namespace App\Casts;

use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes;
use Illuminate\Database\Eloquent\Model;

class AsHash implements CastsInboundAttributes
{
    /**
     * Create a new cast class instance.
     */
    public function __construct(
        protected string|null $algorithm = null,
    ) {}

    /**
     * Prepare the given value for storage.
     *
     * @param  array<string, mixed>  $attributes
     */
    public function set(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): string {
        return is_null($this->algorithm)
            ? bcrypt($value)
            : hash($this->algorithm, $value);
    }
}

キャストに引数を渡す#

自分専用のキャストをモデルに付けるとき、クラスの名前のあとに : を付けて、引数を書けます。引数が複数なら、, で区切ります。引数は、キャストのクラスのコンストラクタに渡されます。

php
/**
 * Get the attributes that should be cast.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'secret' => AsHash::class.':sha256',
    ];
}

キャストした値を比べる#

モデルを更新するとき、Eloquent は前の値と新しい値を比べて、変わった値だけをデータベースに保存します。この「変わったかどうか」の比べ方を、自分で決めることもできます。自分専用のキャストのクラスに、Illuminate\Contracts\Database\Eloquent\ComparesCastableAttributes インターフェイスを持たせ、compare メソッドを書きます。compare は、渡された2つの値を同じとみなすなら true を返します。

php
/**
 * Determine if the given values are equal.
 *
 * @param  \Illuminate\Database\Eloquent\Model  $model
 * @param  string  $key
 * @param  mixed  $firstValue
 * @param  mixed  $secondValue
 * @return bool
 */
public function compare(
    Model $model,
    string $key,
    mixed $firstValue,
    mixed $secondValue
): bool {
    return $firstValue === $secondValue;
}

キャスト可能なオブジェクト(Castable)#

値オブジェクトの側で、「自分はこのキャストで変換してね」と決めておくこともできます。そうすると、モデルにはキャストのクラスではなく、値オブジェクトのクラスを直接付けられます。値オブジェクトのクラスには、Illuminate\Contracts\Database\Eloquent\Castable インターフェイスを持たせます。

php
use App\ValueObjects\Address;

protected function casts(): array
{
    return [
        'address' => Address::class,
    ];
}

Castable インターフェイスを持つクラスには、castUsing メソッドを書きます。castUsing は、そのクラスとの行き来を受け持つキャストのクラスの名前を返します。

php
<?php

namespace App\ValueObjects;

use Illuminate\Contracts\Database\Eloquent\Castable;
use App\Casts\AsAddress;

class Address implements Castable
{
    /**
     * Get the name of the caster class to use when casting from / to this cast target.
     *
     * @param  array<string, mixed>  $arguments
     */
    public static function castUsing(array $arguments): string
    {
        return AsAddress::class;
    }
}

Castable のクラスを使うときも、casts に引数を書けます。引数は、castUsing に渡されます。

php
use App\ValueObjects\Address;

protected function casts(): array
{
    return [
        'address' => Address::class.':argument',
    ];
}

Castable と無名クラス#

「castable」と、PHP の無名クラス(名前のないクラス)を組み合わせると、値オブジェクトと、そのキャストの処理を、1つの castable なオブジェクトにまとめられます。値オブジェクトの castUsing から、無名クラスを返します。無名クラスは CastsAttributes インターフェイスを持たせます。

php
<?php

namespace App\ValueObjects;

use Illuminate\Contracts\Database\Eloquent\Castable;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;

class Address implements Castable
{
    // ...

    /**
     * Get the caster class to use when casting from / to this cast target.
     *
     * @param  array<string, mixed>  $arguments
     */
    public static function castUsing(array $arguments): CastsAttributes
    {
        return new class implements CastsAttributes
        {
            public function get(
                Model $model,
                string $key,
                mixed $value,
                array $attributes,
            ): Address {
                return new Address(
                    $attributes['address_line_one'],
                    $attributes['address_line_two']
                );
            }

            public function set(
                Model $model,
                string $key,
                mixed $value,
                array $attributes,
            ): array {
                return [
                    'address_line_one' => $value->lineOne,
                    'address_line_two' => $value->lineTwo,
                ];
            }
        };
    }
}
インターフェイス 必要なメソッドと働き
CastsAttributes get と set。読むときと書くときの変え方
CastsInboundAttributes set だけ。書くときだけ変える
SerializesCastableAttributes serialize。配列や JSON にするときの形を決める
ComparesCastableAttributes compare。2つの値が等しいかを決める
Castable castUsing。使うキャストのクラスを返す

関連するページ#

公式ドキュメント(英語)

2026年10月5日時点の内容をもとに、日本語でまとめています。

ページの一覧