Eloquent: 修改器與型別轉換
簡介
訪問器、修改器和屬性轉換允許你在模型例項中檢索或設定 Eloquent 屬性值時對其進行轉換。例如,你可能希望在將值儲存到資料庫時使用 Laravel 加密器 對其進行加密,然後在 Eloquent 模型中訪問該屬性時自動解密。或者,你可能希望將儲存在資料庫中的 JSON 字串在透過 Eloquent 模型訪問時轉換為陣列。
訪問器與修改器
定義訪問器
訪問器會在訪問 Eloquent 屬性值時對其進行轉換。要定義訪問器,請在模型上建立一個受保護的方法來表示可訪問的屬性。如果適用,此方法名稱應對應於底層模型屬性 / 資料庫列的“駝峰式”表示形式。
在本例中,我們將為 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 閉包可以接受第二個引數 $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 方法應返回一個數組,其中鍵是要轉換的屬性名稱,值是你希望將列轉換成的型別。支援的轉換型別有:
arrayAsFluent::classAsStringable::classAsUri::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 Casting)
你可以使用 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']);
JSON 與 Unicode
如果你想以 JSON 形式儲存包含未轉義 Unicode 字元的陣列屬性,可以使用 json:unicode 轉換。
1/** 2 * Get the attributes that should be cast. 3 * 4 * @return array<string, string> 5 */ 6protected function casts(): array 7{ 8 return [ 9 'options' => 'json:unicode',10 ];11}
陣列物件與集合轉換
雖然標準的 array 轉換足以滿足許多應用程式的需求,但它確實有一些缺點。由於 array 轉換返回的是原始型別,因此無法直接修改陣列的偏移量。例如,以下程式碼將觸發 PHP 錯誤:
1$user = User::find(1);2 3$user->options['key'] = $value;
為了解決這個問題,Laravel 提供了 AsArrayObject 轉換,它將你的 JSON 屬性轉換為 ArrayObject 類。此功能透過 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 提供了 AsCollection 轉換,它將你的 JSON 屬性轉換為 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 轉換例項化自定義集合類而不是 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}
of 方法可用於指示集合項應透過集合的 mapInto 方法 對映到給定的類中。
1use App\ValueObjects\Option; 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::of(Option::class)13 ];14}
將集合對映到物件時,物件應實現 Illuminate\Contracts\Support\Arrayable 和 JsonSerializable 介面,以定義其例項應如何序列化為 JSON 儲存到資料庫中。
1<?php 2 3namespace App\ValueObjects; 4 5use Illuminate\Contracts\Support\Arrayable; 6use JsonSerializable; 7 8class Option implements Arrayable, JsonSerializable 9{10 public string $name;11 public mixed $value;12 public bool $isLocked;13 14 /**15 * Create a new Option instance.16 */17 public function __construct(array $data)18 {19 $this->name = $data['name'];20 $this->value = $data['value'];21 $this->isLocked = $data['is_locked'];22 }23 24 /**25 * Get the instance as an array.26 *27 * @return array{name: string, data: string, is_locked: bool}28 */29 public function toArray(): array30 {31 return [32 'name' => $this->name,33 'value' => $this->value,34 'is_locked' => $this->isLocked,35 ];36 }37 38 /**39 * Specify the data which should be serialized to JSON.40 *41 * @return array{name: string, data: string, is_locked: bool}42 */43 public function jsonSerialize(): array44 {45 return $this->toArray();46 }47}
二進位制轉換
如果你的 Eloquent 模型除了自動遞增 ID 列外,還有 二進位制型別 的 uuid 或 ulid 列,你可以使用 AsBinary 轉換自動將其值轉換為二進位制表示形式,反之亦然。
1use Illuminate\Database\Eloquent\Casts\AsBinary; 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 'uuid' => AsBinary::uuid(),12 'ulid' => AsBinary::ulid(),13 ];14}
在模型上定義轉換後,你可以將 UUID / ULID 屬性值設定為物件例項或字串。Eloquent 將自動把該值轉換為其二進位制表示形式。檢索屬性值時,你將始終獲得一個純文字字串值。
1use Illuminate\Support\Str;2 3$user->uuid = Str::uuid();4 5return $user->uuid;6 7// "6e8cdeed-2f32-40bd-b109-1e4405be2140"
日期轉換
預設情況下,Eloquent 會將 created_at 和 updated_at 列轉換為 Carbon 例項,它擴充套件了 PHP 的 DateTime 類並提供了各種有用的方法。你可以透過在模型的 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}
要指定在資料庫中實際儲存模型日期時應使用的格式,你應該在模型的 Table 屬性上使用 dateFormat 引數。
1use Illuminate\Database\Eloquent\Attributes\Table;2 3#[Table(dateFormat: 'U')]4class Flight extends Model5{6 // ...7}
日期轉換、序列化與時區
預設情況下,date 和 datetime 轉換會將日期序列化為 UTC ISO-8601 日期字串 (YYYY-MM-DDTHH:MM:SS.uuuuuuZ),無論在應用程式的 timezone 配置選項中指定了什麼時區。強烈建議始終使用此序列化格式,並透過不更改應用程式 timezone 配置選項的預設 UTC 值,將應用程式的日期儲存在 UTC 時區中。在整個應用程式中始終使用 UTC 時區,將為使用 PHP 和 JavaScript 編寫的其他日期處理庫提供最大程度的互操作性。
如果將自定義格式應用於 date 或 datetime 轉換(例如 datetime:Y-m-d H:i:s),則在日期序列化期間將使用 Carbon 例項的內部時區。通常,這將是應用程式 timezone 配置選項中指定的時區。但需要注意的是,timestamp 列(如 created_at 和 updated_at)不受此行為限制,無論應用程式的時區設定如何,它們始終以 UTC 格式化。
列舉轉換
Eloquent 還允許將屬性值轉換為 PHP 列舉 (Enums)。為此,你可以在模型的 casts 方法中指定要轉換的屬性和列舉。
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}
一旦在模型上定義了轉換,當你與該屬性互動時,指定的屬性將自動在列舉之間進行轉換。
1if ($server->status == ServerStatus::Provisioned) {2 $server->status = ServerStatus::Ready;3 4 $server->save();5}
轉換列舉陣列
有時你可能需要你的模型在單個列中儲存列舉值陣列。為此,你可以利用 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 環境變數的值。如果你需要輪換應用程式的加密金鑰,可以 優雅地完成此操作。
查詢時型別轉換
有時你可能需要在執行查詢時應用轉換,例如從表中選擇原始值時。例如,考慮以下查詢:
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 AsJson
所有自定義轉換類都實現 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 AsJson 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(17 Model $model,18 string $key,19 mixed $value,20 array $attributes,21 ): array {22 return json_decode($value, true);23 }24 25 /**26 * Prepare the given value for storage.27 *28 * @param array<string, mixed> $attributes29 */30 public function set(31 Model $model,32 string $key,33 mixed $value,34 array $attributes,35 ): string {36 return json_encode($value);37 }38}
定義好自定義轉換型別後,你可以使用其類名將其附加到模型屬性上。
1<?php 2 3namespace App\Models; 4 5use App\Casts\AsJson; 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' => AsJson::class,19 ];20 }21}
值物件轉換
你不侷限於將值轉換為原始型別。你也可以將值轉換為物件。定義將值轉換為物件的自定義轉換與轉換為原始型別非常相似;但是,如果你的值物件包含多個數據庫列,則 set 方法必須返回一個鍵/值對陣列,用於設定模型上的原始可儲存值。如果你的值物件僅影響單個列,則只需返回可儲存的值即可。
作為示例,我們將定義一個自定義轉換類,將多個模型值轉換為單個 Address 值物件。我們將假設 Address 值物件有兩個公共屬性:lineOne 和 lineTwo。
1<?php 2 3namespace App\Casts; 4 5use App\ValueObjects\Address; 6use Illuminate\Contracts\Database\Eloquent\CastsAttributes; 7use Illuminate\Database\Eloquent\Model; 8use InvalidArgumentException; 9 10class AsAddress implements CastsAttributes11{12 /**13 * Cast the given value.14 *15 * @param array<string, mixed> $attributes16 */17 public function get(18 Model $model,19 string $key,20 mixed $value,21 array $attributes,22 ): Address {23 return new Address(24 $attributes['address_line_one'],25 $attributes['address_line_two']26 );27 }28 29 /**30 * Prepare the given value for storage.31 *32 * @param array<string, mixed> $attributes33 * @return array<string, string>34 */35 public function set(36 Model $model,37 string $key,38 mixed $value,39 array $attributes,40 ): array {41 if (! $value instanceof Address) {42 throw new InvalidArgumentException('The given value is not an Address instance.');43 }44 45 return [46 'address_line_one' => $value->lineOne,47 'address_line_two' => $value->lineTwo,48 ];49 }50}
當轉換為值物件時,對值物件所做的任何更改都會在儲存模型之前自動同步回模型。
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 AsAddress implements CastsAttributes2{3 public bool $withoutObjectCaching = true;4 5 // ...6}
陣列 / JSON 序列化
當使用 toArray 和 toJson 方法將 Eloquent 模型轉換為陣列或 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> $attributes 5 */ 6public function serialize( 7 Model $model, 8 string $key, 9 mixed $value,10 array $attributes,11): string {12 return (string) $value;13}
入站轉換
有時,你可能需要編寫一個自定義轉換類,它僅轉換正在設定到模型上的值,而在從模型檢索屬性時不執行任何操作。
僅入站的自定義轉換應實現 CastsInboundAttributes 介面,該介面僅要求定義一個 set 方法。可以使用 --inbound 選項呼叫 make:cast Artisan 命令來生成僅入站的轉換類。
1php artisan make:cast AsHash --inbound
僅入站轉換的一個經典示例是“雜湊”轉換。例如,我們可以定義一個透過給定演算法對入站值進行雜湊處理的轉換。
1<?php 2 3namespace App\Casts; 4 5use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes; 6use Illuminate\Database\Eloquent\Model; 7 8class AsHash 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(23 Model $model,24 string $key,25 mixed $value,26 array $attributes,27 ): string {28 return is_null($this->algorithm)29 ? bcrypt($value)30 : hash($this->algorithm, $value);31 }32}
轉換引數
將自定義轉換附加到模型時,可以透過使用 : 字元分隔類名並用逗號分隔多個引數來指定轉換引數。這些引數將被傳遞給轉換類的建構函式。
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' => AsHash::class.':sha256',10 ];11}
比較轉換值
如果你想定義應如何比較兩個給定的轉換值以確定它們是否已更改,你的自定義轉換類可以實現 Illuminate\Contracts\Database\Eloquent\ComparesCastableAttributes 介面。這使你可以細粒度地控制 Eloquent 認為哪些值已更改,從而在更新模型時儲存到資料庫。
此介面宣告你的類應包含一個 compare 方法,如果給定的值被認為是相等的,則該方法應返回 true。
1/** 2 * Determine if the given values are equal. 3 * 4 * @param \Illuminate\Database\Eloquent\Model $model 5 * @param string $key 6 * @param mixed $firstValue 7 * @param mixed $secondValue 8 * @return bool 9 */10public function compare(11 Model $model,12 string $key,13 mixed $firstValue,14 mixed $secondValue15): bool {16 return $firstValue === $secondValue;17}
可轉換物件 (Castables)
你可能希望允許應用程式的值物件定義它們自己的自定義轉換類。除了將自定義轉換類附加到你的模型外,你也可以選擇附加一個實現了 Illuminate\Contracts\Database\Eloquent\Castable 介面的值物件類。
1use App\ValueObjects\Address;2 3protected function casts(): array4{5 return [6 'address' => Address::class,7 ];8}
實現 Castable 介面的物件必須定義一個 castUsing 方法,該方法返回負責與 Castable 類進行轉換的自定義轉換器類的類名。
1<?php 2 3namespace App\ValueObjects; 4 5use Illuminate\Contracts\Database\Eloquent\Castable; 6use App\Casts\AsAddress; 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 AsAddress::class;18 }19}
使用 Castable 類時,你仍然可以在 casts 方法定義中提供引數。這些引數將被傳遞給 castUsing 方法。
1use App\ValueObjects\Address;2 3protected function casts(): array4{5 return [6 'address' => Address::class.':argument',7 ];8}
可轉換物件 (Castables) 與匿名轉換類
透過將“可轉換物件”與 PHP 的 匿名類 結合使用,你可以將值物件及其轉換邏輯定義為單個可轉換物件。為此,從你的值物件的 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(22 Model $model,23 string $key,24 mixed $value,25 array $attributes,26 ): Address {27 return new Address(28 $attributes['address_line_one'],29 $attributes['address_line_two']30 );31 }32 33 public function set(34 Model $model,35 string $key,36 mixed $value,37 array $attributes,38 ): array {39 return [40 'address_line_one' => $value->lineOne,41 'address_line_two' => $value->lineTwo,42 ];43 }44 };45 }46}