場景:三表關聯查詢
-- 用戶表
test_user: id, username, tel
-- 用戶頭像表(一對一)
test_avatar: uid, avatar
-- 產品表(一對多:一用戶多產品)
test_pro: id, uid, name
方案一:RelationModel(已棄用,不推薦新專案使用)
class ProModel extends RelationModel {
protected $_link = [
'Avatar' => [
'mapping_type' => self::HAS_ONE,
'class_name' => 'Avatar',
'foreign_key' => 'uid',
'mapping_fields' => 'avatar',
'as_fields' => 'avatar',
],
'User' => [
'mapping_type' => self::BELONGS_TO,
'class_name' => 'User',
'foreign_key' => 'uid',
'mapping_fields' => 'username,tel',
'as_fields' => 'username:user_name,tel:user_tel',
],
];
}
// 使用
$pro = M('Pro')->relation(true)->find(1);
// 結果包含:id, uid, name, avatar, user_name, user_tel
常見坑點
HAS_ONE vs BELONGS_TO:外鍵在哪張表?在關聯表用 HAS_ONE,在主表用 BELONGS_TO
mapping_fields 必須指定,否則預設全欄位
as_fields 別名避免欄位衝突
- ThinkPHP 3.2.3+ 建議改用 ViewModel
方案二:ViewModel(推薦,SQL 級關聯,效能更好)
class ProViewModel extends ViewModel {
public $viewFields = [
'test_pro' => ['id', 'uid', 'name', '_type' => 'LEFT'],
'test_avatar' => ['avatar', '_on' => 'test_pro.uid=test_avatar.uid', '_type' => 'LEFT'],
'test_user' => ['username', 'tel', '_on' => 'test_pro.uid=test_user.id', '_type' => 'LEFT'],
];
}
// 使用
$pro = M('ProViewModel')->find(1);
// 自動生成 SQL:
// SELECT tp.id, tp.uid, tp.name, ta.avatar, tu.username, tu.tel
// FROM test_pro tp
// LEFT JOIN test_avatar ta ON tp.uid = ta.uid
// LEFT JOIN test_user tu ON tp.uid = tu.id
// WHERE tp.id = 1
ViewModel 關鍵配置
| 屬性 | 說明 |
_on | JOIN 條件,支援多欄位 ['a.id=b.id', 'a.type=b.type'] |
_type | JOIN 類型:LEFT、RIGHT、INNER、FULL |
_as | 表別名 |
| 欄位定義 | ['id', 'name', 'title:new_title'] 支援別名 |
方案三:原生 SQL / Query Builder(最靈活、效能最佳)
$result = Db::table('test_pro')
->alias('p')
->join('test_avatar a', 'p.uid = a.uid', 'LEFT')
->join('test_user u', 'p.uid = u.id', 'LEFT')
->field('p.id, p.uid, p.name, a.avatar, u.username, u.tel')
->find(1);
ThinkPHP 3.2 常見 SQL 問題與解決
問題:字段存儲逗號分隔 ID(如 {1,2,3,4,5}),如何查詢包含特定 ID 的記錄?
// 方案一:模糊查詢(效能差,不走索引)
$where['field'] = ['LIKE', "%{$id}%"];
// 方案二:FIND_IN_SET(MySQL 專用,可走索引)
$where['field'] = ['EXP', "FIND_IN_SET({$id}, field)"];
// 方案三:正則(最精確,效能中等)
$where['field'] = ['REGEXP', "[[:<:]]{$id}[[:>:]]"]; // 邊界匹配
遷移建議:ThinkPHP 3.2 → 5.1/6.0/8.0
| 3.2 寫法 | 5.1+ 寫法 |
M('Model') | model('Model') 或 Db::name('table') |
RelationModel | model()->with('relation') |
ViewModel | Db::view() 或模型關聯 hasOne()/belongsTo() |
$_link | 模型類別定義 public function avatar() { return $this->hasOne(Avatar::class, 'uid', 'uid'); } |
最佳實務總結
- 新專案直接使用 ThinkPHP 6.0/8.0,原生支援 Eloquent 風格關聯
- 舊專案維護:ViewModel 優於 RelationModel,SQL 可見、可調優
- 複雜查詢直接寫 SQL 或 Query Builder,不要強行套用 ORM
- 關鍵查詢加上索引、EXPLAIN 分析、慢查詢日誌
- 關聯定義集中在模型類別,控制器保持精簡
掌握 ViewModel 與原生查詢構建器,能解決 95% 的關聯查詢需求,避開 RelationModel 的諸多坑點,為系統遷移鋪平道路。
- 上一篇文章: PHP fsockopen/pfsockopen 被禁用導致 SMTP 發信失效:三大替代方案與主流系統修改指南
- 下一篇文章: 使用 PHPCMS V9 搭建 WAP 手機站:裝置偵測、模板分離與動態靜態策略
Copyright ©
程式碼之家 All Rights Reserved