Eloquent:更動子與型別轉換

簡介

通過存取子 (Accessor)、更動子 (Mutator)、與型別轉換,便可在從 Model 實體上存取 Eloquent 屬性時轉換其值。舉例來說,我們可能想用 Laravel 的加密功能 來在資料庫內加密某個值,並在從 Eloquent Model 上存取該屬性時自動解密。或者,我們可能會想將某個值轉換為 JSON 字串來儲存進資料庫,然後在 Eloquent Model 上以陣列來存取。

存取子 (Accessor) 與更動子 (Mutator)

定義存取子

存取子 (Accessor) 可在存取 Eloquent 屬性時轉換其值。若要定義存取子,可在 Model 中建立一個 get{Attribute}Attribute 方法。其中,{Attribute} 方法即為要存取的欄位之「Studly 命名法」大小寫的屬性名稱。

在這個例子中,我們會為 first_name 屬性定義存取子。在嘗試取得 first_name 屬性時,Eloquent 會自動呼叫該存取子:

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class User extends Model
8{
9 /**
10 * Get the user's first name.
11 *
12 * @param string $value
13 * @return string
14 */
15 public function getFirstNameAttribute($value)
16 {
17 return ucfirst($value);
18 }
19}
1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class User extends Model
8{
9 /**
10 * Get the user's first name.
11 *
12 * @param string $value
13 * @return string
14 */
15 public function getFirstNameAttribute($value)
16 {
17 return ucfirst($value);
18 }
19}

如上所見,該欄位的原始值會傳給該存取子,讓你可以進行操作並回傳值。若要存取存取子的值,只需要在 Model 實體上存取 first_name 屬性即可:

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

存取子也不一定得要與單一屬性互動。你也可以使用存取子來回傳一個從現有屬性組合來的新的值:

1/**
2 * Get the user's full name.
3 *
4 * @return string
5 */
6public function getFullNameAttribute()
7{
8 return "{$this->first_name} {$this->last_name}";
9}
1/**
2 * Get the user's full name.
3 *
4 * @return string
5 */
6public function getFullNameAttribute()
7{
8 return "{$this->first_name} {$this->last_name}";
9}
lightbulb

若想讓過這些計算過的值包含在 Model 的陣列或 JSON 呈現上,則需要將這些欄位附加上去

定義更動子

更動子 (Mutator) 可在寫入 Eloquent 屬性時轉換其值。若要定義更動子,可在 Model 中建立一個 set{Attribute}Attribute 方法。其中,{Attribute} 方法即為要存取的欄位之「Studly 命名法」大小寫的屬性名稱。

我們來為 first_name 屬性定義更動子。該更動子會在嘗試於 Model 上設定 first_name 屬性值時被呼叫:

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class User extends Model
8{
9 /**
10 * Set the user's first name.
11 *
12 * @param string $value
13 * @return void
14 */
15 public function setFirstNameAttribute($value)
16 {
17 $this->attributes['first_name'] = strtolower($value);
18 }
19}
1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class User extends Model
8{
9 /**
10 * Set the user's first name.
11 *
12 * @param string $value
13 * @return void
14 */
15 public function setFirstNameAttribute($value)
16 {
17 $this->attributes['first_name'] = strtolower($value);
18 }
19}

該更動子會接收目前正在設定的屬性的值,讓你可以更改其值並將更改過的值設定在 Eloquent Model 內部的 $attributes 屬性上。若要使用這個更動子,只需要在 Eloquent Model 上設定 first_name 屬性即可:

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

在此範例中,setFirstNameAttribute 函式會以 Sally 值呼叫。改更動子接著會在名字上套用 strtolower 韓式,並將其結果設定到內部的 $attribuets 陣列上。

屬性型別轉換

屬性型別轉換提供了與存取子及更動子類似的方法。不過,你不需要手動在 Model 內定義任何額外的方法。通過 Model 上的 $casts 屬性,就可以方便地將屬性轉換為常見的資料型別。

$casts 屬性應為一個陣列,其索引鍵為要進行型別轉換的屬性名稱,而值則為要將該欄位進行型別轉換的型別。支援的轉換型別如下:

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

為了演示屬性型別轉換,我們來對 is_admin 屬性進行型別轉換。該欄位在資料庫中是以整數 (01) 來表示布林值的:

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class User extends Model
8{
9 /**
10 * The attributes that should be cast.
11 *
12 * @var array
13 */
14 protected $casts = [
15 'is_admin' => 'boolean',
16 ];
17}
1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class User extends Model
8{
9 /**
10 * The attributes that should be cast.
11 *
12 * @var array
13 */
14 protected $casts = [
15 'is_admin' => 'boolean',
16 ];
17}

定義好型別轉換後,只要存取 is_admin 屬性,即使該屬性在資料庫中以整數來儲存,該屬性值總是會被轉換為布林值:

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

若有需要在執行階段加上新的、臨時的型別轉換,則可使用 mergeCasts 方法。這些型別轉換定義會被加到所有在 Model 中已定義的型別轉換上:

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

null 的屬性將不會進行型別轉換。此外,定義型別轉換 (或屬性) 時,也不應有相同名稱的關聯。

Stringable 的型別轉換

可以使用 Illuminate\Database\Eloquent\Casts\AsStringable 型別轉換類別來講 Model 屬性轉換為 [Fluent Illuminate\Support\Stringable 物件] (/docs/8.x/helpers#fluent-strings-method-list):

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 * The attributes that should be cast.
12 *
13 * @var array
14 */
15 protected $casts = [
16 'directory' => AsStringable::class,
17 ];
18}
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 * The attributes that should be cast.
12 *
13 * @var array
14 */
15 protected $casts = [
16 'directory' => AsStringable::class,
17 ];
18}

陣列與 JSON 的型別轉換

array 型別轉換特別適合搭配宜 JSON 序列化保存的欄位。舉例來說,說資料庫內有個包含了序列化 JSON 的 JSONTEXT 欄位型別,則加上 array 型別轉換,就可以在從 Eloquent Model 上存取該欄位時自動將屬性反串聯化為 PHP 陣列:

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class User extends Model
8{
9 /**
10 * The attributes that should be cast.
11 *
12 * @var array
13 */
14 protected $casts = [
15 'options' => 'array',
16 ];
17}
1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class User extends Model
8{
9 /**
10 * The attributes that should be cast.
11 *
12 * @var array
13 */
14 protected $casts = [
15 'options' => 'array',
16 ];
17}

定義好型別轉換後,存取 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();
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']);
1$user = User::find(1);
2 
3$user->update(['options->key' => 'value']);

陣列物件與 Collection 的型別轉換

雖然使用標準的 array 型別轉換對於大多數專案就夠用了,但 array 也有一些缺點。由於 array 型別轉換回傳的是原生型別,因此我們無法直接更改陣列的元素。舉例來說,下列程式碼會觸發 PHP 錯誤:

1$user = User::find(1);
2 
3$user->options['key'] = $value;
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 * The attributes that should be cast.
5 *
6 * @var array
7 */
8protected $casts = [
9 'options' => AsArrayObject::class,
10];
1use Illuminate\Database\Eloquent\Casts\AsArrayObject;
2 
3/**
4 * The attributes that should be cast.
5 *
6 * @var array
7 */
8protected $casts = [
9 'options' => AsArrayObject::class,
10];

Laravel 還提供了一個類似的 AsCollection 型別轉換,可將 JSON 屬性轉換為 Laravel 的 Collection 實體:

1use Illuminate\Database\Eloquent\Casts\AsCollection;
2 
3/**
4 * The attributes that should be cast.
5 *
6 * @var array
7 */
8protected $casts = [
9 'options' => AsCollection::class,
10];
1use Illuminate\Database\Eloquent\Casts\AsCollection;
2 
3/**
4 * The attributes that should be cast.
5 *
6 * @var array
7 */
8protected $casts = [
9 'options' => AsCollection::class,
10];

日期的型別轉換

預設情況下,Eloquent 會將 created_atupdated_at 欄位轉換為 Carbon 實體。Carbon 繼承自 PHP 的 DateTime 類別,並提供了各種實用方法。可以通過往 Model 的 $casts 屬性陣列內定義額外的日期型別轉換來給其他日期屬性進行轉換。通常來說,日期應使用 datetimeimmutable_datetime 型別轉換類型。

