iT邦幫忙

2026 iThome 鐵人賽

DAY 7
0
Software Development

一套真實運作中的 Laravel 系統,拆解它的原生機制系列 第 7

Day 07:Model Factory 語意化設計——用具名 state 表達建立資料的意圖

  • 分享至 

  • xImage
  •  

前言:測試資料要用什麼身份建立,寫在哪裡比較好?

「測試資料就是隨便造幾筆,反正跑完測試就丟掉,有什麼好講究的?」

資料本身或許不需要講究,但建立資料的方式,決定了你的程式碼有多容易讀懂。今天要看的是 Laravel Model Factory 的一個實用技巧——用具名的 state 方法,把「這筆資料代表什麼身份」講清楚,而不是每次都手動組一堆屬性。

今日目標

  • 認識 Laravel Model Factory 的 state 方法怎麼運作
  • 看一個真實案例:superAdmin()admin() 這種具名 state 怎麼設計
  • 理解語意化 state 方法換來的可讀性好處
  • 知道這種設計適用的範圍跟侷限

本文主體

基本的 Factory 定義

Laravel 的 Model Factory 用 definition() 方法定義一筆資料的預設樣貌:

class UserFactory extends Factory
{
    public function definition(): array
    {
        return [
            'name' => fake()->name(),
            'username' => fake()->unique()->userName(),
            'email' => fake()->unique()->safeEmail(),
            'email_verified_at' => now(),
            'password' => static::$password ??= Hash::make('password'),
            'remember_token' => Str::random(10),
        ];
    }
}

這定義了一個「一般使用者」該有的樣子——有名字、有信箱、密碼已經雜湊過。單純呼叫 User::factory()->create(),就能造出一筆符合這個樣貌的測試使用者。

問題:不同角色的使用者,該怎麼造

這個系統的使用者有角色分級——一般使用者、管理員、最高權限管理員,各自的權限不同。如果每次要造一個「管理員」測試資料,都要手動組出對應的角色關聯,程式碼會變得又長又難懂:

// 不用具名 state,每次都要手動組
$user = User::factory()->create();
$role = Role::factory()->create(['name' => 'Admin']);
$user->roles()->attach($role);

具名 state:把「這是什麼身份」講清楚

這個系統的做法是在 Factory 裡定義具名的 state 方法:

public function superAdmin(): static
{
    return $this->has(Role::factory()->state(['name' => 'Super Admin']));
}

public function admin(): static
{
    return $this->has(
        Role::factory()->state(['name' => 'Admin'])
    );
}

有了這兩個方法,建立測試資料的地方直接寫:

$user = User::factory()->superAdmin()->create();
$user = User::factory()->admin()->create();

這一行程式碼本身就在說明「這是什麼」,不需要額外註解,也不需要讀者自己去推敲「這個 user 關聯了一個叫 Super Admin 的角色,所以他應該是最高權限」——方法名稱直接講出了意圖。has() 是 Laravel Factory 提供的關聯建立方法,這裡的用法是「順便建立一個 Role,並把它關聯到這個 User 上」,一行程式碼同時做完「造角色」跟「建立關聯」兩件事。

❌ 每次都手動組出角色關聯

$user = User::factory()->create();
$role = Role::factory()->create(['name' => 'Super Admin']);
$user->roles()->attach($role);

三行才能表達一件事,而且讀的人要自己拼湊「這是要造一個最高權限使用者」這個意圖。

✅ 用具名 state 方法一行表達意圖

$user = User::factory()->superAdmin()->create();

官方內建的 unverified() state 也是同一種思路

這個系統的 UserFactory 也保留了 Laravel 內建常見的 unverified() state:

public function unverified(): static
{
    return $this->state(fn (array $attributes) => [
        'email_verified_at' => null,
    ]);
}

unverified() 這種寫法,跟 superAdmin()admin() 是同一種設計原則的兩種實現方式——unverified() 只是覆寫幾個屬性值(用 state()),superAdmin() 則是額外建立關聯資料(用 has())。不管底層實作是覆寫屬性還是建立關聯,這種設計換來的都是同一件事:呼叫端不用知道「什麼樣的屬性組合才算是一個 XX 身份」,只要呼叫對應名稱的方法就好。

適用範圍跟侷限

具名 state 方法最適合套用在「有明確業務意義、會被重複用到」的資料狀態——像「這是一個最高權限使用者」「這是一筆已發佈的新聞」,這種狀態會在很多測試案例裡反覆出現,值得抽成一個具名方法。但不是每個屬性組合都值得抽——如果某個特定屬性值只有一支測試會用到一次,直接用 state(['xxx' => 'yyy']) 覆寫就好,抽成具名方法反而增加維護一個很少被重複使用的方法的成本。

今日思考題

你的專案裡有沒有一段建立測試資料的程式碼,每次都要手動組一長串屬性或關聯,才能表達「這是某種特定身份」?如果抽成一個具名的 Factory state 方法,呼叫端會不會變得更好讀?

今日重點回顧

  • Model Factory 的 state 方法可以覆寫屬性值,也可以用 has() 建立關聯資料
  • 真實案例:superAdmin()admin() 把「建立某種角色的使用者」封裝成一個具名方法,呼叫端一行程式碼就能表達意圖
  • 這種設計換來的核心價值是可讀性——呼叫端不用知道背後的屬性/關聯組合,只要呼叫對應名稱的方法
  • 值得抽成具名方法的條件:有明確業務意義、會在多處測試案例裡重複出現

明日預告

明天開始進入這個系列的第二部——先從一份 Policy 檔案看起,理解 Laravel 的授權機制怎麼決定「誰能看見什麼」。


上一篇
Day 06:Backed Enum 玩法——用類別常數當 enum 值做型別查找表
下一篇
Day 08:Policy 授權基礎——一份 Policy 檔案怎麼決定誰能看見什麼
系列文
一套真實運作中的 Laravel 系統,拆解它的原生機制14
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言