[fastadmin] 第四十四篇 not exist: think\db\Query->getOriginal报错排查

[fastadmin] 第四十四篇 not exist: think\db\Query->getOriginal报错排查

大家好,今天我们来聊一个FastAdmin开发中经常让人摸不着头脑的错误:method not exist: think\db\Query->getOriginal。这个错误看似简单,实则暗藏玄机,经常让不少开发者头疼不已。作为有着10年PHP教学经验的老司机,我来和大家分享一下这个问题的前因后果和解决之道。

一、问题场景再现

最近在开发一个商品管理系统时,遇到了这样一个情况:在编辑产品信息时,前端提交表单数据,后台报错:

method not exist: think\db\Query->getOriginal

这个错误乍看之下,是在告诉我们系统试图在Query对象上调用getOriginal方法,但这个方法不存在。但问题是,getOriginal明明是ThinkPHP模型的方法,为什么会找不到呢?

二、错误的本质是什么?

在ThinkPHP中,getOriginal()方法是Model类的一个实例方法,用于获取模型的原始数据(未修改前的数据)。而Query类是负责构建SQL查询的,它并没有getOriginal方法。

这就好比你拿着电视遥控器想给空调调温度——工具用错了!

三、深入排查过程

首先,我们来看看相关代码:

// 控制器中调用编辑方法
public function edit_product($ids = null)
{
    $row = $this->model->find($ids);
    // 其他代码...

    if ($this->request->isPost()) {
        // 处理POST请求,调用基础控制器的编辑方法
        return $this->baseController->editOwn($row, $ids, $source_data, $secondary, 'normal');
    }

    // 显示表单的代码...
}

// 基础控制器中的编辑方法
public function editOwn($row, $ids = null, $source_data, $old_secondary, $type = 'normal')
{
    // 编辑逻辑...
    $result = $row->allowField(true)->save($params);
    // 其他代码...
}

通过打印变量,我发现$row的值竟然是字符串"app\admin\model\Product",而不是预期的模型实例对象!这可是大问题。

再深入看Product模型的代码:

protected static function init()
{
    self::beforeUpdate(function ($model) {
        $operationBatch = uniqid('batch_'); 
        $oldData = $model->getOriginal();
        // 后续日志记录代码...
    });
}

啊哈!问题找到了。模型定义了beforeUpdate事件,尝试在更新前获取原始数据,但传入的$row不是Model实例,导致调用getOriginal方法失败。

四、深层原因分析

这个问题的根源在于FastAdmin的架构设计中,模型、控制器之间的交互方式出现了混淆:

  1. 对象类型混淆:变量$row预期是Model实例,实际却是类名字符串
  2. 跨控制器调用:从ProductManager控制器调用Product控制器的方法时,参数传递出现问题
  3. 模型事件触发时机:模型的beforeUpdate事件在对象类型不正确时依然尝试执行
  4. 数据库操作方式不一致:有时使用Query构建器,有时使用Model对象

这就像厨房里一个人用电饭煲煮饭,另一个人却拿着锅铲要去搅拌——工具和预期不匹配。

五、解决方案

既然已经找到问题,解决起来就有思路了。我尝试了几种方案:

方案1:确保$row是模型实例

public function editOwn($row, $ids = null, $source_data, $old_secondary, $type = 'normal')
{
    // 确保$row是模型实例
    if (!($row instanceof \think\Model)) {
        $row = new \app\admin\model\Product();
        $rowData = Db::name('product')->where('id', $ids)->find();
        $row->data($rowData);
        $row->isUpdate(true);
    }

    // 后续代码...
}

方案2:绕过模型事件系统

最终,我选择了更直接的方案——完全绕过模型事件系统,直接使用Db类操作数据库:

public function edit_product($ids = null)
{
    if ($this->request->isPost()) {
        $params = $this->request->post('row/a');
        $secondaryParams = $this->request->post('secondary/a');

        Db::startTrans();
        try {
            // 直接更新主表
            Db::name('product')->where('id', $ids)->update($params);

            // 更新副表
            $pid = Db::name('product')->where('id', $ids)->value('pid');
            $exists = Db::name('product_data')->where('product_id', $pid)->find();

            if ($exists) {
                Db::name('product_data')->where('product_id', $pid)->update($secondaryParams);
            } else {
                $secondaryParams['product_id'] = $pid;
                $secondaryParams['type'] = 'normal';
                Db::name('product_data')->insert($secondaryParams);
            }

            // 记录日志
            $this->logModel->save([
                'product_id' => $ids,
                'type' => 'normal',
                'content' => json_encode(Db::name('product')->where('id', $ids)->find()),
                'operate_type' => '编辑',
                'admin_id' => session('admin.id'),
                'created_by' => session('admin.id'),
                'created_time' => time()
            ]);

            Db::commit();
            return json(['code' => 1, 'msg' => '保存成功']);
        } catch (\Exception $e) {
            Db::rollback();
            return json(['code' => 0, 'msg' => '保存失败: ' . $e->getMessage()]);
        }
    }

    // GET请求显示表单的代码...
}

这种方法简单直接,完全绕开了模型事件可能带来的复杂性。

六、经验总结与最佳实践

通过这次排错,我总结了几点在FastAdmin开发中的经验:

  1. 保持类型一致性:始终检查对象类型,特别是在跨控制器调用时

    if (!($row instanceof \think\Model)) {
        // 处理错误或重新获取正确实例
    }
  2. 谨慎使用模型事件:模型事件功能强大但需谨慎使用,尤其在复杂交互中

    // 在事件处理器中添加防御性代码
    self::beforeUpdate(function ($model) {
        if (!method_exists($model, 'getOriginal')) {
            return true; // 跳过处理
        }
        // 其他代码...
    });
  3. 异常处理详细化:捕获具体异常并记录详细日志

    try {
        // 代码...
    } catch (\Exception $e) {
        \think\Log::error('具体操作失败: ' . $e->getMessage() . "\n" . $e->getTraceAsString());
    }
  4. 考虑使用原生操作:对于复杂场景,有时直接使用Db类比模型更可控

  5. 保持变量名语义清晰:避免一个变量在不同上下文中表示不同含义

七、写在最后

这个看似简单的错误,背后实际上反映了框架设计与实际应用之间的微妙平衡。作为开发者,我们既要理解框架的运行机制,也要灵活运用不同的解决方案。

记住,在使用任何框架时,了解其核心机制至关重要。ThinkPHP的ORM模型系统功能强大,但强大的背后也藏着复杂性。当你不确定时,简单直接的解决方案往往更可靠。

好了,今天的分享就到这里。希望这篇文章能帮助大家在遇到类似问题时,有更清晰的思路去解决。如果你有更好的解决方案或者其他问题,欢迎留言交流!

下期再见!


本文作者拥有13年PHP教学经验,专注于Web开发实战教学。欢迎关注更多FastAdmin系列教程。

本作品采用《CC 协议》,转载必须注明作者和本文链接
• 15年技术深耕:理论扎实 + 实战丰富,教学经验让复杂技术变简单 • 8年企业历练:不仅懂技术,更懂业务落地与项目实操 • 全栈服务力:技术培训 | 软件定制开发 | AI智能化升级 关注「上海PHP自学中心」获取实战干货
wangchunbo
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

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