在定義 datedatetime 型別轉換時,也可以指定日期的格式。該格式會在 Model 被序列化成陣列或 JSON 時使用:

1/**
2 * The attributes that should be cast.
3 *
4 * @var array
5 */
6protected $casts = [
7 'created_at' => 'datetime:Y-m-d',
8];
1/**
2 * The attributes that should be cast.
3 *
4 * @var array
5 */
6protected $casts = [
7 'created_at' => 'datetime:Y-m-d',
8];

在將欄位轉換為日期時,可以將相應的 Model 屬性值設為 UNIX 時戳、日期字串 (Y-m-d)、日期與時間字串、或是 DateTime / Carbon 實體。日期的值會被正確地轉換並保存在資料庫中。

在 Model 中定義 serializeDate 方法,即可為 Model 中所有的日期定義預設的序列化方法。改方法並不會影響日期儲存到資料庫時的格式化方法:

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

若要指定用來將 Model 日期保存在資料庫時使用的格式,可在 Model 中定義 $dateFormat 屬性:

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

日期型別轉換、序列化、與時區

預設情況下,不論專案的 timezone 設定選項設為哪個時區,datedatetime 都會將日期序列化為 UTC 的 ISO-8601 日期字串 (1986-05-28T21:05:54.000000Z)。我們強烈建議你保持使用這個序列化格式,也建議你只將專案的 timezone 設定選項設為預設的 UTC,並讓專案中以 UTC 來儲存所有的日期時間。在專案中保持一致地使用 UTC 時區,可為其他 PHP 與 JavaScript 的日期操作函示庫提供最大的互用性。

若有在 datedatetime 型別轉換內提供自訂格式,如 datetime:Y-m-d H:i:s,則在進行日期序列化時,會使用 Carbon 實體內部的時區。一般來說,這個時區就是專案的 timezone 設定選項。

Enum 的型別轉換

exclamation

Enum 型別轉換只可在 PHP 8.1 以上使用。

Eloquent 也能讓我們將屬性值轉換為 PHP Enum。為此,可在 Model 中的 $casts 屬性陣列中指定要型別轉換的屬性與 Enum:

1use App\Enums\ServerStatus;
2 
3/**
4 * The attributes that should be cast.
5 *
6 * @var array
7 */
8protected $casts = [
9 'status' => ServerStatus::class,
10];
1use App\Enums\ServerStatus;
2 
3/**
4 * The attributes that should be cast.
5 *
6 * @var array
7 */
8protected $casts = [
9 'status' => ServerStatus::class,
10];

定義好 Model 的型別轉換後,每次存取該屬性時就會自動轉換對 Enum 進行轉換:

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

加密的型別轉換

encrypted 型別轉換會通過 Laravel 的內建加密功能來加密 Model 的屬性值。此外,還有 encrypted:array, encrypted:collection, encrypted:object, AsEncryptedArrayObject, 與 AsEncryptedCollection 等型別轉換,這些型別轉換都與其未加密的版本一樣以相同方式運作。不過,可想而知,底層的值會先加密才保存進資料庫。

由於加密後的文字長度時無法預測的,且通常比明文的版本還要長,因此請確保其資料庫欄位為 TEXT 型別或更大的型別。此外,由於在資料庫中值都是經過加密的,因此你也沒辦法查詢或搜尋加密過的屬性質。

查詢時的型別轉換

有時候我們可能會需要在執行查詢時套用型別轉換,例如從資料表中選擇原始資料時。舉例來說,假設有下列查詢:

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();
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();
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 中有各式內建的實用型別轉換類型。不過,有時候我們也會需要定義自己的型別轉換類型。我們可以通過實作 CastsAttributes 介面來自訂型別轉換類型。

實作了這個介面的類別必須定義一組 getset 方法。get 方法用於將儲存在資料庫內的原始值轉換為型別值;set 方法則負責將型別值轉換為可儲存在資料庫內的原始值。在這裡,我們將重新實作一個內建的 json 型別轉換類型為例:

