商業,創業,業務開發,自媒體經營,生活美食,時事分析。
這是我的 FB粉專 以及 IG,我也滿常使用 Threads,歡迎大家追蹤互動~
在創業、職涯、人生的路上,希望大家一起陪伴與成長。
去年 (2022) 發布的 Laravel 9 中,Eloquent Accessor & Mutator 迎來了一次大改,Attribute Casting 的部分則是調整不大。
伴隨著 PHP 8.1 的釋出,Laravel 8 的後繼版本已經支援 Enum Casting (當然 Laravel 9 之後的版本也會支援)。
延伸閱讀:Laravel 9 的新功能:最低要求 PHP 8.0,底層更新為 Symfony 6.0,開始使用 PHP 8.1 的 Enum
larry 想藉 Laravel 9 大幅更新 Accessor & Mutator 的這個機會,走一遍 Mutator & Casting 的官方文件。本篇文章會以 Laravel 9 Mutator & Casting 官方文件為主。
https://laravel.com/docs/9.x/eloquent-mutators
首先,依照官方文件的語意,Accessor 指的是 get 這個動作,Mutator 指的是 set。
什麼時機會使用到 Accessor / Mutator 呢?例如,每次存一個值都要 encrypt,或是取出來用時都要 decrypt。或是,你要每次都要把 array 轉成 json 存到資料庫,使用時取出 json 要再轉回 array。
也就是,如果你每次「存」或「取」都要做一定動作,可以考慮使用 Accessor / Mutator。Laravel 9 開始,假如你要存取一個 model 裡的 first_name
attribute,要這樣寫
use Illuminate\Database\Eloquent\Casts\Attribute;
// 注意有 return type
// 注意是 protected method, 不允許調用端直接調用
protected function firstName(): Attribute
{
// 可以只寫 get 或 set 其一
return Attribute::make(
get: fn ($value) => ucfirst($value),
set: fn ($value) => strtolower($value),
);
}
有時後程式邏輯需要把資料庫中的幾個欄位,重組成一個新的 object,Laravel 稱之為 value object。
use Illuminate\Database\Eloquent\Casts\Attribute;
// 注意是 protected method
// 當調用端存取 address 時會觸發這個 function
// 也就是自動新增了一個 address attribute
protected function address(): Attribute
{
// class Address 就是所謂的 value object
return Attribute::make(
get: fn ($value, $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,
],
);
}
讀者可以觀察一下,get / set 使用的都是 PHP 7.4 的 arrow function。get return 的是一個新生成的 object,set 中設定的是一個 array。
class Address
就是所謂的 value object。調用端要使用,會像是 $user->address->lineOne
。
定義 value object 時,lineOne
、lineTwo
都要是 public。
class Address
{
public $lineOne;
public $lineTwo;
public function __construct(...)
{
//…
}
}
其實把 DB 中的欄位重新包裝成 value object (如上方的 class Address
),在程式邏輯中使用,已經算是 Attribute Casting。Laravel 提供了一個不用寫 Accessor / Mutator 的方式,將 DB 欄位轉成常用的資料格式。
Attribute Casting
例如 Laravel 的 DB migration boolean
,實際在 MySql 裡是 tinyint(1)
,存的值是 1 或 0。這時你可以 cast 成 boolean
。
use Illuminate\Database\Eloquent\Casts\AsStringable;
use Illuminate\Database\Eloquent\Casts\AsArrayObject;
use Illuminate\Database\Eloquent\Casts\AsCollection;
class User extends Model
{
protected $casts = [
'is_admin' => 'boolean',
'directory' => AsStringable::class,
'options' => 'array',
// 也可以 cast 成 PHP ArrayObject 或 Laravel Collection
//'options' => AsArrayObject::class,
//'options' => AsCollection::class,
];
}
也就是如果 DB migration 是 boolean
,這樣可以使用沒錯。但如果要寫漂亮一點,model attribute 可以 cast 成 boolean
。字串的部分,你也可以 cast 成 Laravel fluent string,方便後續操作。
另外,如果你的 DB migration 有 json
或 text
,model attribute 可以 cast 成 array
,存取時自動會在 array 和 json 格式轉換。等同於資料庫欄位可以直接存 array,這是很有趣的。
接下來是一個重要功能 Enum Casting (僅支援 PHP 8.1 以上的環境)
use App\Enums\ServerStatus;
protected $casts = [
'status' => ServerStatus::class,
];
這樣要存或取 $model->status
都會直接轉換成 enum。
if ($server->status == ServerStatus::Provisioned)
{
$server->status = ServerStatus::Ready;
$server->save();
}
Custom Casts
如果你執行 Artisan command
php artisan make:cast YourClass
就會新增一個 YourClass 在 app/Casts
目錄下。例如 class Address
use App\ValueObjects\Address as AddressValueObject;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class Address implements CastsAttributes
{
public function get($model, $key, $value, $attributes)
{
// 將 DB 欄位轉成 value object return
return new AddressValueObject(
$attributes['address_line_one'],
$attributes['address_line_two']
);
}
public function set($model, $key, $value, $attributes)
{
if (! $value instanceof AddressValueObject)
{
//throw exception
}
// return array 去設定 DB 欄位
return [
'address_line_one' => $value->lineOne,
'address_line_two' => $value->lineTwo,
];
}
}
記得文章上方的 Accessor / Mutator,使用的是 PHP 7.4 的 arrow function。function expression 內容就如同上方範例,只是 Custom Casts 把程式獨立到一個 class。
記得要設定 model,這樣等於新增了一個 address
attribute (DB 存的是 address_line_one
, address_line_two
)
use App\Casts\Address;
protected $casts = [
‘address’ => Address::class,
];
結論
Accessor / Mutator 與 Casting 是一體的兩面。你可以寫 Accessor / Mutator,但如果正好有 Laravel 預先定義的 cast 資料類型,就直接用 casting 比較方便,包含 PHP 8.1 的 enum。
使用情境上,如果每次存取資料都有一個固定動作,可以考慮使用 Accessor / Mutator 或 Casting。例如每次存一個值都要 encrypt,或是每次都要把 array 轉成 json 存到資料庫,使用時取出 json 要再轉回 array。
或是轉成 Laravel Stringable 或 Collection 等資料格式,方便後續處理。將幾個 DB 欄位重新包裝成 value object 也是一個使用情境。
Accessor / Mutator 與 Casting 不是 Laravel 的大主題,但實際走過文件,還是有很多細節要注意。希望大家在資料庫欄位 / 程式邏輯之間的轉換 (ORM,Object Relational Mapping),也有更深一層的理解囉。
商業,創業,業務開發,自媒體經營,生活美食,時事分析。
這是我的 FB粉專 以及 IG,我也滿常使用 Threads,歡迎大家追蹤互動~
在創業、職涯、人生的路上,希望大家一起陪伴與成長。