理解 ThinkPHP ORM 的 hasOne 和 belongsTo 关联 —— 以医疗品种数据模型为例

在 ThinkPHP 这类典型 ORM 框架中,模型关联是日常开发绕不开的重要知识点。常见的 hasOnebelongsTo 这对方法,经常让新老开发者分不清场景。本文结合最近 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

常见易错点

  1. 模型用错(把 belongsTo 当 hasOne 用)
    结果 with/查询报 method not exist,或者取不到数据。
  2. 控制器用 Db::name 查询而非模型类
    会导致 with 关联根本不会生效,出现 method not exist 异常。
  3. 外键顺序写反
    hasOne('模型', '副表外键', '主表主键')belongsTo('模型', '本表外键', '对方主键')
    绝不能对应错误。
  4. 没有正确 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 协议》,转载必须注明作者和本文链接
嗨,我是波波。曾经创业,有收获也有损失。我积累了丰富教学与编程经验,期待和你互动和进步! 公众号:上海PHP自学中心
wangchunbo
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
司机 @ 某医疗行业
文章
309
粉丝
352
喜欢
565
收藏
1131
排名:61
访问:12.6 万
私信
所有博文
社区赞助商