ThinkPHP模型中直接调用where()报错,根本原因是模型类未继承think\Model或误用了think\db\Query;正确做法是确保类继承Model,并注意Query与Model职责区分。

ThinkPHP模型里直接调用where()会报错?先确认是不是没继承Model
很多新手在模型类里写$this->where('status', 1)->select()却提示Call to undefined method,根本原因是:模型类没继承think\Model,或者用了use think\db\Query误当模型用。
正确做法是确保模型类声明继承关系:
namespace app\model;
use think\Model;
class User extends Model
{
// 这样才能用链式查询
}
- 如果继承的是
think\db\Query,那它本身不是模型,不能挂载到控制器自动实例化,也无save()、delete()等模型方法 - TP6 默认模型路径是
app\model\,且需开启model_auto_init(默认开启),否则db('user')返回的是查询器,不是模型实例 - 控制器中用
User::where(...)或(new User())->where(...)都行,但前者走静态代理,后者走实例化——TP6.1+推荐静态调用
链式操作中途想终止或复用?别直接select(),用buildSql()或缓存Query对象
常见需求:构造一个基础查询(比如排除软删除、限定租户),后续在不同方法里加where再执行。但select()一调就执行SQL,没法再链了。
解决办法是:不执行,只保留查询器对象:
立即学习“PHP免费学习笔记(深入)”;
$base = User::where('tenant_id', 1)->where('deleted_at', null);
$adminList = $base->where('role', 'admin')->select();
$userList = $base->where('status', 1)->select();
- TP6.0+ 中,
where()等方法返回的始终是Query对象(非null),可安全链式复用 - 若需生成原生SQL调试,用
$base->buildSql(),返回带?占位符的字符串,注意它不会自动转义参数 - 避免在循环里反复写
User::where(...)——每次都会新建模型实例和查询器,开销略大;复用对象更轻量
order()传数组 vs 字符串,排序失效时先查字段是否存在
写order(['sort' => 'asc', 'id' => 'desc'])结果没生效,或者报SQLSTATE[42S22]: Column not found,大概率是字段名拼错,或该字段不在当前查询的表里(尤其连表时)。
-
order('sort asc, id desc')是字符串写法,TP会原样塞进SQL,不校验字段存在性;order(['sort' => 'asc'])是数组写法,TP会尝试加反引号,但同样不查schema - 连表查询时,
order('user.sort')比order('sort')更稳妥,避免歧义 - 如果排序字段来自
with()关联,不能直接order()——关联数据是后期合并的,得用with(['profile' => function ($q) { $q->order('level'); }])
用find()还是select()?看返回值结构和后续是否要调模型方法
很多人以为find()只是“查一条”,其实它决定返回值类型和可用方法:
-
find()返回单个Model实例(即使查不到也是null),能直接调$user->together('profile')、$user->save() -
select()返回Collection对象(TP6),里面每个元素仍是Model实例,但集合本身没有save();若需批量改,得foreach逐个调实例方法 - 如果只是取原始数组(比如给前端JSON),用
select()->toArray()或find()?->toArray(),避免序列化整个模型对象带来的额外属性和方法开销 - 注意:
findOrEmpty()返回空模型而非null,适合需要统一调toArray()的场景,但别误以为它会触发append字段自动加载
链式操作看着顺滑,但真正卡住的往往不是语法,而是模型实例生命周期、字段作用域、以及Query和Model两类对象的职责边界——写完记得用getLastSql()看实际发出的SQL,比猜靠谱得多。



















