跳到內容

Eloquent:Mutators & Casting

簡介

訪問器、修改器和屬性 Cast 讓您可以在檢索或設定模型實例時轉換 Eloquent 屬性值。例如,您可能想使用 Laravel 加密器 來加密儲存在資料庫中的值,然後在您於 Eloquent 模型上訪問該屬性時自動解密。或者,您可能想要將儲存在資料庫中的 JSON 字串轉換為 PHP 陣列,以便透過您的 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(): Attribute
14 {
15 return Attribute::make(
16 get: fn (string $value) => ucfirst($value),
17 );
18 }
19}

所有訪問器方法都會返回一個 Attribute 實例,該實例定義了屬性將如何被訪問,以及(可選地)如何被修改。在此範例中,我們僅定義屬性將如何被訪問。為此,我們將 get 參數提供給 Attribute 類別建構子。

如您所見,欄位的原始值會傳遞給訪問器,讓您可以操作並傳回該值。要訪問訪問器的值,您只需訪問模型實例上的 first_name 屬性即可。

1use App\Models\User;
2 
3$user = User::find(1);
4 
5$firstName = $user->first_name;

如果您希望將這些計算值添加到模型的陣列 / JSON 表示形式中,您將需要附加它們

從多個屬性建構 Value Objects

有時您的訪問器可能需要將多個模型屬性轉換為單個「value object」。為此,您的 get 閉包可以接受第二個參數 $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}

訪問器快取

當從訪問器傳回 value objects 時,對 value object 所做的任何變更都將在模型儲存之前自動同步回模型。這是可能的,因為 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(): Attribute
2{
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(): Attribute
14 {
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';

在此範例中,將使用值 Sally 呼叫 set 回呼。然後,修改器將把 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}

屬性 Cast

屬性 Cast 提供了與訪問器和修改器類似的功能,而無需您在模型上定義任何額外的方法。相反,您模型的 casts 方法提供了一種方便的方式,可將屬性轉換為常見的資料類型。

casts 方法應傳回一個陣列,其中鍵是要 Cast 的屬性名稱,而值是您希望將欄位 Cast 為的類型。支援的 Cast 類型為:

  • array
  • AsStringable::class
  • boolean
  • collection
  • date
  • datetime
  • immutable_date
  • immutable_datetime
  • decimal:<precision>
  • double
  • encrypted
  • encrypted:array
  • encrypted:collection
  • encrypted:object
  • float
  • hashed
  • integer
  • object
  • real
  • string
  • timestamp

為了示範屬性 Cast,讓我們將儲存在資料庫中為整數 (01) 的 is_admin 屬性 Cast 為布林值。

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(): array
15 {
16 return [
17 'is_admin' => 'boolean',
18 ];
19 }
20}

定義 Cast 後,當您訪問 is_admin 屬性時,它將始終被 Cast 為布林值,即使底層值在資料庫中儲存為整數。

1$user = App\Models\User::find(1);
2 
3if ($user->is_admin) {
4 // ...
5}

如果您需要在執行時新增新的暫時性 Cast,您可以使用 mergeCasts 方法。這些 Cast 定義將被添加到模型上已定義的任何 Cast 中。

1$user->mergeCasts([
2 'is_admin' => 'integer',
3 'options' => 'object',
4]);

null 的屬性將不會被 Cast。此外,您永遠不應定義與關聯關係同名的 Cast(或屬性),也不要將 Cast 指派給模型的主鍵。

Stringable Cast

您可以使用 Illuminate\Database\Eloquent\Casts\AsStringable Cast 類別將模型屬性 Cast 為 fluent 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(): array
16 {
17 return [
18 'directory' => AsStringable::class,
19 ];
20 }
21}

陣列與 JSON Cast

當處理儲存為序列化 JSON 的欄位時,array Cast 特別有用。例如,如果您的資料庫具有包含序列化 JSON 的 JSONTEXT 欄位類型,則將 array Cast 新增到該屬性將在您於 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(): array
15 {
16 return [
17 'options' => 'array',
18 ];
19 }
20}

一旦定義了 Cast,您就可以訪問 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']);

