Eloquent:ミューテタとキャスト
イントロダクション
アクセサ、ミューテタ、および属性のキャストを使用すると、モデルインスタンスでEloquentの属性値を取得または設定する際に、値を変換できます。たとえば、Laravelの暗号化機能を使用してデータベースに保存中の値を暗号化し、Eloquentモデルでその属性にアクセスするときに自動的に復号できます。または、データベースに保存されているJSON文字列を、Eloquentモデルを介してアクセスするときに配列に変換することもできます。
アクセサとミューテタ
アクセサの定義
アクセサは、Eloquentの属性値にアクセスするときに、その値を変換します。アクセサを定義するには、モデルにアクセス可能な属性を表すprotectedメソッドを作成します。このメソッド名は、該当する場合、実際の基になるモデル属性/データベースカラムの「キャメルケース」表現に対応する必要があります。
この例では、first_name属性のアクセサを定義します。first_name属性の値を取得しようとすると、Eloquentによってアクセサが自動的に呼び出されます。すべての属性アクセサ/ミューテタメソッドは、Illuminate\Database\Eloquent\Casts\Attributeの戻り値タイプヒントを宣言する必要があります。
1<?php 2 3namespace App\Models; 4 5use Illuminate\Database\Eloquent\Casts\Attribute; 6use Illuminate\Database\Eloquent\Model; 7 8class User extends Model 9{10 /**11 * Get the user's first name.12 */13 protected function firstName(): Attribute14 {15 return Attribute::make(16 get: fn (string $value) => ucfirst($value),17 );18 }19}
すべてのアクセサメソッドは、属性へのアクセス方法と、オプションでミューテートする方法を定義するAttributeインスタンスを返します。この例では、属性へのアクセス方法のみを定義しています。そのために、Attributeクラスのコンストラクタにget引数を指定します。
ご覧のとおり、カラムの元の値がアクセサに渡され、値を操作して返すことができます。アクセサの値にアクセスするには、モデルインスタンスのfirst_name属性にアクセスするだけです。
1use App\Models\User;2 3$user = User::find(1);4 5$firstName = $user->first_name;
これらの計算された値をモデルの配列/JSON表現に追加したい場合は、それらを追加する必要があります。
複数の属性から値オブジェクトを構築する
アクセサが複数のモデル属性を単一の「値オブジェクト」に変換する必要がある場合があります。そのために、getクロージャは第2引数として$attributesを受け取ることができます。これは自動的にクロージャに提供され、モデルのすべての現在の属性の配列が含まれます。
1use App\Support\Address; 2use Illuminate\Database\Eloquent\Casts\Attribute; 3 4/** 5 * Interact with the user's address. 6 */ 7protected function address(): Attribute 8{ 9 return Attribute::make(10 get: fn (mixed $value, array $attributes) => new Address(11 $attributes['address_line_one'],12 $attributes['address_line_two'],13 ),14 );15}
アクセサのキャッシュ
アクセサから値オブジェクトを返す場合、値オブジェクトに加えられた変更は、モデルが保存される前に自動的にモデルに同期されます。これは、Eloquentがアクセサによって返されたインスタンスを保持し、アクセサが呼び出されるたびに同じインスタンスを返すことができるためです。
1use App\Models\User;2 3$user = User::find(1);4 5$user->address->lineOne = 'Updated Address Line 1 Value';6$user->address->lineTwo = 'Updated Address Line 2 Value';7 8$user->save();
ただし、特に計算負荷が高い場合は、文字列やブール値のようなプリミティブ値のキャッシュを有効にしたい場合があります。これを実現するには、アクセサを定義するときにshouldCacheメソッドを呼び出します。
1protected function hash(): Attribute2{3 return Attribute::make(4 get: fn (string $value) => bcrypt(gzuncompress($value)),5 )->shouldCache();6}
属性のオブジェクトキャッシュ動作を無効にしたい場合は、属性を定義するときにwithoutObjectCachingメソッドを呼び出します。
1/** 2 * Interact with the user's address. 3 */ 4protected function address(): Attribute 5{ 6 return Attribute::make( 7 get: fn (mixed $value, array $attributes) => new Address( 8 $attributes['address_line_one'], 9 $attributes['address_line_two'],10 ),11 )->withoutObjectCaching();12}
ミューテタの定義
ミューテタは、Eloquentの属性値を設定するときに変換します。ミューテタを定義するには、属性を定義するときにset引数を指定します。first_name属性のミューテタを定義しましょう。このミューテタは、モデルのfirst_name属性の値を設定しようとすると自動的に呼び出されます。
1<?php 2 3namespace App\Models; 4 5use Illuminate\Database\Eloquent\Casts\Attribute; 6use Illuminate\Database\Eloquent\Model; 7 8class User extends Model 9{10 /**11 * Interact with the user's first name.12 */13 protected function firstName(): Attribute14 {15 return Attribute::make(16 get: fn (string $value) => ucfirst($value),17 set: fn (string $value) => strtolower($value),18 );19 }20}
ミューテタのクロージャは、属性に設定される値を受け取り、値を操作して操作された値を返すことができます。ミューテタを使用するには、Eloquentモデルのfirst_name属性を設定するだけです。
1use App\Models\User;2 3$user = User::find(1);4 5$user->first_name = 'Sally';
この例では、setコールバックがSallyという値で呼び出されます。ミューテタは、名前にstrtolower関数を適用し、その結果の値をモデルの内部$attributes配列に設定します。
複数の属性をミューテートする
ミューテタが基になるモデルに複数の属性を設定する必要がある場合があります。そのために、setクロージャから配列を返すことができます。配列の各キーは、モデルに関連付けられた基になる属性/データベースカラムに対応している必要があります。
1use App\Support\Address; 2use Illuminate\Database\Eloquent\Casts\Attribute; 3 4/** 5 * Interact with the user's address. 6 */ 7protected function address(): Attribute 8{ 9 return Attribute::make(10 get: fn (mixed $value, array $attributes) => new Address(11 $attributes['address_line_one'],12 $attributes['address_line_two'],13 ),14 set: fn (Address $value) => [15 'address_line_one' => $value->lineOne,16 'address_line_two' => $value->lineTwo,17 ],18 );19}
属性のキャスト
属性のキャストは、モデルに追加のメソッドを定義することなく、アクセサやミューテタと同様の機能を提供します。代わりに、モデルのcastsメソッドは、属性を一般的なデータ型に変換する便利な方法を提供します。
castsメソッドは、キーがキャストされる属性の名前であり、値がカラムをキャストしたい型である配列を返す必要があります。サポートされているキャスト型は次のとおりです。
arrayAsStringable::classbooleancollectiondatedatetimeimmutable_dateimmutable_datetimedecimal:<precision>doubleencryptedencrypted:arrayencrypted:collectionencrypted:objectfloathashedintegerobjectrealstringtimestamp
属性のキャストを実演するために、データベースに整数(0または1)として保存されているis_admin属性をブール値にキャストしてみましょう。
1<?php 2 3namespace App\Models; 4 5use Illuminate\Database\Eloquent\Model; 6 7class User extends Model 8{ 9 /**10 * Get the attributes that should be cast.11 *12 * @return array<string, string>13 */14 protected function casts(): array15 {16 return [17 'is_admin' => 'boolean',18 ];19 }20}
キャストを定義すると、is_admin属性は、データベースに整数として保存されている場合でも、アクセスするたびに常にブール値にキャストされます。
1$user = App\Models\User::find(1);2 3if ($user->is_admin) {4 // ...5}
実行時に新しい一時的なキャストを追加する必要がある場合は、mergeCastsメソッドを使用できます。これらのキャスト定義は、モデルにすでに定義されているキャストに追加されます。
1$user->mergeCasts([2 'is_admin' => 'integer',3 'options' => 'object',4]);
nullの属性はキャストされません。さらに、リレーションシップと同じ名前のキャスト(または属性)を定義したり、モデルの主キーにキャストを割り当てたりしないでください。
Stringableへのキャスト
Illuminate\Database\Eloquent\Casts\AsStringableキャストクラスを使用して、モデルの属性を流暢なIlluminate\Support\Stringableオブジェクトにキャストできます。
1<?php 2 3namespace App\Models; 4 5use Illuminate\Database\Eloquent\Casts\AsStringable; 6use Illuminate\Database\Eloquent\Model; 7 8class User extends Model 9{10 /**11 * Get the attributes that should be cast.12 *13 * @return array<string, string>14 */15 protected function casts(): array16 {17 return [18 'directory' => AsStringable::class,19 ];20 }21}
配列とJSONのキャスト
arrayキャストは、シリアライズされたJSONとして保存されているカラムを扱う場合に特に便利です。たとえば、データベースにシリアライズされたJSONを含むJSONまたはTEXTフィールド型がある場合、その属性にarrayキャストを追加すると、Eloquentモデルでアクセスするときに属性が自動的にPHP配列にデシリアライズされます。
1<?php 2 3namespace App\Models; 4 5use Illuminate\Database\Eloquent\Model; 6 7class User extends Model 8{ 9 /**10 * Get the attributes that should be cast.11 *12 * @return array<string, string>13 */14 protected function casts(): array15 {16 return [17 'options' => 'array',18 ];19 }20}
キャストが定義されると、options属性にアクセスでき、JSONからPHP配列に自動的にデシリアライズされます。options属性の値を設定すると、指定された配列は保存のために自動的にJSONにシリアライズされます。
1use App\Models\User; 2 3$user = User::find(1); 4 5$options = $user->options; 6 7$options['key'] = 'value'; 8 9$user->options = $options;10 11$user->save();
JSON属性の単一フィールドをより簡潔な構文で更新するには、属性を複数代入可能にし、updateメソッドを呼び出すときに->演算子を使用します。
1$user = User::find(1);2 3$user->update(['options->key' => 'value']);
ArrayObjectとCollectionへのキャスト
標準のarrayキャストは多くのアプリケーションで十分ですが、いくつかの欠点があります。arrayキャストはプリミティブ型を返すため、配列のオフセットを直接変更することはできません。たとえば、次のコードはPHPエラーをトリガーします。
1$user = User::find(1);2 3$user->options['key'] = $value;
これを解決するために、LaravelはJSON属性をArrayObjectクラスにキャストするAsArrayObjectキャストを提供しています。この機能は、Laravelのカスタムキャスト実装を使用して実装されており、Laravelが変更されたオブジェクトをインテリジェントにキャッシュおよび変換し、個々のオフセットがPHPエラーをトリガーすることなく変更できるようにします。AsArrayObjectキャストを使用するには、属性に割り当てるだけです。
1use Illuminate\Database\Eloquent\Casts\AsArrayObject; 2 3/** 4 * Get the attributes that should be cast. 5 * 6 * @return array<string, string> 7 */ 8protected function casts(): array 9{10 return [11 'options' => AsArrayObject::class,12 ];13}
同様に、LaravelはJSON属性をLaravelのCollectionインスタンスにキャストするAsCollectionキャストを提供しています。
1use Illuminate\Database\Eloquent\Casts\AsCollection; 2 3/** 4 * Get the attributes that should be cast. 5 * 6 * @return array<string, string> 7 */ 8protected function casts(): array 9{10 return [11 'options' => AsCollection::class,12 ];13}
AsCollectionキャストがLaravelの基本コレクションクラスの代わりにカスタムコレクションクラスをインスタンス化するようにしたい場合は、キャスト引数としてコレクションクラス名を指定できます。
1use App\Collections\OptionCollection; 2use Illuminate\Database\Eloquent\Casts\AsCollection; 3 4/** 5 * Get the attributes that should be cast. 6 * 7 * @return array<string, string> 8 */ 9protected function casts(): array10{11 return [12 'options' => AsCollection::using(OptionCollection::class),13 ];14}
日付のキャスト
デフォルトでは、Eloquentはcreated_atとupdated_atカラムを、PHPのDateTimeクラスを拡張し、便利なメソッドを多数提供するCarbonのインスタンスにキャストします。モデルのcastsメソッド内に追加の日付キャストを定義することで、追加の日付属性をキャストできます。通常、日付はdatetimeまたはimmutable_datetimeキャスト型を使用してキャストする必要があります。
dateまたはdatetimeキャストを定義する場合、日付のフォーマットを指定することもできます。このフォーマットは、モデルが配列またはJSONにシリアライズされるときに使用されます。
1/** 2 * Get the attributes that should be cast. 3 * 4 * @return array<string, string> 5 */ 6protected function casts(): array 7{ 8 return [ 9 'created_at' => 'datetime:Y-m-d',10 ];11}
カラムが日付としてキャストされると、対応するモデル属性の値をUNIXタイムスタンプ、日付文字列(Y-m-d)、日時文字列、またはDateTime / Carbonインスタンスに設定できます。日付の値は正しく変換され、データベースに保存されます。
モデルのすべての日付のデフォルトのシリアライズ形式をカスタマイズするには、モデルにserializeDateメソッドを定義します。このメソッドは、データベースに保存するために日付がどのようにフォーマットされるかには影響しません。
1/**2 * Prepare a date for array / JSON serialization.3 */4protected function serializeDate(DateTimeInterface $date): string5{6 return $date->format('Y-m-d');7}
データベース内でモデルの日付を実際に保存するときに使用する形式を指定するには、モデルに$dateFormatプロパティを定義する必要があります。
1/**2 * The storage format of the model's date columns.3 *4 * @var string5 */6protected $dateFormat = 'U';
日付のキャスト、シリアライズ、タイムゾーン
デフォルトでは、dateおよびdatetimeキャストは、アプリケーションのtimezone構成オプションで指定されたタイムゾーンに関係なく、日付をUTC ISO-8601日付文字列(YYYY-MM-DDTHH:MM:SS.uuuuuuZ)にシリアライズします。このシリアライズ形式を常に使用し、アプリケーションのtimezone構成オプションをデフォルトのUTC値から変更せずに、アプリケーションの日付をUTCタイムゾーンで保存することを強くお勧めします。アプリケーション全体で一貫してUTCタイムゾーンを使用すると、PHPおよびJavaScriptで記述された他の日付操作ライブラリとの相互運用性が最大限に高まります。
dateまたはdatetimeキャストにdatetime:Y-m-d H:i:sなどのカスタムフォーマットが適用される場合、日付のシリアライズ中にCarbonインスタンスの内部タイムゾーンが使用されます。通常、これはアプリケーションのtimezone構成オプションで指定されたタイムゾーンになります。ただし、created_atやupdated_atなどのtimestampカラムはこの動作から除外され、アプリケーションのタイムゾーン設定に関係なく常にUTCでフォーマットされることに注意してください。
Enumのキャスト
Eloquentでは、属性値をPHPのEnumにキャストすることもできます。これを実現するには、モデルのcastsメソッドでキャストしたい属性とenumを指定します。
1use App\Enums\ServerStatus; 2 3/** 4 * Get the attributes that should be cast. 5 * 6 * @return array<string, string> 7 */ 8protected function casts(): array 9{10 return [11 'status' => ServerStatus::class,12 ];13}
モデルでキャストを定義すると、指定された属性は、属性を操作するときに自動的にenumとの間でキャストされます。
1if ($server->status == ServerStatus::Provisioned) {2 $server->status = ServerStatus::Ready;3 4 $server->save();5}
Enumの配列のキャスト
モデルが単一のカラム内にenum値の配列を保存する必要がある場合があります。これを実現するには、Laravelが提供するAsEnumArrayObjectまたはAsEnumCollectionキャストを利用できます。
1use App\Enums\ServerStatus; 2use Illuminate\Database\Eloquent\Casts\AsEnumCollection; 3 4/** 5 * Get the attributes that should be cast. 6 * 7 * @return array<string, string> 8 */ 9protected function casts(): array10{11 return [12 'statuses' => AsEnumCollection::of(ServerStatus::class),13 ];14}
暗号化キャスト
encryptedキャストは、Laravelの組み込みの暗号化機能を使用してモデルの属性値を暗号化します。さらに、encrypted:array、encrypted:collection、encrypted:object、AsEncryptedArrayObject、およびAsEncryptedCollectionキャストは、暗号化されていない対応するものと同様に機能します。ただし、ご想像のとおり、基になる値はデータベースに保存されるときに暗号化されます。
暗号化されたテキストの最終的な長さは予測できず、プレーンテキストの対応するものよりも長くなるため、関連するデータベースカラムがTEXT型以上であることを確認してください。さらに、値はデータベースで暗号化されているため、暗号化された属性値をクエリまたは検索することはできません。
キーのローテーション
ご存知のように、Laravelはアプリケーションのapp構成ファイルで指定されたkey構成値を使用して文字列を暗号化します。通常、この値はAPP_KEY環境変数の値に対応します。アプリケーションの暗号化キーをローテーションする必要がある場合は、新しいキーを使用して暗号化された属性を手動で再暗号化する必要があります。
クエリ時のキャスト
テーブルから生の値をSELECTする場合など、クエリの実行中にキャストを適用する必要がある場合があります。たとえば、次のクエリを考えてみましょう。
1use App\Models\Post;2use App\Models\User;3 4$users = User::select([5 'users.*',6 'last_posted_at' => Post::selectRaw('MAX(created_at)')7 ->whereColumn('user_id', 'users.id')8])->get();
このクエリの結果のlast_posted_at属性は単純な文字列になります。クエリを実行するときにこの属性にdatetimeキャストを適用できれば素晴らしいでしょう。幸いなことに、withCastsメソッドを使用してこれを実現できます。
1$users = User::select([2 'users.*',3 'last_posted_at' => Post::selectRaw('MAX(created_at)')4 ->whereColumn('user_id', 'users.id')5])->withCasts([6 'last_posted_at' => 'datetime'7])->get();
カスタムキャスト
Laravelにはさまざまな組み込みの便利なキャスト型がありますが、独自のキャスト型を定義する必要がある場合もあります。キャストを作成するには、make:cast Artisanコマンドを実行します。新しいキャストクラスはapp/Castsディレクトリに配置されます。
1php artisan make:cast Json
すべてのカスタムキャストクラスはCastsAttributesインターフェイスを実装します。このインターフェイスを実装するクラスは、getメソッドとsetメソッドを定義する必要があります。getメソッドは、データベースからの生の値をキャストされた値に変換する役割を担い、setメソッドは、キャストされた値をデータベースに保存できる生の値に変換する必要があります。例として、組み込みのjsonキャスト型をカスタムキャスト型として再実装します。
1<?php 2 3namespace App\Casts; 4 5use Illuminate\Contracts\Database\Eloquent\CastsAttributes; 6use Illuminate\Database\Eloquent\Model; 7 8class Json implements CastsAttributes 9{10 /**11 * Cast the given value.12 *13 * @param array<string, mixed> $attributes14 * @return array<string, mixed>15 */16 public function get(Model $model, string $key, mixed $value, array $attributes): array17 {18 return json_decode($value, true);19 }20 21 /**22 * Prepare the given value for storage.23 *24 * @param array<string, mixed> $attributes25 */26 public function set(Model $model, string $key, mixed $value, array $attributes): string27 {28 return json_encode($value);29 }30}
カスタムキャスト型を定義したら、そのクラス名を使用してモデル属性にアタッチできます。
1<?php 2 3namespace App\Models; 4 5use App\Casts\Json; 6use Illuminate\Database\Eloquent\Model; 7 8class User extends Model 9{10 /**11 * Get the attributes that should be cast.12 *13 * @return array<string, string>14 */15 protected function casts(): array16 {17 return [18 'options' => Json::class,19 ];20 }21}
値オブジェクトのキャスト
値をプリミティブ型にキャストすることに限定されません。値をオブジェクトにキャストすることもできます。値をオブジェクトにキャストするカスタムキャストの定義は、プリミティブ型へのキャストと非常に似ていますが、setメソッドは、モデルに生の保存可能な値を設定するために使用されるキー/値ペアの配列を返す必要があります。
例として、複数のモデル値を単一のAddress値オブジェクトにキャストするカスタムキャストクラスを定義します。Address値には、lineOneとlineTwoの2つのパブリックプロパティがあると仮定します。
1<?php 2 3namespace App\Casts; 4 5use App\ValueObjects\Address as AddressValueObject; 6use Illuminate\Contracts\Database\Eloquent\CastsAttributes; 7use Illuminate\Database\Eloquent\Model; 8use InvalidArgumentException; 9 10class Address implements CastsAttributes11{12 /**13 * Cast the given value.14 *15 * @param array<string, mixed> $attributes16 */17 public function get(Model $model, string $key, mixed $value, array $attributes): AddressValueObject18 {19 return new AddressValueObject(20 $attributes['address_line_one'],21 $attributes['address_line_two']22 );23 }24 25 /**26 * Prepare the given value for storage.27 *28 * @param array<string, mixed> $attributes29 * @return array<string, string>30 */31 public function set(Model $model, string $key, mixed $value, array $attributes): array32 {33 if (! $value instanceof AddressValueObject) {34 throw new InvalidArgumentException('The given value is not an Address instance.');35 }36 37 return [38 'address_line_one' => $value->lineOne,39 'address_line_two' => $value->lineTwo,40 ];41 }42}
値オブジェクトにキャストする場合、値オブジェクトに加えられた変更は、モデルが保存される前に自動的にモデルに同期されます。
1use App\Models\User;2 3$user = User::find(1);4 5$user->address->lineOne = 'Updated Address Value';6 7$user->save();
値オブジェクトを含むEloquentモデルをJSONまたは配列にシリアライズする予定がある場合は、値オブジェクトにIlluminate\Contracts\Support\ArrayableおよびJsonSerializableインターフェイスを実装する必要があります。
値オブジェクトのキャッシュ
値オブジェクトにキャストされる属性が解決されると、それらはEloquentによってキャッシュされます。したがって、属性に再度アクセスすると、同じオブジェクトインスタンスが返されます。
カスタムキャストクラスのオブジェクトキャッシュ動作を無効にしたい場合は、カスタムキャストクラスにパブリックなwithoutObjectCachingプロパティを宣言できます。
1class Address implements CastsAttributes2{3 public bool $withoutObjectCaching = true;4 5 // ...6}
配列/JSONシリアライズ
EloquentモデルがtoArrayおよびtoJsonメソッドを使用して配列またはJSONに変換されると、カスタムキャストの値オブジェクトも、Illuminate\Contracts\Support\ArrayableおよびJsonSerializableインターフェイスを実装している限り、通常はシリアライズされます。ただし、サードパーティのライブラリによって提供される値オブジェクトを使用する場合、これらのインターフェイスをオブジェクトに追加できない場合があります。
したがって、カスタムキャストクラスが値オブジェクトのシリアライズを担当することを指定できます。そのためには、カスタムキャストクラスがIlluminate\Contracts\Database\Eloquent\SerializesCastableAttributesインターフェイスを実装する必要があります。このインターフェイスは、クラスに値オブジェクトのシリアライズされた形式を返す必要があるserializeメソッドが含まれていることを示しています。
1/**2 * Get the serialized representation of the value.3 *4 * @param array<string, mixed> $attributes5 */6public function serialize(Model $model, string $key, mixed $value, array $attributes): string7{8 return (string) $value;9}
インバウンドキャスト
場合によっては、モデルに設定される値のみを変換し、モデルから属性が取得されるときには何の操作も行わないカスタムキャストクラスを作成する必要があるかもしれません。
インバウンドのみのカスタムキャストはCastsInboundAttributesインターフェイスを実装する必要があり、これにはsetメソッドの定義のみが必要です。make:cast Artisanコマンドを--inboundオプション付きで実行して、インバウンドのみのキャストクラスを生成できます。
1php artisan make:cast Hash --inbound
インバウンドのみのキャストの典型的な例は、「ハッシュ化」キャストです。たとえば、指定されたアルゴリズムを介してインバウンド値をハッシュ化するキャストを定義できます。
1<?php 2 3namespace App\Casts; 4 5use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes; 6use Illuminate\Database\Eloquent\Model; 7 8class Hash implements CastsInboundAttributes 9{10 /**11 * Create a new cast class instance.12 */13 public function __construct(14 protected string|null $algorithm = null,15 ) {}16 17 /**18 * Prepare the given value for storage.19 *20 * @param array<string, mixed> $attributes21 */22 public function set(Model $model, string $key, mixed $value, array $attributes): string23 {24 return is_null($this->algorithm)25 ? bcrypt($value)26 : hash($this->algorithm, $value);27 }28}
キャストパラメータ
カスタムキャストをモデルにアタッチする場合、キャストパラメータは、クラス名から:文字で区切り、複数のパラメータをコンマで区切って指定できます。パラメータは、キャストクラスのコンストラクタに渡されます。
1/** 2 * Get the attributes that should be cast. 3 * 4 * @return array<string, string> 5 */ 6protected function casts(): array 7{ 8 return [ 9 'secret' => Hash::class.':sha256',10 ];11}
Castable
アプリケーションの値オブジェクトが独自のカスタムキャストクラスを定義できるようにしたい場合があります。カスタムキャストクラスをモデルにアタッチする代わりに、Illuminate\Contracts\Database\Eloquent\Castableインターフェイスを実装する値オブジェクトクラスをアタッチすることもできます。
1use App\ValueObjects\Address;2 3protected function casts(): array4{5 return [6 'address' => Address::class,7 ];8}
Castableインターフェイスを実装するオブジェクトは、Castableクラスとの間でキャストを担当するカスタムキャスタークラスのクラス名を返すcastUsingメソッドを定義する必要があります。
1<?php 2 3namespace App\ValueObjects; 4 5use Illuminate\Contracts\Database\Eloquent\Castable; 6use App\Casts\Address as AddressCast; 7 8class Address implements Castable 9{10 /**11 * Get the name of the caster class to use when casting from / to this cast target.12 *13 * @param array<string, mixed> $arguments14 */15 public static function castUsing(array $arguments): string16 {17 return AddressCast::class;18 }19}
Castableクラスを使用する場合でも、castsメソッドの定義で引数を指定できます。引数はcastUsingメソッドに渡されます。
1use App\ValueObjects\Address;2 3protected function casts(): array4{5 return [6 'address' => Address::class.':argument',7 ];8}
Castableと無名キャストクラス
「castable」とPHPの無名クラスを組み合わせることで、値オブジェクトとそのキャストロジックを単一のcastableオブジェクトとして定義できます。これを実現するには、値オブジェクトのcastUsingメソッドから無名クラスを返します。無名クラスはCastsAttributesインターフェイスを実装する必要があります。
1<?php 2 3namespace App\ValueObjects; 4 5use Illuminate\Contracts\Database\Eloquent\Castable; 6use Illuminate\Contracts\Database\Eloquent\CastsAttributes; 7 8class Address implements Castable 9{10 // ...11 12 /**13 * Get the caster class to use when casting from / to this cast target.14 *15 * @param array<string, mixed> $arguments16 */17 public static function castUsing(array $arguments): CastsAttributes18 {19 return new class implements CastsAttributes20 {21 public function get(Model $model, string $key, mixed $value, array $attributes): Address22 {23 return new Address(24 $attributes['address_line_one'],25 $attributes['address_line_two']26 );27 }28 29 public function set(Model $model, string $key, mixed $value, array $attributes): array30 {31 return [32 'address_line_one' => $value->lineOne,33 'address_line_two' => $value->lineTwo,34 ];35 }36 };37 }38}