跳轉至內容

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(): Attribute
14 {
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(): 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';

在此示例中,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 方法應返回一個數組,其中鍵是要轉換的屬性名稱,值是你希望將列轉換成的型別。支援的轉換型別有:

  • array
  • AsFluent::class
  • AsStringable::class
  • AsUri::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

為了演示屬性轉換,讓我們將儲存在資料庫中為整數(01)的 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(): array
15 {
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(): array
16 {
17 return [
18 'directory' => AsStringable::class,
19 ];
20 }
21}

陣列與 JSON 轉換

array 轉換在使用序列化為 JSON 的列時特別有用。例如,如果你的資料庫有包含序列化 JSON 的 JSONTEXT 欄位型別,在該屬性上新增 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(): array
15 {
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(): array
10{
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(): array
10{
11 return [
12 'options' => AsCollection::of(Option::class)
13 ];
14}

將集合對映到物件時,物件應實現 Illuminate\Contracts\Support\ArrayableJsonSerializable 介面,以定義其例項應如何序列化為 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(): array
30 {
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(): array
44 {
45 return $this->toArray();
46 }
47}

二進位制轉換

如果你的 Eloquent 模型除了自動遞增 ID 列外,還有 二進位制型別uuidulid 列,你可以使用 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_atupdated_at 列轉換為 Carbon 例項,它擴充套件了 PHP 的 DateTime 類並提供了各種有用的方法。你可以透過在模型的 casts 方法中定義額外的日期轉換來轉換其他日期屬性。通常,日期應使用 datetimeimmutable_datetime 轉換型別。

定義 datedatetime 轉換時,你還可以指定日期的格式。當 模型被序列化為陣列或 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): string
5{
6 return $date->format('Y-m-d');
7}

要指定在資料庫中實際儲存模型日期時應使用的格式,你應該在模型的 Table 屬性上使用 dateFormat 引數。

1use Illuminate\Database\Eloquent\Attributes\Table;
2 
3#[Table(dateFormat: 'U')]
4class Flight extends Model
5{
6 // ...
7}

日期轉換、序列化與時區

預設情況下,datedatetime 轉換會將日期序列化為 UTC ISO-8601 日期字串 (YYYY-MM-DDTHH:MM:SS.uuuuuuZ),無論在應用程式的 timezone 配置選項中指定了什麼時區。強烈建議始終使用此序列化格式,並透過不更改應用程式 timezone 配置選項的預設 UTC 值,將應用程式的日期儲存在 UTC 時區中。在整個應用程式中始終使用 UTC 時區,將為使用 PHP 和 JavaScript 編寫的其他日期處理庫提供最大程度的互操作性。

如果將自定義格式應用於 datedatetime 轉換(例如 datetime:Y-m-d H:i:s),則在日期序列化期間將使用 Carbon 例項的內部時區。通常,這將是應用程式 timezone 配置選項中指定的時區。但需要注意的是,timestamp 列(如 created_atupdated_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 提供的 AsEnumArrayObjectAsEnumCollection 轉換。

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}

加密轉換

encrypted 轉換將使用 Laravel 內建的 加密 功能對模型屬性值進行加密。此外,encrypted:arrayencrypted:collectionencrypted:objectAsEncryptedArrayObjectAsEncryptedCollection 轉換的工作方式與它們未加密的對應項類似;然而,正如你所預料的,底層值在儲存到資料庫時會被加密。

由於加密文字的最終長度不可預測且比其純文字對應物長,請確保關聯的資料庫列型別為 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 介面。實現此介面的類必須定義 getset 方法。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> $attributes
14 * @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> $attributes
29 */
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(): array
16 {
17 return [
18 'options' => AsJson::class,
19 ];
20 }
21}

值物件轉換

你不侷限於將值轉換為原始型別。你也可以將值轉換為物件。定義將值轉換為物件的自定義轉換與轉換為原始型別非常相似;但是,如果你的值物件包含多個數據庫列,則 set 方法必須返回一個鍵/值對陣列,用於設定模型上的原始可儲存值。如果你的值物件僅影響單個列,則只需返回可儲存的值即可。

作為示例,我們將定義一個自定義轉換類,將多個模型值轉換為單個 Address 值物件。我們將假設 Address 值物件有兩個公共屬性:lineOnelineTwo

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 CastsAttributes
11{
12 /**
13 * Cast the given value.
14 *
15 * @param array<string, mixed> $attributes
16 */
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> $attributes
33 * @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\ArrayableJsonSerializable 介面。

值物件快取

當被轉換為值物件的屬性被解析時,它們會被 Eloquent 快取。因此,如果再次訪問該屬性,將返回相同的物件例項。

如果你想停用自定義轉換類的物件快取行為,可以在自定義轉換類上宣告一個公共的 withoutObjectCaching 屬性。

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

陣列 / JSON 序列化

當使用 toArraytoJson 方法將 Eloquent 模型轉換為陣列或 JSON 時,你的自定義轉換值物件通常也會被序列化,只要它們實現了 Illuminate\Contracts\Support\ArrayableJsonSerializable 介面即可。但是,在使用第三方庫提供的值物件時,你可能無法將這些介面新增到物件中。

因此,你可以指定你的自定義轉換類負責序列化值物件。為此,你的自定義轉換類應實現 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> $attributes
21 */
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 $secondValue
15): bool {
16 return $firstValue === $secondValue;
17}

可轉換物件 (Castables)

你可能希望允許應用程式的值物件定義它們自己的自定義轉換類。除了將自定義轉換類附加到你的模型外,你也可以選擇附加一個實現了 Illuminate\Contracts\Database\Eloquent\Castable 介面的值物件類。

1use App\ValueObjects\Address;
2 
3protected function casts(): array
4{
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> $arguments
14 */
15 public static function castUsing(array $arguments): string
16 {
17 return AsAddress::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) 與匿名轉換類

透過將“可轉換物件”與 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> $arguments
16 */
17 public static function castUsing(array $arguments): CastsAttributes
18 {
19 return new class implements CastsAttributes
20 {
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}