理解 ThinkPHP ORM 的 hasOne 和 belongsTo 关联 —— 以医疗品种数据模型为例
在 ThinkPHP 这类典型 ORM 框架中,模型关联是日常开发绕不开的重要知识点。常见的 hasOne
和 belongsTo
这对方法,经常让新老开发者分不清场景。本文结合最近 FastAdmin + ThinkPHP 项目中的“品种主表 Pz 与底价品种副表 Ypdjpzf 关联”实际业务场景,深度(但通俗!)地讲解两者区别和应用。
基本概念
- hasOne 关联:主表 1 对1 副表。常翻译为“拥有一个”。
- belongsTo 关联:当前表属于另一个表。常翻译为“属于”。
直观理解
- hasOne:主表每条数据拥有一条副表数据。
“Pz 拥有一个 Ypdjpzf”。 - belongsTo:当前表这条数据属于另一个表。
“Ypdjpzf 属于一个 Pz”。
结合实际项目演示
假设有如下两个表(已极度简化):
pz
(主表)- id 主键
- name 品种名
ypdjpzf
(副表)- id 主键
- dj_id —— 外键,指向 pz.id
- djdg 成本单价
- djds 返款单价
业务需求
前端希望通过主表 Pz 直接拿到副表各项底价字段。例如渲染:
<input value="{$row.djpzf.djdg}">
关联的方向
hasOne 的推荐场景
主表:Pz,副表:Ypdjpzf
“我有一个副表 Ypdjpzf,它里面的 dj_id 指向我(Pz)的 id。”
所以模型里这样写:
// app\admin\model\pz\Pz.php
public function djpzf()
{
// 副表外键,主表主键
return $this->hasOne('app\admin\model\pz\Ypdjpzf', 'dj_id', 'id');
}
- 逻辑口语化是“我的一条数据(Pz主表),有一个副表 Ypdjpzf 的数据,是通过 ypdjpzf.dj_id 指向我的 id 实现的”。
belongsTo 的场景
副表:Ypdjpzf,主表:Pz
“我是副表,我的 dj_id 用来指向主表 id,所以我属于某条主表数据。”
此时 Ypdjpzf 模型这么写:
// app\admin\model\pz\Ypdjpzf.php
public function pz()
{
return $this->belongsTo('app\admin\model\pz\Pz', 'dj_id', 'id');
}
- 逻辑口语化是“我副表每条数据都属于一个主表 Pz,就是用我的 dj_id 跟主表 id 关联”。
实战代码片段
场景一:从主表 Pz 出发,最常见的 hasOne 使用
// Pz.php
public function djpzf()
{
return $this->hasOne('app\admin\model\pz\Ypdjpzf', 'dj_id', 'id');
}
// 控制器
$row = Pz::with(['djpzf'])->find($id);
前端模板可直接用:
<input value="{$row.djpzf.djdg|default=''}">
常用于主表的详情、编辑页,要显示底价字段等副表数据。
场景二:用 belongsTo,从副表出发反向查
假设你查副表数据,希望直拿主表信息:
// Ypdjpzf.php
public function pz()
{
return $this->belongsTo('app\admin\model\pz\Pz', 'dj_id', 'id');
}
// 控制器
$row = Ypdjpzf::with(['pz'])->find($id);
// 模板
{$row.pz.name}
需要注意什么?
- 看外键在哪一边!
- hasOne:主表→副表,副表有外键。
- belongsTo:副表→主表,自己有外键指向人家。
- 查数据的“起点”是谁,就用哪个。
- 从
Pz
查到底价副表,优先用hasOne
。 - 从副表出发回查主表,用
belongsTo
。
- 从
常见易错点
- 模型用错(把 belongsTo 当 hasOne 用)
结果 with/查询报 method not exist,或者取不到数据。 - 控制器用 Db::name 查询而非模型类
会导致 with 关联根本不会生效,出现 method not exist 异常。 - 外键顺序写反
hasOne('模型', '副表外键', '主表主键')
、belongsTo('模型', '本表外键', '对方主键')
绝不能对应错误。 - 没有正确 assign/render
前端变量无法{$row.djpzf.djdg}就会始终是空。
总结口诀
- “我有外键,我 belongsTo”
- “他有外键指向我,我 hasOne”
- 主表想查副表,优先 hasOne。
- 副表查主表,则 belongsTo。
项目经验小结
在本项目实际开发中,从 Pz 主表查“底价品种副表”最常见写法是:
// Pz.php
public function djpzf()
{
return $this->hasOne('app\admin\model\pz\Ypdjpzf', 'dj_id', 'id');
}
用法:
$row = Pz::with(['djpzf'])->find($id);
// $row->djpzf->djdg // ok!
而如果你的数据流是从副表出发,记得在 Ypdjpzf 模型里定义 belongsTo 兼容。
结语
ThinkPHP/ORM 的 hasOne
/belongsTo
不难,只要记住“外键在哪,谁主动找谁”。
在多表复杂业务场合,始终一条原则:
- 找谁的就 with 谁的模型,外键在谁就用 belongsTo,主表查副表就用 hasOne。
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: