跳到主要內容

Eloquent 修改器和轉換

📝 此頁面為 Laravel 官方文檔的繁體中文翻譯。查看原始英文版本

Eloquent:修改器和轉換

簡介

存取器、修改器和屬性轉換允許您在從模型實例中檢索或設定 Eloquent 屬性值時對其進行轉換。例如,您可能希望在值儲存在資料庫中時使用 Laravel 加密器來加密它,然後在 Eloquent 模型上存取時自動解密屬性。或者,您可能希望在透過 Eloquent 模型存取時將儲存在資料庫中的 JSON 字串轉換為陣列。

存取器和修改器

定義存取器

存取器在存取時轉換 Eloquent 屬性值。要定義存取器,請在模型上建立一個受保護的方法來表示可存取的屬性。此方法名稱應與底層模型屬性/資料庫欄位的「駝峰命名法」表示相對應。

在此範例中,我們將為 first_name 屬性定義一個存取器。當嘗試存取 first_name 屬性的值時,Eloquent 將自動呼叫存取器。所有屬性存取器/修改器方法必須聲明 Illuminate\Database\Eloquent\Casts\Attribute 的返回類型提示:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * 獲取使用者的名字。
     */
    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 表示中,您將需要追加它們

從多個屬性建立值物件

有時您的存取器可能需要將多個模型屬性轉換為單個「值物件」。為此,您的 get 閉包可以接受第二個參數 $attributes,它將自動供應給閉包並包含所有模型當前屬性的陣列:

use App\Support\Address;
use Illuminate\Database\Eloquent\Casts\Attribute;

/**
 * 與使用者的地址互動。
 */
protected function address(): Attribute
{
    return Attribute::make(
        get: fn (mixed $value, array $attributes) => new Address(
            $attributes['address_line_one'],
            $attributes['address_line_two'],
        ),
    );
}

存取器快取

當從存取器返回值物件時,對值物件所做的任何更改都將在模型儲存之前自動同步回模型。這是可能的,因為 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 方法:

/**
 * 與使用者的地址互動。
 */
protected function address(): Attribute
{
    return Attribute::make(
        get: fn (mixed $value, array $attributes) => new Address(
            $attributes['address_line_one'],
            $attributes['address_line_two'],
        ),
    )->withoutObjectCaching();
}

定義修改器

修改器在設定時轉換 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
{
    /**
     * 與使用者的名字互動。
     */
    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 陣列中。

修改多個屬性

有時您的修改器可能需要在底層模型上設定多個屬性。為此,您可以從 set 閉包返回一個陣列。陣列中的每個鍵應對應於與模型關聯的底層屬性/資料庫欄位:

use App\Support\Address;
use Illuminate\Database\Eloquent\Casts\Attribute;

/**
 * 與使用者的地址互動。
 */
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,
        ],
    );
}

屬性轉換

屬性轉換提供了與存取器和修改器類似的功能,而無需您在模型上定義任何額外方法。相反,模型的 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
{
    /**
     * 獲取應被轉換的屬性。
     *
     * @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 的屬性不會被轉換。此外,您不應定義與關聯同名的轉換(或屬性),或將轉換分配給模型的主鍵。

可字串化轉換

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

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Casts\AsStringable;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * 獲取應被轉換的屬性。
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'directory' => AsStringable::class,
        ];
    }
}

陣列和 JSON 轉換

當處理儲存為序列化 JSON 的欄位時,array 轉換特別有用。例如,如果您的資料庫有一個包含序列化 JSON 的 JSONTEXT 欄位類型,向該屬性添加 array 轉換將在您在 Eloquent 模型上存取時自動將屬性反序列化為 PHP 陣列:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * 獲取應被轉換的屬性。
     *
     * @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

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

/**
 * 獲取應被轉換的屬性。
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'options' => 'json:unicode',
    ];
}

陣列物件和集合轉換

雖然標準的 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;

/**
 * 獲取應被轉換的屬性。
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'options' => AsArrayObject::class,
    ];
}

同樣地,Laravel 提供了一個 AsCollection 轉換,它將您的 JSON 屬性轉換為 Laravel Collection 實例:

use Illuminate\Database\Eloquent\Casts\AsCollection;

/**
 * 獲取應被轉換的屬性。
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'options' => AsCollection::class,
    ];
}

如果您希望 AsCollection 轉換實例化一個自訂集合類別而不是 Laravel 的基礎集合類別,您可以將集合類別名稱作為轉換參數提供:

use App\Collections\OptionCollection;
use Illuminate\Database\Eloquent\Casts\AsCollection;

/**
 * 獲取應被轉換的屬性。
 *
 * @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;

/**
 * 獲取應被轉換的屬性。
 *
 * @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;

    /**
     * 建立一個新的 Option 實例。
     */
    public function __construct(array $data)
    {
        $this->name = $data['name'];
        $this->value = $data['value'];
        $this->isLocked = $data['is_locked'];
    }

    /**
     * 獲取實例的陣列表示。
     *
     * @return array{name: string, data: string, is_locked: bool}
     */
    public function toArray(): array
    {
        return [
            'name' => $this->name,
            'value' => $this->value,
            'is_locked' => $this->isLocked,
        ];
    }

    /**
     * 指定應被序列化為 JSON 的資料。
     *
     * @return array{name: string, data: string, is_locked: bool}
     */
    public function jsonSerialize(): array
    {
        return $this->toArray();
    }
}

二進制轉換

