LaravelDocs(中文)

Eloquent:變異器和類型轉換 (Mutators and Casting)

Eloquent 變異器和轉換讓你轉換屬性值

簡介 (Introduction)

訪問器、變異器和屬性轉換允許你在檢索或在模型實例上設定時轉換 Eloquent 屬性值。例如,你可能想使用 Laravel 加密工具 在值儲存在資料庫時將其加密,然後在訪問 Eloquent 模型上的屬性時自動解密。或者,你可能想轉換儲存在資料庫中的 JSON 字符串,當通過 Eloquent 模型訪問時將其轉換為陣列。

訪問器和變異器 (Accessors And Mutators)

定義訪問器 (Defining An Accessor)

訪問器在訪問 Eloquent 屬性值時進行轉換。要定義訪問器,請在模型上建立一個受保護的方法來表示可訪問的屬性。此方法名稱應與真實基礎模型屬性/資料庫列的「駝峰大小寫」表示相對應。

在此範例中,我們為 first_name 屬性定義訪問器。當 Eloquent 嘗試檢索 first_name 屬性的值時,訪問器將被自動呼叫。所有屬性訪問器/變異器方法必須宣告返回類型提示為 Illuminate\Database\Eloquent\Casts\Attribute

<?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 類別建構子提供 get 參數。

如你所見,列的原始值被傳遞給訪問器,允許你操縱並返回該值。要訪問訪問器的值,你可以簡單地訪問模型實例上的 first_name 屬性:

use App\Models\User;

$user = User::find(1);

$firstName = $user->first_name;

[!NOTE] 如果你想將這些計算值新增到模型的陣列/JSON 表示中,你需要附加它們

從多個屬性建構值物件 (Building Value Objects From Multiple Attributes)

有時你的訪問器可能需要將多個模型屬性轉換為單個「值物件」。為此,你的 get 閉包可能接受第二個 $attributes 參數,該參數將自動提供給閉包,並將包含模型所有目前屬性的陣列:

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'],
        ),
    );
}

訪問器快取 (Accessor Caching)

當從訪問器返回值物件時,對值物件所做的任何更改將在保存模型前自動同步回模型。這是可能的,因為 Eloquent 保留訪問器返回的實例,因此每次呼叫訪問器時都可以返回相同的實例:

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 方法:

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

如果你想停用屬性的物件快取行為,你可以在定義屬性時呼叫 withoutObjectCaching 方法:

/**
 * 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();
}

定義變異器 (Defining A Mutator)

變異器在設定 Eloquent 屬性值時進行轉換。要定義變異器,你可以在定義屬性時提供 set 參數。讓我們為 first_name 屬性定義一個變異器。當我們嘗試在模型上設定 first_name 屬性的值時,此變異器將被自動呼叫:

<?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 屬性:

use App\Models\User;

$user = User::find(1);

$user->first_name = 'Sally';

在此範例中,set 回呼將被呼叫且值為 Sally。變異器將隨後對名稱應用 strtolower 函數,並在模型的內部 $attributes 陣列中設定其結果值。

變異多個屬性 (Mutating Multiple Attributes)

有時你的變異器可能需要在基礎模型上設定多個屬性。為此,你可以從 set 閉包返回一個陣列。陣列中的每個鍵應對應於與模型關聯的基礎屬性/資料庫列:

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 Casting)

屬性轉換提供類似於訪問器和變異器的功能,無需在你的模型上定義任何額外的方法。相反,你的模型的 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

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

<?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 屬性在你訪問它時也將始終被轉換為布林值:

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

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

如果你需要在運行時新增臨時轉換,你可以使用 mergeCasts 方法。這些轉換定義將被新增到模型上已定義的任何轉換中:

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

[!WARNING] 值為 null 的屬性將不會被轉換。此外,你永遠不應定義與關係同名的轉換(或屬性),或為模型的主鍵分配轉換。

字符串化轉換 (Stringable Casting)

你可以使用 Illuminate\Database\Eloquent\Casts\AsStringable 轉換類別將模型屬性轉換為 fluent Illuminate\Support\Stringable 物件

<?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 And Json Casting)

array 轉換特別適用於處理儲存為序列化 JSON 的列。例如,如果你的資料庫有包含序列化 JSON 的 JSONTEXT 欄位類型,將 array 轉換新增到該屬性將在你通過 Eloquent 模型訪問時自動將屬性反序列化為 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 以供儲存:

use App\Models\User;

$user = User::find(1);

$options = $user->options;

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

$user->options = $options;

$user->save();

要使用更簡潔的語法更新 JSON 屬性的單個欄位,你可以 使屬性可大量分配,並在呼叫 update 方法時使用 -> 運算子:

$user = User::find(1);

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

JSON 和 Unicode (Json And Unicode)

如果你想將陣列屬性儲存為帶有未轉義 Unicode 字符的 JSON,你可以使用 json:unicode 轉換:

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

陣列物件和集合轉換 (Array Object And Collection Casting)

雖然標準 array 轉換對許多應用程式來說是充分的,但它確實有一些缺點。由於 array 轉換返回原始類型,無法直接變異陣列的偏移量。例如,以下代碼將觸發 PHP 錯誤:

$user = User::find(1);

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

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

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,
    ];
}

類似地,Laravel 提供了一個 AsCollection 轉換,將你的 JSON 屬性轉換為 Laravel 集合 實例:

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,
    ];
}

如果你想要 AsCollection 轉換例項化自訂集合類別而不是 Laravel 的基礎集合類別,你可以提供集合類別名稱作為轉換參數:

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 方法可用於表示集合項應通過集合的 mapInto 方法 對應到給定的類別:

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\ArrayableJsonSerializable 介面,以定義其實例應如何序列化為資料庫中的 JSON:

<?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();
    }
}

日期轉換 (Date Casting)

預設情況下,Eloquent 將 created_atupdated_at 列轉換為 Carbon 的實例,它擴展了 PHP DateTime 類別並提供了大量有用的方法。你可以通過在模型的 casts 方法中定義其他日期轉換來轉換其他日期屬性。通常,日期應使用 datetimeimmutable_datetime 轉換類型進行轉換。

定義 datedatetime 轉換時,你也可以指定日期的格式。當 模型被序列化為陣列或 JSON 時,將使用此格式:

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

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

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

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

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

/**
 * The storage format of the model's date columns.
 *
 * @var string
 */