1<?php
2 
3namespace App\Casts;
4 
5use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
6 
7class Json implements CastsAttributes
8{
9 /**
10 * Cast the given value.
11 *
12 * @param \Illuminate\Database\Eloquent\Model $model
13 * @param string $key
14 * @param mixed $value
15 * @param array $attributes
16 * @return array
17 */
18 public function get($model, $key, $value, $attributes)
19 {
20 return json_decode($value, true);
21 }
22 
23 /**
24 * Prepare the given value for storage.
25 *
26 * @param \Illuminate\Database\Eloquent\Model $model
27 * @param string $key
28 * @param array $value
29 * @param array $attributes
30 * @return string
31 */
32 public function set($model, $key, $value, $attributes)
33 {
34 return json_encode($value);
35 }
36}
1<?php
2 
3namespace App\Casts;
4 
5use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
6 
7class Json implements CastsAttributes
8{
9 /**
10 * Cast the given value.
11 *
12 * @param \Illuminate\Database\Eloquent\Model $model
13 * @param string $key
14 * @param mixed $value
15 * @param array $attributes
16 * @return array
17 */
18 public function get($model, $key, $value, $attributes)
19 {
20 return json_decode($value, true);
21 }
22 
23 /**
24 * Prepare the given value for storage.
25 *
26 * @param \Illuminate\Database\Eloquent\Model $model
27 * @param string $key
28 * @param array $value
29 * @param array $attributes
30 * @return string
31 */
32 public function set($model, $key, $value, $attributes)
33 {
34 return json_encode($value);
35 }
36}

定義好自訂的型別轉換類型後,就可以使用類別名稱將其附加到 Model 屬性內:

1<?php
2 
3namespace App\Models;
4 
5use App\Casts\Json;
6use Illuminate\Database\Eloquent\Model;
7 
8class User extends Model
9{
10 /**
11 * The attributes that should be cast.
12 *
13 * @var array
14 */
15 protected $casts = [
16 'options' => Json::class,
17 ];
18}
1<?php
2 
3namespace App\Models;
4 
5use App\Casts\Json;
6use Illuminate\Database\Eloquent\Model;
7 
8class User extends Model
9{
10 /**
11 * The attributes that should be cast.
12 *
13 * @var array
14 */
15 protected $casts = [
16 'options' => Json::class,
17 ];
18}

數值物件的型別轉換

進行型別轉換時,我們不只可以將值轉換為 PHP 的原生型別,我們還可以將值轉換為物件。定義這種將值轉換為物件的自訂型別轉換就跟轉換成原生型別類似。不過,在這種型別轉換類別中的 set 方法應回傳一組在 Model 上用於設定原始、可儲存值的索引鍵/值配對。

在這裡,我們以將多個 Model 值轉換到單一 Address 數值物件的自訂型別轉換類別為例。我們假設 Address 值有兩個公用屬性:lineOnelineTwo

1<?php
2 
3namespace App\Casts;
4 
5use App\Models\Address as AddressModel;
6use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
7use InvalidArgumentException;
8 
9class Address implements CastsAttributes
10{
11 /**
12 * Cast the given value.
13 *
14 * @param \Illuminate\Database\Eloquent\Model $model
15 * @param string $key
16 * @param mixed $value
17 * @param array $attributes
18 * @return \App\Models\Address
19 */
20 public function get($model, $key, $value, $attributes)
21 {
22 return new AddressModel(
23 $attributes['address_line_one'],
24 $attributes['address_line_two']
25 );
26 }
27 
28 /**
29 * Prepare the given value for storage.
30 *
31 * @param \Illuminate\Database\Eloquent\Model $model
32 * @param string $key
33 * @param \App\Models\Address $value
34 * @param array $attributes
35 * @return array
36 */
37 public function set($model, $key, $value, $attributes)
38 {
39 if (! $value instanceof AddressModel) {
40 throw new InvalidArgumentException('The given value is not an Address instance.');
41 }
42 
43 return [
44 'address_line_one' => $value->lineOne,
45 'address_line_two' => $value->lineTwo,
46 ];
47 }
48}
1<?php
2 
3namespace App\Casts;
4 
5use App\Models\Address as AddressModel;
6use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
7use InvalidArgumentException;
8 
9class Address implements CastsAttributes
10{
11 /**
12 * Cast the given value.
13 *
14 * @param \Illuminate\Database\Eloquent\Model $model
15 * @param string $key
16 * @param mixed $value
17 * @param array $attributes
18 * @return \App\Models\Address
19 */
20 public function get($model, $key, $value, $attributes)
21 {
22 return new AddressModel(
23 $attributes['address_line_one'],
24 $attributes['address_line_two']
25 );
26 }
27 
28 /**
29 * Prepare the given value for storage.
30 *
31 * @param \Illuminate\Database\Eloquent\Model $model
32 * @param string $key
33 * @param \App\Models\Address $value
34 * @param array $attributes
35 * @return array
36 */
37 public function set($model, $key, $value, $attributes)
38 {
39 if (! $value instanceof AddressModel) {
40 throw new InvalidArgumentException('The given value is not an Address instance.');
41 }
42 
43 return [
44 'address_line_one' => $value->lineOne,
45 'address_line_two' => $value->lineTwo,
46 ];
47 }
48}