Array Object 和 Collection Cast

雖然標準的 array Cast 對於許多應用程式來說已足夠,但它確實有一些缺點。由於 array Cast 傳回原始類型,因此無法直接修改陣列的偏移量。例如,以下程式碼將觸發 PHP 錯誤:

1$user = User::find(1);
2 
3$user->options['key'] = $value;

為了解决此問題,Laravel 提供了 AsArrayObject Cast,可將您的 JSON 屬性 Cast 為 ArrayObject 類別。此功能是使用 Laravel 的 自訂 Cast 實作來實作的,這允許 Laravel 智慧地快取和轉換修改後的物件,以便可以修改單個偏移量而不會觸發 PHP 錯誤。要使用 AsArrayObject Cast,只需將其指派給一個屬性即可。

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 提供了 AsCollection Cast,可將您的 JSON 屬性 Cast 為 Laravel Collection 實例。

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 Cast 實例化自訂的 collection 類別而不是 Laravel 的基礎 collection 類別,您可以提供 collection 類別名稱作為 Cast 參數。

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(): array
10{
11 return [
12 'options' => AsCollection::using(OptionCollection::class),
13 ];
14}

日期 Cast

預設情況下,Eloquent 會將 created_atupdated_at 欄位 Cast 為 Carbon 的實例,它擴展了 PHP DateTime 類別並提供了各種有用的方法。您可以透過在模型的 casts 方法中定義額外的日期 Cast 來 Cast 其他日期屬性。通常,日期應使用 datetimeimmutable_datetime Cast 類型來 Cast。

當定義 datedatetime Cast 時,您還可以指定日期的格式。當模型序列化為陣列或 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}

當欄位被 Cast 為日期時,您可以將對應的模型屬性值設定為 UNIX 時間戳記、日期字串 (Y-m-d)、日期時間字串或 DateTime / Carbon 實例。日期的值將被正確轉換並儲存在您的資料庫中。

您可以透過在模型上定義 serializeDate 方法來自訂所有模型日期的預設序列化格式。此方法不會影響日期在資料庫中儲存時的格式。

1/**
2 * Prepare a date for array / JSON serialization.
3 */
4protected function serializeDate(DateTimeInterface $date): string
5{
6 return $date->format('Y-m-d');
7}

要指定實際在資料庫中儲存模型日期時應使用的格式,您應在模型上定義 $dateFormat 屬性。

1/**
2 * The storage format of the model's date columns.
3 *
4 * @var string
5 */
6protected $dateFormat = 'U';

日期 Cast、序列化和時區

預設情況下,無論您的應用程式的 timezone 設定選項中指定的時區為何,datedatetime Cast 都會將日期序列化為 UTC ISO-8601 日期字串 (YYYY-MM-DDTHH:MM:SS.uuuuuuZ)。強烈建議您始終使用此序列化格式,並透過不將應用程式的 timezone 設定選項從其預設 UTC 值變更來將應用程式的日期儲存在 UTC 時區中。在整個應用程式中一致地使用 UTC 時區將為您提供與 PHP 和 JavaScript 中編寫的其他日期操作函式庫的最大互操作性。

如果自訂格式應用於 datedatetime Cast,例如 datetime:Y-m-d H:i:s,則在日期序列化期間將使用 Carbon 實例的內部時區。通常,這將是應用程式的 timezone 設定選項中指定的時區。但是,重要的是要注意,諸如 created_atupdated_at 之類的時間戳記欄位不受此行為的約束,並且始終以 UTC 格式格式化,而與應用程式的時區設定無關。

Enum Cast

Eloquent 還允許您將屬性值 Cast 為 PHP Enums。為了實現這一點,您可以在模型的 casts 方法中指定屬性和您希望 Cast 的 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}

一旦您在模型上定義了 Cast,當您與屬性交互時,指定的屬性將自動 Cast 為和從 enum Cast。

1if ($server->status == ServerStatus::Provisioned) {
2 $server->status = ServerStatus::Ready;
3 
4 $server->save();
5}

Cast Enum 陣列