protected $dateFormat = 'U';

日期轉換、序列化和時區 (Date Casting And Timezones)

預設情況下,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 格式化。

列舉轉換 (Enum Casting)

Eloquent 也允許你將屬性值轉換為 PHP 列舉。為了完成此操作,你可以在模型的 casts 方法中指定你希望轉換的屬性和列舉:

use App\Enums\ServerStatus;

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

在模型上定義轉換後,指定的屬性將在你與屬性進行互動時自動轉換為列舉或從列舉轉換:

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

    $server->save();
}

轉換列舉陣列 (Casting Arrays Of Enums)

有時你可能需要模型在單個列中儲存列舉值的陣列。為了完成此操作,你可以利用 Laravel 提供的 AsEnumArrayObjectAsEnumCollection 轉換:

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 Casting)

encrypted 轉換將使用 Laravel 的內置 加密 功能加密模型的屬性值。此外,encrypted:arrayencrypted:collectionencrypted:objectAsEncryptedArrayObjectAsEncryptedCollection 轉換的行為類似於它們的未加密對應物;但是,如你所預期的,基礎值在儲存在資料庫時被加密。

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

金鑰輪換 (Key Rotation)

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

查詢時間轉換 (Query Time Casting)

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

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 方法完成此操作:

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

自訂轉換 (Custom Casts)

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

php artisan make:cast AsJson

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

<?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

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,
        ];
    }
}

值物件轉換 (Value Object Casting)

你不限於將值轉換為原始類型。你也可以將值轉換為物件。定義將值轉換為物件的自訂轉換與轉換為原始類型非常相似;但是,如果你的值物件涵蓋多個資料庫列,set 方法必須返回一個鍵/值對的陣列,將用於在模型上設定原始的、可儲存的值。如果你的值物件僅影響單個列,你應簡單地返回可儲存的值。

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

<?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,
        ];
    }
}

將值物件進行轉換時,對值物件所做的任何更改將在保存模型前自動同步回模型:

use App\Models\User;

$user = User::find(1);

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

$user->save();

[!NOTE] 如果你計劃將包含值物件的 Eloquent 模型序列化為 JSON 或陣列,你應在值物件上實現 Illuminate\Contracts\Support\ArrayableJsonSerializable 介面。

值物件快取 (Value Object Caching)

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

如果你想停用自訂轉換類別的物件快取行為,你可以在自訂轉換類別上宣告一個公開的 withoutObjectCaching 屬性:

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

    // ...
}

陣列 / JSON 序列化 (Array Json Serialization)

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

因此,你可以指定你的自訂轉換類別將負責序列化值物件。為此,你的自訂轉換類別應實現 Illuminate\Contracts\Database\Eloquent\SerializesCastableAttributes 介面。此介面規定你的類別應包含一個 serialize 方法,該方法應返回值物件的序列化形式:

/**
 * 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;
}

入站轉換 (Inbound Casting)

有時,你可能需要編寫一個自訂轉換類別,它僅轉換在模型上設定的值,而不會在從模型檢索屬性時執行任何操作。

入站專用自訂轉換應實現 CastsInboundAttributes 介面,它僅要求定義 set 方法。make:cast Artisan 命令可以使用 --inbound 選項來呼叫以生成入站專用轉換類別:

php artisan make:cast AsHash --inbound

入站專用轉換的一個典型例子是「雜湊」轉換。例如,我們可能定義一個通過給定演算法轉換入站值的轉換:

<?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);
    }
}

轉換參數 (Cast Parameters)

將自訂轉換附加到模型時,可以通過使用 : 字符從類別名稱中分隔並逗號分隔多個參數來指定轉換參數。參數將被傳遞到轉換類別的建構子:

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

比較轉換值 (Comparing Cast Values)

如果你想定義兩個給定的轉換值應如何進行比較以確定它們是否已改變,你的自訂轉換類別可能實現 Illuminate\Contracts\Database\Eloquent\ComparesCastableAttributes 介面。這允許你對 Eloquent 認為哪些值已改變並因此在更新模型時儲存到資料庫進行細粒度控制。

此介面規定你的類別應包含一個 compare 方法,如果給定的值被認為相等,該方法應返回 true

/**
 * 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;
}

可轉換的 (Castables)

你可能想允許你應用程式的值物件定義自己的自訂轉換類別。與其將自訂轉換類別附加到模型,不如可以選擇附加一個實現 Illuminate\Contracts\Database\Eloquent\Castable 介面的值物件類別:

use App\ValueObjects\Address;

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

實現 Castable 介面的物件必須定義一個 castUsing 方法,該方法返回負責轉換至和從 Castable 類別轉換的自訂轉換程式類別的類別名稱:

<?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 方法:

use App\ValueObjects\Address;

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

可轉換的和匿名轉換類別 (Anonymous Cast Classes)

通過結合「可轉換的」與 PHP 的 匿名類別,你可以將值物件及其轉換邏輯定義為單個可轉換物件。為此,從值物件的 castUsing 方法返回匿名類別。匿名類別應實現 CastsAttributes 介面:

<?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,
                ];
            }
        };
    }
}