ThinkPHP中BaseModel必须显式定义并统一用D()实例化,TP3依赖D()自动加载继承链,TP5废弃Common\Model约定改用app\model,TP6强制think\Model父类且废除D()函数,升级时链式调用行为会因查询构造器重构而突变。

ThinkPHP 的基类模型不是“可选配件”,而是必须明确区分层级、控制实例化方式的核心结构。直接继承 Model 写法在 TP3/5/6 中都存在,但行为差异极大,尤其在 D() 和 M() 的调用逻辑上容易踩坑。
BaseModel 必须显式定义且统一用 D() 实例化
TP3.x 项目中常见把通用 CRUD 封装进 BaseModel,再让业务模型继承它。这种写法本身没问题,但关键点在于:所有业务模型(如 UserModel)必须通过 D('User') 调用,不能用 M('User')。
原因:M() 绕过自动加载机制,只认数据库表名,不会查找 /Application/Common/Model/ 下的类文件;而 D() 会按命名规范自动定位到 Common\Model\UserModel 并完成实例化。
-
D('User')→ 找Common\Model\UserModel,继承自BaseModel -
M('user')→ 直接 newModel('user'),不走任何自定义类逻辑 - 若未定义
UserModel类,D('User')会退化为M('user'),但此时BaseModel中的方法全部失效
TP5/6 中 BaseModel 的命名空间和继承链要对齐框架版本
TP5 开始废弃 Common\Model 目录约定,推荐使用 app\model\;TP6 进一步强制使用 think\Model 作为父类,且不再支持 D() 函数。
立即学习“PHP免费学习笔记(深入)”;
所以你在 TP5 写的这个:
namespace app\model;
use think\Model;
class BaseModel extends Model
{
// 通用方法
}
到了 TP6 就必须改成:
namespace app\model;
use think\Model;
class BaseModel extends Model
{
protected $autoWriteTimestamp = true;
protected $dateFormat = 'Y-m-d H:i:s';
}
否则会因父类路径或静态属性不兼容导致 save() 报错或时间字段写入失败。
- TP5 中
BaseModel可以放在任意命名空间,只要控制器里use app\model\BaseModel即可 - TP6 中若用
Db::name('user')->select(),完全绕过模型层,BaseModel定义的方法一概不生效 - TP6 推荐用
new User()或依赖注入,而不是model('User')(已废弃)
getDataById() 这类自定义方法容易忽略别名冲突和 SQL 注入风险
知识库中给出的 getDataById($id) 示例里写了硬拼 SQL:'t.id = '.$id —— 这是严重漏洞,TP3 也应改用参数绑定。
更关键的是,该方法用了 join('__USER__ u on u.id = t.uid'),其中 __USER__ 是 TP 的表前缀占位符,但实际执行时若没配置 DB_PREFIX 或前缀为空,会导致表名解析失败,报错 Table 'dbname.__USER__' doesn't exist。
- 正确写法应统一用
$this->table('user')或$this->name('user')显式指定表,避免依赖__占位符 -
join()中的关联字段必须确保两边字段类型一致,比如t.uid是 int,u.id也得是 int,否则 MySQL 5.7+ 严格模式下会报错 - 如果业务中大量用
find()返回一维数组,要注意它默认只取第一条,即使where条件匹配多条也不会报错,容易掩盖数据异常
真正难处理的不是怎么写基类,而是当项目从 TP3 升级到 TP6 时,BaseModel 里那些基于 $this->where()->save() 的链式调用,会因为查询构造器底层重构而行为突变——比如 where() 的数组键名解析规则、空值过滤策略、时间字段自动格式化开关等,全得逐个验证。别指望“继承关系还在”就等于“逻辑没变”。



