對數值物件進行型別轉換時,對數值物件進行的所有更改都會在 Model 儲存前同步回 Model 上:

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

若有打算要將包含數值物件的 Eloquent Model 序列化為 JSON 或陣列,則該數值物件應實作 Illuminate\Contracts\Support\ArrayableJsonSerializable 介面。

Array / JSON 的序列化

當 Eloquent Model 通過 toArraytoJson 轉換為陣列或 JSON 時,只要自訂的型別轉換數值物件有實作 Illuminate\Contracts\Support\ArrayableJsonSerializable 介面,該數值物件也會一併被序列化。不過,若我們使用的數值物件是來自第三方套件的,那我們可能就沒辦法提供這些負責序列化介面。

因此,我們可以指定讓自訂型別轉換類別來負責處理數值物件的序列化。為此,自訂型別轉換類別應實作 Illuminate\Contracts\Database\Eloquent\SerializesCastableAttributes 介面。實作這個介面,就代表該類別中應包含一個 serialize 方法,該方法應回傳數值物件的序列化形式:

1/**
2 * Get the serialized representation of the value.
3 *
4 * @param \Illuminate\Database\Eloquent\Model $model
5 * @param string $key
6 * @param mixed $value
7 * @param array $attributes
8 * @return mixed
9 */
10public function serialize($model, string $key, $value, array $attributes)
11{
12 return (string) $value;
13}
1/**
2 * Get the serialized representation of the value.
3 *
4 * @param \Illuminate\Database\Eloquent\Model $model
5 * @param string $key
6 * @param mixed $value
7 * @param array $attributes
8 * @return mixed
9 */
10public function serialize($model, string $key, $value, array $attributes)
11{
12 return (string) $value;
13}

輸入型別轉換

我們偶爾會需要一種型別轉換:只在對 Model 賦值時轉換值,存取時則不進行任何轉換。純輸入的型別轉換最常見的例子就是「雜湊」型別轉換。純輸入的型別轉換應實作 CastsInboundAttributes 介面,該介面只要求要定義 set 方法。

1<?php
2 
3namespace App\Casts;
4 
5use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes;
6 
7class Hash implements CastsInboundAttributes
8{
9 /**
10 * The hashing algorithm.
11 *
12 * @var string
13 */
14 protected $algorithm;
15 
16 /**
17 * Create a new cast class instance.
18 *
19 * @param string|null $algorithm
20 * @return void
21 */
22 public function __construct($algorithm = null)
23 {
24 $this->algorithm = $algorithm;
25 }
26 
27 /**
28 * Prepare the given value for storage.
29 *
30 * @param \Illuminate\Database\Eloquent\Model $model
31 * @param string $key
32 * @param array $value
33 * @param array $attributes
34 * @return string
35 */
36 public function set($model, $key, $value, $attributes)
37 {
38 return is_null($this->algorithm)
39 ? bcrypt($value)
40 : hash($this->algorithm, $value);
41 }
42}
1<?php
2 
3namespace App\Casts;
4 
5use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes;
6 
7class Hash implements CastsInboundAttributes
8{
9 /**
10 * The hashing algorithm.
11 *
12 * @var string
13 */
14 protected $algorithm;
15 
16 /**
17 * Create a new cast class instance.
18 *
19 * @param string|null $algorithm
20 * @return void
21 */
22 public function __construct($algorithm = null)
23 {
24 $this->algorithm = $algorithm;
25 }
26 
27 /**
28 * Prepare the given value for storage.
29 *
30 * @param \Illuminate\Database\Eloquent\Model $model
31 * @param string $key
32 * @param array $value
33 * @param array $attributes
34 * @return string
35 */
36 public function set($model, $key, $value, $attributes)
37 {
38 return is_null($this->algorithm)
39 ? bcrypt($value)
40 : hash($this->algorithm, $value);
41 }
42}

