iT邦幫忙

2021 iThome 鐵人賽

DAY 20
0
Modern Web

Laravel 實務筆記系列 第 20

Eloquent ORM - Model 資料轉換

  • 分享至 

  • xImage
  •  

現在我們可以用各種方法將資料讀取出來,不過通常讀取後還要將資料做一些轉換才適用,舉個例子像是 boolean 欄位在 SQL 資料庫裡存的是數字的 0 跟 1,直接讀出來的話也會是 0/1 ,通常我們會想要將這種欄位轉換成 false/true 顯示。

對於這種情況 Eloquent 就可以在 Model 中指定欄位做資料轉換,這樣讀取出來的資料就會全部自行轉換成一樣的格式。

反過來也可以定義當寫入資料時事先對資料做轉換再儲存。

Casting

如果是簡單的資料格式轉換的話,可以在 Model 中用 $casts 定義。

這邊用 Todo 簡單做示範,用 $casts 定義將 use_id 的值轉換為布林值。

@@ -10,10 +10,22 @@ use App\Models\Tag;
 class Todo extends Model
 {
     use HasFactory;
+    
+    protected $casts = [ 
+        'use_id' => 'boolean',
+    ];
 

試著讀出來看看

可以看到本來是數字的 user_id 都變成 true 了。

casting 只會影響讀取的值,當寫入資料時還是依資料表的欄位型別儲存。

可以轉換的格式

datetime

casting 中比較常用到的是日期的轉換,把 UTC 格式轉成比較好視讀的樣子

protected $casts = [ 
    'created_at' => 'datetime:Y-m-d',
];

如果不想把日期欄位一個個加到 casting ,可以定義一個共用的 serializeDate 轉換函式

@@ -6,14 +6,30 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
 use Illuminate\Database\Eloquent\Model;
 use App\Models\User;
 use App\Models\Tag;
+use DateTimeInterface;
 
 class Todo extends Model
 {
     use HasFactory;
 
     protected $fillable = [
         'name',
     ];
+
+    protected function serializeDate(DateTimeInterface $date)
+    {
+        return $date->format('Y-m-d');
+    }

Accessor

如果要做比較複雜的轉換,讀取資料可以經由 Accessor 做資料變換,以 Todo 為例,可以看到目前的資料全都是小寫。

這時候在 Todo Model 中加上 Accessor getNameAttribute

/app/Models/Todo.php

@@ -14,6 +14,11 @@ class Todo extends Model
     protected $fillable = [
         'name',
     ];
+
+    public function getNameAttribute($value)
+    {
+        return strtoupper($value);
+    }

然後重新整理資料

資料庫中的資料完全沒變,讀出來後變成全大寫了。

因為定義了 getNameAttribute ,當經由 Todo Model 讀取資料時就會對每個 name 欄位的資料執行轉換,像這邊是用 strtoupper 將資料轉為全大寫。

要讓 Accessor 對應到欄位是經由函式的命名來設定

get<欄位的駝峰型>Attribute

舉例來說

name -> getNameAttribute
first_name -> getFirstNameAttribute

這種參照法還有個有趣的用途,就是創造原本不存在的欄位。

像是在 Model 建立 getUpperNameAttribute 函式

public function getUpperNameAttribute()
{ 
    return strtoupper($this->name);
}

getUpperNameAttribute 會將目前 Model 中的 name 值轉為全大寫後回傳。

這時候用

Todo::first()->upper_name

會發現竟然能讀到值,明明 Todo 沒有 upper_name 這個欄位。

不過只是這樣定義的話,就只有動態讀取的時候能夠取得 upper_name 的值,並不會常駐在資料結構中,像是用 Todo::get() 的話會發現沒有 upper_name 。

appends

如果希望讀取出來的每個 Todo 都帶有 upper_name 資料的話,要給 Model 追加定義 $appends 變數。

@@ -14,6 +14,13 @@ class Todo extends Model
     protected $fillable = [
         'name',
     ];
+    protected $appends = ['upper_name'];
 
     public function getUpperNameAttribute()
     { 
         return strtoupper($this->name);
     }

Model 會根據 $appends 中的值尋找對應的 Accessor 讀取資料後,加入到現有的 Model 回傳當中。

注意只要有定義 $appends 就要為每個 append 的值定義 Accessor,不然找不到的話會直接報錯。

casts 跟 appends 的順序

資料讀出來會先經過 casts 做轉換,才根據 appends 追加欄位,這造成兩種狀況

  • 在 casts 裡定義 append 欄位的資料轉換是沒意義的,因為當 append 時 cast 已經執行完了
  • append 建立資料會根據 cast 之後的值

像這邊先把 name 轉換成布林值後 upper_name 也受影響了

Mutator

上面講的都是讀取資料的轉換,Model 也可以定義在寫入資料時的轉換

@@ -6,14 +6,30 @@ class Todo extends Model 
 
     protected $fillable = [
         'name',
     ];
+
+    public function setNameAttribute($value)
+    {
+        $this->attributes['name'] = strtolower($value);
+    }

一樣要注意函式的命名

set<欄位的駝峰型>Attribute

另外 Mutator 不像 Accessor 可以直接回傳值,必須賦值給 Model 上對應的欄位。


上一篇
Eloquent ORM - 多型態關聯
下一篇
Service Container
系列文
Laravel 實務筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言