資料表之間的關聯是資料庫設計的核心。Laravel Eloquent 提供直觀、強大的關聯定義語法,支援六大類型:一對一、一對多、多對多、遠程一對多、多態關聯、多態多對多。本文將系統整理各類型的定義、反向關聯、中間表操作、查詢技巧與效能優化(預加載)。
hasOne / belongsTohasMany / belongsTobelongsToMany(需中間表)hasManyThrough(跨中間層)morphTo / morphMany(單表對應多模型)morphToMany / morphedByMany// User.php
public function phone() {
return $this->hasOne(Phone::class, 'foreign_key', 'local_key');
}
// Phone.php
public function user() {
return $this->belongsTo(User::class, 'foreign_key', 'other_key');
}
慣例外鍵為 user_id,主鍵為 id,可透過第二、三參數自訂。
$phone = User::find(1)->phone; // 動態屬性
$phone = User::find(1)->phone()->where('active', 1)->first(); // 查詢建構器
// Post.php
public function comments() {
return $this->hasMany(Comment::class, 'foreign_key', 'local_key');
}
// Comment.php
public function post() {
return $this->belongsTo(Post::class, 'foreign_key', 'other_key');
}
$comments = Post::find(1)->comments()->where('title', 'like', '%foo%')->get();
users, roles, role_user (user_id, role_id)
// User.php
public function roles() {
return $this->belongsToMany(Role::class, 'role_user', 'user_id', 'role_id');
}
// Role.php
public function users() {
return $this->belongsToMany(User::class, 'role_user', 'role_id', 'user_id');
}
$user = User::find(1);
foreach ($user->roles as $role) {
echo $role->pivot->created_at; // 需在關聯加上 ->withPivot('created_at')
}
// 自動維護時間戳
return $this->belongsToMany(Role::class)->withTimestamps();
return $this->belongsToMany(Role::class)->wherePivot('approved', 1);
範例:Country → User → Post。Country 沒有直接外鍵到 Post,透過 User 中介。
// Country.php
public function posts() {
return $this->hasManyThrough(Post::class, User::class,
'country_id', // User 表外鍵
'user_id', // Post 表外鍵
'id', // Country 主鍵
'id' // User 主鍵
);
}
posts, comments, likes (likeable_id, likeable_type)
class Like extends Model {
public function likeable() { return $this->morphTo(); }
}
class Post extends Model {
public function likes() { return $this->morphMany(Like::class, 'likeable'); }
}
class Comment extends Model {
public function likes() { return $this->morphMany(Like::class, 'likeable'); }
}
Relation::morphMap([
'posts' => Post::class,
'comments' => Comment::class,
]);
範例:Post、Video 共用 Tag。
// Post.php
public function tags() { return $this->morphToMany(Tag::class, 'taggable'); }
// Tag.php
public function posts() { return $this->morphedByMany(Post::class, 'taggable'); }
public function videos() { return $this->morphedByMany(Video::class, 'taggable'); }
Post::has('comments')->get(); // 至少一則評論
Post::has('comments', '>=', 3)->get(); // 三則以上
Post::has('comments.votes')->get(); // 巢狀關聯
Post::whereHas('comments', fn($q) => $q->where('content', 'like', 'foo%'))->get();
$posts = Post::withCount(['votes', 'comments' => fn($q) => $q->where('content', 'like', 'foo%')])->get();
echo $posts[0]->comments_count;
// 未預加載:26 次查詢 (1 本書 + 25 作者)
$books = Book::all();
foreach ($books as $book) { echo $book->author->name; }
// 預加載:2 次查詢
$books = Book::with('author')->get();
// 多關聯預加載
$books = Book::with(['author', 'publisher'])->get();
// 巢狀預加載
$books = Book::with('author.contacts')->get();
// 條件預加載
$users = User::with(['posts' => fn($q) => $q->where('title', 'like', '%first%')])->get();
$books = Book::all();
if ($someCondition) {
$books->load('author', 'publisher');
$books->load(['author' => fn($q) => $q->orderBy('published_date')]);
}
$post->comments()->save(new Comment(['message' => '...'])); $post->comments()->saveMany([...]); $post->comments()->create(['message' => '...']); // 需 $fillable
$user->roles()->attach($roleId, ['expires' => $expires]); $user->roles()->detach($roleId); $user->roles()->sync([1 => ['expires' => true], 2, 3]);
$user->roles()->updateExistingPivot($roleId, $attributes);
class Comment extends Model {
protected $touches = ['post'];
public function post() { return $this->belongsTo(Post::class); }
}
熟練掌握 Eloquent 關聯,能以極少代碼表達複雜資料關係,並透過預加載、查詢建構器、統計方法大幅提升效能與可維護性。實務上建議:先設計資料表結構,再對應定義模型關聯,善用遷移檔管理 Schema。