不只是 CRUD!Laravel Eloquent ORM 終極實戰聖經:從模型、關聯到效能優化的屠龍之術
嘿,我是浪花科技的資深工程師 Eric。在 Laravel 的世界裡打滾這麼多年,看過無數專案,從一人小作品到企業級的龐然大物,我發現有一個東西,是所有 Laravel 開發者都繞不過去的坎,也是決定你程式碼優雅程度與專案維護性的關鍵——那就是 Eloquent ORM。
很多新手會把 Eloquent 當成一個單純的資料庫查詢工具,以為學會了 create、read、update、delete (CRUD) 就天下無敵了。少年,你太天真了!這就好比你拿到了一把絕世神兵,卻只拿它來切水果。今天的這篇 Laravel Eloquent ORM 完整指南,我就要帶你從基礎的模型設定,一路深入到精髓的模型關聯、效能殺手 N+1 問題的解決方案,以及讓你的程式碼質感瞬間提升的集合 (Collections) 魔法。準備好了嗎?泡杯咖啡,讓我們開始這趟屠龍之旅吧!
Eloquent 的核心:模型 (Model) 是什麼?
在我們開始寫任何查詢之前,得先搞懂 Eloquent 的靈魂——模型 (Model)。簡單來說,一個 Model 就是你 PHP 程式碼中,對應資料庫裡一張資料表 (Table) 的代理人。例如,你有一個 `posts` 資料表,你就會建立一個 `Post` 模型來操作它。這種將資料表映射到物件的設計模式,就叫做 Active Record Pattern。
聽起來很玄?別怕,其實就是把冷冰冰的 SQL 指令,變成我們更容易理解和操作的物件導向語法。這也是 Eloquent 最大的魅力所在:程式碼可讀性極高,而且維護起來輕鬆多了。
建立你的第一個 Model
Laravel 的 Artisan command-line tool 是我們的好朋友。要建立一個 Model,只需要一行指令:
php artisan make:model Post -m
這裡的 `-m` 參數是個小技巧,它會順便幫我們建立對應的 migration 檔案,用來定義 `posts` 資料表的結構。工程師嘛,能懶則懶,能自動化就絕不手動。
Laravel 喜歡「約定優於配置」(Convention over Configuration)。預設情況下,`Post` 這個單數、首字大寫的 Model,會自動對應到 `posts` 這個複數、全小寫的資料表。當然,你也可以打破這個約定。
綁定資料表與主鍵
如果你的資料表名稱不符合預設的複數命名規則,或者主鍵不是 `id`,別擔心,Eloquent 提供了幾個屬性讓你輕鬆設定:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
use HasFactory;
/**
* 與模型關聯的資料表。
*
* @var string
*/
protected $table = 'my_posts'; // 如果你的資料表不叫 posts
/**
* 資料表的主鍵。
*
* @var string
*/
protected $primaryKey = 'post_id'; // 如果你的主鍵不叫 id
/**
* 指示主鍵是否自動遞增。
*
* @var bool
*/
public $incrementing = false; // 如果主鍵不是自動遞增的整數
/**
* 指示模型是否自動維護時間戳記。
*
* @var bool
*/
public $timestamps = false; // 如果你不需要 created_at 和 updated_at
}
屬性設定:Mass Assignment 的愛與愁
Mass Assignment (批量賦值) 是一個很方便的功能,它允許我們用一個陣列來一次性建立或更新 Model,例如 `Post::create($request->all())`。但這也可能是一個安全漏洞!如果惡意使用者在表單中偷偷加入一個 `is_admin` 欄位,而你又沒有設防,那後果不堪設想。
為了防止這種情況,Eloquent 提供了 `$fillable` (白名單) 和 `$guarded` (黑名單) 兩個屬性,你必須二選一來使用:
- $fillable: 只有在這個陣列裡的欄位,才能被批量賦值。這是比較推薦、比較安全的作法。
- $guarded: 在這個陣列裡的欄位,不能被批量賦值。如果設為 `protected $guarded = [];`,就等於是把所有欄位都開放,這在開發初期可能很方便,但請務必了解其風險。
class Post extends Model
{
// 推薦作法:明確列出可以批量賦值的欄位
protected $fillable = ['title', 'content', 'author_id'];
// 另一種作法:只有 id 和 is_published 不能被批量賦值
// protected $guarded = ['id', 'is_published'];
}
我囉嗦一句:請養成使用 `$fillable` 的好習慣,你的資安同事會感謝你。
Eloquent 的基本功:CRUD 操作
搞定了 Model,接下來就是實際操作資料了。Eloquent 的語法非常直觀,幾乎就像在說英文。
新增資料 (Create)
// 方法一:實例化後儲存
$post = new Post;
$post->title = 'Hello, Eloquent!';
$post->content = 'This is a complete guide.';
$post->save();
// 方法二:使用 create 方法 (需要設定 $fillable)
$post = Post::create([
'title' => 'Another Post',
'content' => 'Content here.'
]);
讀取資料 (Read)
// 取得所有文章
$posts = Post::all();
// 根據主鍵查找文章
$post = Post::find(1);
// 查找,但如果找不到就拋出 404 錯誤
$post = Post::findOrFail(1);
// 加上條件查詢
$publishedPosts = Post::where('is_published', true)
->orderBy('created_at', 'desc')
->take(10)
->get();
// 只想取得第一筆符合條件的資料
$firstPost = Post::where('is_published', true)->first();
更新資料 (Update)
// 方法一:先找到模型,再更新屬性後儲存
$post = Post::find(1);
$post->title = 'Updated Title';
$post->save();
// 方法二:批量更新 (Query Builder 模式)
// 注意:這種方式不會觸發 Model 的事件,也不會自動更新 updated_at
Post::where('is_published', false)->update(['is_published' => true]);
刪除資料 (Delete)
// 刪除單一模型
$post = Post::find(1);
$post->delete();
// 根據主鍵刪除多筆資料
Post::destroy([1, 2, 3]);
// 根據條件刪除
Post::where('views', '<', 100)->delete();
另外,Eloquent 還支援「軟刪除」(Soft Deletes),資料不會真的從資料庫消失,而是標記一個 `deleted_at` 時間戳,這對於需要保留歷史紀錄的系統來說,簡直是救星!
Eloquent 的精髓:定義模型關聯 (Relationships)
如果 Eloquent 只能做 CRUD,那它跟一般的 Query Builder 沒兩樣。真正讓它封神的是強大的「關聯」功能。它讓你可以像操作物件一樣,輕鬆地存取關聯的資料。
一對一 (One to One)
例如,一個 `User` 模型只會有一個 `Phone` 模型。
// 在 User Model 中
public function phone()
{
return $this->hasOne(Phone::class);
}
// 在 Phone Model 中
public function user()
{
return $this->belongsTo(User::class);
}
// 使用:
$phone = User::find(1)->phone;
$user = Phone::find(1)->user;
一對多 (One to Many)
例如,一篇文章 `Post` 可以有多則留言 `Comment`。
// 在 Post Model 中
public function comments()
{
return $this->hasMany(Comment::class);
}
// 在 Comment Model 中
public function post()
{
return $this->belongsTo(Post::class);
}
// 使用:
$comments = Post::find(1)->comments; // 這會回傳一個 Collection
foreach ($comments as $comment) {
// ...
}
多對多 (Many to Many)
例如,一篇文章 `Post` 可以有多個標籤 `Tag`,一個標籤 `Tag` 也可以被用在多篇文章上。這需要一張中間表 (Pivot Table),通常命名為 `post_tag`。
// 在 Post Model 中
public function tags()
{
return $this->belongsToMany(Tag::class);
}
// 在 Tag Model 中
public function posts()
{
return $this->belongsToMany(Post::class);
}
// 使用:
$tags = Post::find(1)->tags;
進階戰術:渴求式載入 (Eager Loading) 解決 N+1 問題
這是所有 Eloquent 新手最容易踩的效能地雷,也是面試時必考的送分題(或送命題)。
什麼是 N+1 查詢問題?
想像一下,你要顯示 10 篇文章以及它們各自的作者。你可能會這樣寫:
$posts = Post::take(10)->get();
foreach ($posts as $post) {
// 這裡每次循環都會發送一次 SQL 查詢來找作者!
echo $post->author->name;
}
這段程式碼會發生什麼事?
- 第 1 次查詢:`SELECT * FROM posts LIMIT 10`
- 接下來的 10 次查詢:`SELECT * FROM authors WHERE id = ?` (在迴圈中執行 10 次)
總共執行了 1 + 10 = 11 次查詢!這就是可怕的 N+1 問題。如果 N 是 1000 呢?你的資料庫大概會直接罷工。
用 `with()` 解決問題
解法非常簡單,就是使用「渴求式載入」(Eager Loading),告訴 Eloquent:「嘿!在我查詢文章的時候,順便把作者資料也一次打包帶走!」
// 正確的作法
$posts = Post::with('author')->take(10)->get();
foreach ($posts as $post) {
// 這裡不會再有額外的查詢
echo $post->author->name;
}
這樣 Eloquent 只會執行 2 次查詢:
- 第 1 次查詢:`SELECT * FROM posts LIMIT 10`
- 第 2 次查詢:`SELECT * FROM authors WHERE id IN (1, 2, 3, …)`
效能天差地遠!請務必把 `with()` 這個方法刻在你的 DNA 裡。
集合 (Collections) 的魔法
當你使用 `get()` 或 `all()`,或是存取 `hasMany` 這種關聯時,Eloquent 回傳的不是一個單純的陣列,而是一個 `Illuminate\Support\Collection` 物件。這個物件超乎想像的強大,內建了數十種好用的方法,可以讓你用鏈式呼叫 (chaining) 優雅地處理資料。
$posts = Post::all();
// 篩選出標題長度大於 10 的文章
$longTitlePosts = $posts->filter(function ($post) {
return strlen($post->title) > 10;
});
// 將每篇文章的標題轉為大寫
$uppercasedTitles = $posts->map(function ($post) {
return strtoupper($post->title);
});
// 只取出所有文章的 id
$postIds = $posts->pluck('id');
善用 Collection 可以大幅減少你在 Controller 裡寫 `foreach` 迴圈的機會,讓程式碼更簡潔、更具表達力。
好了,今天的 Laravel Eloquent ORM 完整指南 就到這裡。我們從最基本的 Model 設定,一路聊到 CRUD、強大的關聯、效能關鍵的 Eager Loading,最後再到優雅的 Collection 操作。Eloquent 的世界博大精深,但掌握了這些核心觀念,你已經超越了 80% 的新手。記住,好的工具能讓你事半功倍,但前提是你得真正理解它。別再只會切水果了,是時候拿起神兵,去屠龍了!
延伸閱讀
- Eloquent 是蜜糖還是毒藥?資深工程師的 Laravel ORM 實戰心法,避開效能地雷區
- 你的 Laravel 專案是技術債炸彈還是傳世藝術品?Laravel 10 專案架構最佳實務指南
- 網站卡住了?別再讓使用者等到天荒地老!Laravel 排程與背景任務 (Scheduler & Queue) 終極指南
如果你對 Laravel 開發、網站架構優化,或是如何將這些強大的技術應用在你的 WordPress 專案上感興趣,我們的團隊擁有豐富的實戰經驗。無論是企業級系統開發、API 串接還是效能調校,我們都能提供最專業的解決方案。
立即聯繫浪花科技,讓我們聊聊如何讓你的專案更上一層樓!
常見問題 (FAQ)
Q1: 什麼是 Laravel Eloquent ORM?為什麼我應該使用它?
Eloquent 是 Laravel 框架內建的物件關聯對映 (Object-Relational Mapper) 工具。它讓你能夠使用 PHP 物件導向的語法來操作資料庫,而不是手寫 SQL 指令。使用它的好處包括:程式碼可讀性更高、開發速度更快、內建許多安全機制 (如防止 SQL Injection),並且可以輕鬆處理複雜的資料庫關聯。
Q2: 什麼是 N+1 查詢問題,以及如何用 Eloquent 解決?
N+1 查詢問題是指在一個迴圈中,因為存取關聯資料而導致執行了大量額外的資料庫查詢。例如,查詢 N 筆文章後,在迴圈裡分別查詢每篇文章的作者,總共會產生 N+1 次查詢。在 Eloquent 中,解決這個問題的最好方法是使用「渴求式載入」(Eager Loading),也就是在主查詢中使用 `with(‘relationship_name’)` 方法,讓 Eloquent 在一次額外的查詢中就將所有需要的關聯資料取回,將查詢次數從 N+1 次大幅減少到 2 次。
Q3: 在 Eloquent Model 中 `$fillable` 和 `$guarded` 有什麼不同?
`$fillable` 和 `$guarded` 都是為了防止「批量賦值」(Mass Assignment) 漏洞而設計的,你只能擇一使用。`$fillable` 是「白名單」,陣列中列出的欄位是唯一允許被批量賦值的。`$guarded` 則是「黑名單」,陣列中列出的欄位是禁止被批量賦值的。一般來說,使用 `$fillable` 明確指定允許的欄位是更安全、更推薦的做法。
Q4: 我什麼時候應該考慮不使用 Eloquent,而是用 Query Builder 或原生 SQL?
雖然 Eloquent 非常強大方便,但在某些情境下可能不是最佳選擇。例如,當你需要產生非常複雜的報表、執行大量的資料彙總 (aggregation),或是進行極度要求效能的批次更新/刪除操作時,使用 Laravel 的 Query Builder 或是原生 SQL (`DB::raw()`) 可能會提供更好的效能和更靈活的控制。經驗法則是:對於日常的 CRUD 和模型相關的業務邏輯,用 Eloquent;對於複雜的查詢和效能敏感的操作,考慮降級使用 Query Builder。






