返回博客

ThinkPHP 5 Model 根本没有 newQuery()——Laravel 老手必踩的 ORM 迁移坑

接手一个用 ThinkPHP 5 + FastAdmin 搭建的后台管理系统,在商品模块新增了一个 Service 层方法,沿用 Laravel 的习惯写下 (new Product())->newQuery()->where(...),随手按下保存,接口立刻 500。

日志里是一行不留情面的 Fatal error,整个请求栈终止在刚写完的那行代码。

现象

Fatal error: Call to undefined method app\common\model\Product::newQuery()

或者 FastAdmin 包装后更精简:

Method not exist: think\model\...\newQuery

两种触发写法:

// 写法 1 —— 实例调用(Laravel 习惯)
$rows = (new Product())->newQuery()
    ->where('status', 1)
    ->select();
 
// 写法 2 —— 静态调用
$rows = Product::newQuery()->where('status', 1)->select();

两种都是 500,因为方法根本不存在。

根因

ThinkPHP 5 和 Laravel Eloquent 在 ORM 设计上有一个根本性差异:

Laravel 的架构:Model 是数据容器,Builder 是独立对象。newQuery() 的职责是为当前 Model 创建一个新的 Eloquent\Builder 实例,持有 Model 引用,作为查询入口。

ThinkPHP 5 的架构:Model 类本身通过魔术方法 __call / __callStatic 直接代理到底层 think\db\Query。调用 Product::where(...) 等价于 Laravel 的 Product::query()->where(...),根本不需要显式"创建 Builder"这一步。

翻 TP5 源码 thinkphp/library/think/Model.php__callStatic 的实现大致是:

public static function __callStatic($method, $args)
{
    return call_user_func_array([new static()->db(), $method], $args);
}

所以 Product::where(...) 最终等价于 (new Product())->db()->where(...),中间那一层 Builder 工厂在 TP5 里是隐式的。

坑:newQuery() 是 Laravel 专有概念,TP5 不存在

newQuery() 是 Eloquent 的 API,专门用来获取 Builder 实例。在 ThinkPHP 5 中调用它只会得到 Method not exist 的 Fatal error。TP5 Model 没有 Builder 工厂方法——它自己就是查询代理,无需也无法 newQuery()

正解

方式 1:静态调用(日常首选)

$rows = Product::where('status', 1)
    ->where('category_id', $catId)
    ->order('created_at', 'desc')
    ->select();

直接在 Model 类名上调用查询方法,TP5 魔术代理帮你转发。

方式 2:实例调用

$model = new Product();
$rows  = $model->where('status', 1)->select();

与静态调用等价,偶尔在需要先设置属性再查询时使用。

方式 3:显式拿 Query 对象

$q    = (new Product())->db();   // 返回 think\db\Query
$rows = $q->where('status', 1)->select();

db() 才是 TP5 中 newQuery() 的真正等价物,但几乎不需要显式调用——魔术方法已经帮你处理了。

方式 4:绕过 Model 用 Db facade

当不需要模型特性(软删自动过滤、关联、accessor)时,直接用 Db facade 更轻:

use think\Db;
 
$rows = Db::name('product')->where('status', 1)->select();
正解:删掉 newQuery(),直接在 Model 上链式调用

(new Product())->newQuery()->where(...) 改成 Product::where(...) 即可。TP5 的魔术代理让 Model 类名本身就是查询入口,不需要也不存在 newQuery() 这一层。

快速排查存量代码

接手含 Laravel 背景成员写过的 TP5 项目时,跑一下:

grep -rn "->newQuery\|::newQuery" application/ --include="*.php"

所有命中都是 Laravel 习惯残留,直接删掉 ->newQuery() / ::newQuery() 改静态调用即可。

一句话外卖

ThinkPHP 5 Model 本身就是 Query 代理,Model::where() 等价于 Laravel 的 Model::query()->where()——从 Eloquent 迁过来时,删掉 newQuery(),不需要"先拿 Builder 再查询"这个思维定式。