型別轉換的參數

在 Model 上設定自訂型別轉換時,可以指定型別轉換的參數,請使用 : 字元來區分型別轉換類別名稱與參數,並使用逗號來區分多個參數。這些參數會傳給型別轉換類別的建構函式:

1/**
2 * The attributes that should be cast.
3 *
4 * @var array
5 */
6protected $casts = [
7 'secret' => Hash::class.':sha256',
8];
1/**
2 * The attributes that should be cast.
3 *
4 * @var array
5 */
6protected $casts = [
7 'secret' => Hash::class.':sha256',
8];

Castable

我們可以讓專案中的數值物件自己定義自己的自訂型別轉換類別。與在 Model 中設定自訂的型別轉換類別相比,我們可以設定實作了 Illuminate\Contracts\Database\Eloquent\Castable 介面的數值物件類別:

1use App\Models\Address;
2 
3protected $casts = [
4 'address' => Address::class,
5];
1use App\Models\Address;
2 
3protected $casts = [
4 'address' => Address::class,
5];

實作了 Castable 介面的物件必須定義 castUsing 方法。該方法則應回傳用於對 Castable 類別進行型別轉換的自訂型別轉換類別名稱:

1<?php
2 
3namespace App\Models;
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 $arguments
14 * @return string
15 */
16 public static function castUsing(array $arguments)
17 {
18 return AddressCast::class;
19 }
20}
1<?php
2 
3namespace App\Models;
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 $arguments
14 * @return string
15 */
16 public static function castUsing(array $arguments)
17 {
18 return AddressCast::class;
19 }
20}

即使是使用 Castable 類別,也可以在 $casts 定義中提供引數。這些引數會被傳給 castUsing 方法:

1use App\Models\Address;
2 
3protected $casts = [
4 'address' => Address::class.':argument',
5];
1use App\Models\Address;
2 
3protected $casts = [
4 'address' => Address::class.':argument',
5];

Castable 與匿名型別轉換類別

通過將「Castable」與 PHP 的匿名函式搭配使用,我們就能在單一 Castable 物件內定義數值物件與其型別轉換邏輯。為此,請在數值物件的 castUsing 方法內回傳一個匿名類別。這個匿名類別應實作 CastsAttributes 介面:

1<?php
2 
3namespace App\Models;
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 $arguments
16 * @return object|string
17 */
18 public static function castUsing(array $arguments)
19 {
20 return new class implements CastsAttributes
21 {
22 public function get($model, $key, $value, $attributes)
23 {
24 return new Address(
25 $attributes['address_line_one'],
26 $attributes['address_line_two']
27 );
28 }
29 
30 public function set($model, $key, $value, $attributes)
31 {
32 return [
33 'address_line_one' => $value->lineOne,
34 'address_line_two' => $value->lineTwo,
35 ];
36 }
37 };
38 }
39}
1<?php
2 
3namespace App\Models;
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 $arguments
16 * @return object|string
17 */
18 public static function castUsing(array $arguments)
19 {
20 return new class implements CastsAttributes
21 {
22 public function get($model, $key, $value, $attributes)
23 {
24 return new Address(
25 $attributes['address_line_one'],
26 $attributes['address_line_two']
27 );
28 }
29 
30 public function set($model, $key, $value, $attributes)
31 {
32 return [
33 'address_line_one' => $value->lineOne,
34 'address_line_two' => $value->lineTwo,
35 ];
36 }
37 };
38 }
39}
翻譯進度
100% 已翻譯
更新時間:
2024年6月30日 上午8:18:00 [世界標準時間]
翻譯人員:
  • cornch
幫我們翻譯此頁

留言

尚無留言

“Laravel” is a Trademark of Taylor Otwell.
The source documentation is released under MIT license. See laravel/docs on GitHub for details.
The translated documentations are released under MIT license. See cornch/laravel-docs-l10n on GitHub for details.