如果您的 Eloquent 模型有一個二進制類型uuidulid 欄位(除了模型的自動遞增 ID 欄位之外),您可以使用 AsBinary 轉換自動將值轉換為其二進制表示形式以及從其二進制表示形式轉換回來:

use Illuminate\Database\Eloquent\Casts\AsBinary;

/**
 * 獲取應被轉換的屬性。
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'uuid' => AsBinary::uuid(),
        'ulid' => AsBinary::ulid(),
    ];
}

在模型上定義轉換後,您可以將 UUID / ULID 屬性值設定為物件實例或字串。Eloquent 將自動將值轉換為其二進制表示形式。存取屬性的值時,您將始終收到純文字字串值:

use Illuminate\Support\Str;

$user->uuid = Str::uuid();

return $user->uuid;

// "6e8cdeed-2f32-40bd-b109-1e4405be2140"

日期轉換

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

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

/**
 * 獲取應被轉換的屬性。
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'created_at' => 'datetime:Y-m-d',
    ];
}

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

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

/**
 * 為陣列 / JSON 序列化準備日期。
 */
protected function serializeDate(DateTimeInterface $date): string
{
    return $date->format('Y-m-d');
}

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

use Illuminate\Database\Eloquent\Attributes\Table;

#[Table(dateFormat: 'U')]
class Flight extends Model
{
    // ...
}

日期轉換、序列化和時區

預設情況下,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 列舉。為此,您可以在模型的 casts 方法中指定您希望轉換的屬性和列舉:

use App\Enums\ServerStatus;

/**
 * 獲取應被轉換的屬性。
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'status' => ServerStatus::class,
    ];
}

定義模型上的轉換後,當您與屬性互動時,指定的屬性將自動轉換為和從列舉轉換:

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

    $server->save();
}

轉換列舉陣列

有時您可能需要您的模型在單個欄位中儲存列舉值的陣列。為此,您可以使用 Laravel 提供的 AsEnumArrayObjectAsEnumCollection 轉換:

use App\Enums\ServerStatus;
use Illuminate\Database\Eloquent\Casts\AsEnumCollection;

/**
 * 獲取應被轉換的屬性。
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'statuses' => AsEnumCollection::of(ServerStatus::class),
    ];
}

加密轉換

encrypted 轉換將使用 Laravel 內建的加密功能加密模型的屬性值。此外,encrypted:arrayencrypted:collectionencrypted:objectAsEncryptedArrayObjectAsEncryptedCollection 轉換的工作方式與其未加密的對應物相同;但是,正如您可能預期的那樣,儲存在資料庫中的底層值是加密的。

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

金鑰輪換

如您所知,Laravel 使用應用程式的 app 配置檔案中指定的 key 配置值來加密字串。通常,此值對應於 APP_KEY 環境變數的值。如果您需要輪換應用程式的加密金鑰,您可以優雅地進行

查詢時轉換

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

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

自訂轉換

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
{
    /**
     * 轉換給定的值。
     *
     * @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);
    }

    /**
     * 為儲存準備給定的值。
     *
     * @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
{
    /**
     * 獲取應被轉換的屬性。
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'options' => AsJson::class,
        ];
    }
}

值物件轉換

您不限於將值轉換為原始類型。您還可以將值轉換為物件。定義將值轉換為物件的自訂轉換與轉換為原始類型非常相似;但是,如果您的值物件包含多個資料庫欄位,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
{
    /**
     * 轉換給定的值。
     *
     * @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']
        );
    }

    /**
     * 為儲存準備給定的值。
     *
     * @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 介面。

值物件快取

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

如果您想禁用自訂轉換類別的物件快取行為,您可以在自訂轉換類別上聲明一個公共 withoutObjectCaching 屬性:

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

    // ...
}

陣列 / JSON 序列化

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

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

/**
 * 獲取值的序列表示。
 *
 * @param  array<string, mixed>  $attributes
 */
public function serialize(
    Model $model,
    string $key,
    mixed $value,
    array $attributes,
): string {
    return (string) $value;
}

入站轉換

偶爾,您可能需要編寫一個僅轉換設定在模型上的值且在從模型存取屬性時不執行任何操作的自訂轉換類別。

僅入站自訂轉換應實現 CastsInboundAttributes 介面,該介面只需要定義一個 set 方法。可以使用 --inbound 選項調用 make:cast Artisan 命令來生成僅入站的轉換類別:

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
{
    /**
     * 建立一個新的轉換類別實例。
     */
    public function __construct(
        protected string|null $algorithm = null,
    ) {}

    /**
     * 為儲存準備給定的值。
     *
     * @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);
    }
}

轉換參數

將自訂轉換附加到模型時,可以透過使用 : 字元將它們與類別名稱分隔,並使用逗號分隔多個參數來指定轉換參數。參數將被傳遞給轉換類別的構造函數:

/**
 * 獲取應被轉換的屬性。
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'secret' => AsHash::class.':sha256',
    ];
}

比較轉換值

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

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

/**
 * 判斷給定的值是否相等。
 *
 * @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
{
    /**
     * 獲取從 / 到此轉換目標轉換時要使用的轉換器類別名稱。
     *
     * @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',
    ];
}

Castables 和匿名轉換類別

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

<?php

namespace App\ValueObjects;

use Illuminate\Contracts\Database\Eloquent\Castable;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;

class Address implements Castable
{
    // ...

    /**
     * 獲取從 / 到此轉換目標轉換時要使用的轉換器類別。
     *
     * @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,
                ];
            }
        };
    }
}