有時您可能需要您的模型在單個欄位中儲存 enum 值陣列。為了實現這一點,您可以使用 Laravel 提供的 AsEnumArrayObjectAsEnumCollection Cast。

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(): array
10{
11 return [
12 'statuses' => AsEnumCollection::of(ServerStatus::class),
13 ];
14}

加密 Cast

encrypted Cast 將使用 Laravel 的內建加密功能來加密模型的屬性值。此外,encrypted:arrayencrypted:collectionencrypted:objectAsEncryptedArrayObjectAsEncryptedCollection Cast 的工作方式與它們的未加密對應項類似;但是,正如您可能期望的那樣,底層值在儲存到資料庫中時會被加密。

由於加密文字的最終長度是不可預測的,並且比其純文字對應項更長,因此請確保關聯的資料庫欄位為 TEXT 類型或更大。此外,由於值在資料庫中被加密,因此您將無法查詢或搜尋加密的屬性值。

金鑰輪換

您可能知道,Laravel 使用您的應用程式 app 設定檔中指定的 key 設定值來加密字串。通常,此值對應於 APP_KEY 環境變數的值。如果您需要輪換應用程式的加密金鑰,則需要使用新金鑰手動重新加密您的加密屬性。

查詢時間 Cast

有時您可能需要在執行查詢時應用 Cast,例如當從表格中選擇原始值時。例如,考慮以下查詢:

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 Cast 應用於此屬性,那就太好了。值得慶幸的是,我們可以使用 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();

自訂 Cast

Laravel 有各種內建的、有用的 Cast 類型;但是,您有時可能需要定義自己的 Cast 類型。要建立 Cast,請執行 make:cast Artisan 命令。新的 Cast 類別將放置在您的 app/Casts 目錄中。

1php artisan make:cast Json

所有自訂 Cast 類別都實作 CastsAttributes 介面。實作此介面的類別必須定義 getset 方法。 get 方法負責將來自資料庫的原始值轉換為 Cast 值,而 set 方法應將 Cast 值轉換為可以儲存在資料庫中的原始值。作為範例,我們將重新實作內建的 json Cast 類型作為自訂 Cast 類型。

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> $attributes
14 * @return array<string, mixed>
15 */
16 public function get(Model $model, string $key, mixed $value, array $attributes): array
17 {
18 return json_decode($value, true);
19 }
20 
21 /**
22 * Prepare the given value for storage.
23 *
24 * @param array<string, mixed> $attributes
25 */
26 public function set(Model $model, string $key, mixed $value, array $attributes): string
27 {
28 return json_encode($value);
29 }
30}

一旦您定義了自訂 Cast 類型,您就可以使用其類別名稱將其附加到模型屬性。

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(): array
16 {
17 return [
18 'options' => Json::class,
19 ];
20 }
21}

Value Object Cast

您不僅限於將值 Cast 為原始類型。您也可以將值 Cast 為物件。定義將值 Cast 為物件的自訂 Cast 與 Cast 為原始類型非常相似;但是,set 方法應傳回鍵 / 值對的陣列,該陣列將用於在模型上設定原始的可儲存值。

作為範例,我們將定義一個自訂 Cast 類別,將多個模型值 Cast 為單個 Address value object。我們將假設 Address value object 具有兩個 public 屬性:lineOnelineTwo

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 CastsAttributes
11{
12 /**
13 * Cast the given value.
14 *
15 * @param array<string, mixed> $attributes
16 */
17 public function get(Model $model, string $key, mixed $value, array $attributes): AddressValueObject
18 {
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> $attributes
29 * @return array<string, string>
30 */
31 public function set(Model $model, string $key, mixed $value, array $attributes): array
32 {
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}

當 Cast 為 value objects 時,對 value object 所做的任何變更都將在模型儲存之前自動同步回模型。

1use App\Models\User;
2 
3$user = User::find(1);
4 
5$user->address->lineOne = 'Updated Address Value';
6 
7$user->save();

如果您計劃將包含 value objects 的 Eloquent 模型序列化為 JSON 或陣列,則應在 value object 上實作 Illuminate\Contracts\Support\ArrayableJsonSerializable 介面。

Value Object 快取

當解析 Cast 為 value objects 的屬性時,Eloquent 會快取它們。因此,如果再次訪問該屬性,將傳回相同的物件實例。

如果您想停用自訂 Cast 類別的物件快取行為,您可以在自訂 Cast 類別上宣告 public withoutObjectCaching 屬性。

1class Address implements CastsAttributes
2{
3 public bool $withoutObjectCaching = true;
4 
5 // ...
6}

陣列 / JSON 序列化

當使用 toArraytoJson 方法將 Eloquent 模型轉換為陣列或 JSON 時,您的自訂 Cast value objects 通常也會被序列化,只要它們實作了 Illuminate\Contracts\Support\ArrayableJsonSerializable 介面。但是,當使用第三方函式庫提供的 value objects 時,您可能無法將這些介面添加到物件中。

因此,您可以指定您的自訂 Cast 類別將負責序列化 value object。為此,您的自訂 Cast 類別應實作 Illuminate\Contracts\Database\Eloquent\SerializesCastableAttributes 介面。此介面指出您的類別應包含一個 serialize 方法,該方法應傳回 value object 的序列化形式。

1/**
2 * Get the serialized representation of the value.
3 *
4 * @param array<string, mixed> $attributes
5 */
6public function serialize(Model $model, string $key, mixed $value, array $attributes): string
7{
8 return (string) $value;
9}

Inbound Cast

有時,您可能需要編寫一個自訂 Cast 類別,該類別僅轉換正在模型上設定的值,並且在從模型檢索屬性時不執行任何操作。

Inbound 僅限的自訂 Cast 應實作 CastsInboundAttributes 介面,該介面僅需要定義 set 方法。可以使用 --inbound 選項調用 make:cast Artisan 命令來產生僅限 inbound 的 Cast 類別。

1php artisan make:cast Hash --inbound

Inbound 僅限 Cast 的經典範例是「雜湊」Cast。例如,我們可以定義一個 Cast,透過給定的演算法雜湊 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> $attributes
21 */
22 public function set(Model $model, string $key, mixed $value, array $attributes): string
23 {
24 return is_null($this->algorithm)
25 ? bcrypt($value)
26 : hash($this->algorithm, $value);
27 }
28}

Cast 參數

當將自訂 Cast 附加到模型時,可以透過使用 : 字元將 Cast 參數與類別名稱分隔開來指定 Cast 參數,並以逗號分隔多個參數。這些參數將傳遞給 Cast 類別的建構子。

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}

Castables

您可能希望允許應用程式的 value objects 定義它們自己的自訂 Cast 類別。您可以選擇附加實作 Illuminate\Contracts\Database\Eloquent\Castable 介面的 value object 類別,而不是將自訂 Cast 類別附加到您的模型。

1use App\ValueObjects\Address;
2 
3protected function casts(): array
4{
5 return [
6 'address' => Address::class,
7 ];
8}

實作 Castable 介面的物件必須定義一個 castUsing 方法,該方法傳回負責 Cast 為和從 Castable 類別 Cast 的自訂 caster 類別的類別名稱。

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> $arguments
14 */
15 public static function castUsing(array $arguments): string
16 {
17 return AddressCast::class;
18 }
19}

當使用 Castable 類別時,您仍然可以在 casts 方法定義中提供參數。這些參數將傳遞給 castUsing 方法。

1use App\ValueObjects\Address;
2 
3protected function casts(): array
4{
5 return [
6 'address' => Address::class.':argument',
7 ];
8}

Castables & 匿名 Cast 類別

透過將「castables」與 PHP 的 匿名類別 結合使用,您可以將 value object 及其 Cast 邏輯定義為單個 castable 物件。為了實現這一點,請從 value object 的 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> $arguments
16 */
17 public static function castUsing(array $arguments): CastsAttributes
18 {
19 return new class implements CastsAttributes
20 {
21 public function get(Model $model, string $key, mixed $value, array $attributes): Address
22 {
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): array
30 {
31 return [
32 'address_line_one' => $value->lineOne,
33 'address_line_two' => $value->lineTwo,
34 ];
35 }
36 };
37 }
38}