現象
// 控制器中
$model = D('User'); // 方式一
$model = new Model('User'); // 方式二
// 兩者都能 CURD,但自動驗證(_validate)只有 D() 生效
源碼追蹤
D() 方法(ThinkPHP/Library/Think/ThinkPHP.php)
function D($name = '', $baseModel = '') {
if (empty($name)) return new Think\Model();
static $_model = [];
$key = $name . $baseModel;
if (isset($_model[$key])) return $_model[$key]; // 單例緩存
$class = parse_model_name($name); // User → UserModel
$model = new $class($name, $baseModel); // 實例化,傳遞名稱
$_model[$key] = $model;
return $model;
}
Model 構造函數(ThinkPHP/Library/Think/Model.class.php)
public function __construct($name = '', $tablePrefix = '', $connection = '') {
if (!empty($name)) {
$this->name = $name; // 設置模型名
$this->_auto = true; // 關鍵:開啟自動驗證/完成
$this->_setupTableName(); // 解析表名
}
// ...
}
new Model('User') 直接調用
$model = new Model('User');
// 進入構造函數,但 $name='User',$baseModel='',$connection=''
// 由於沒有經過 D() 的單例邏輯,且某些版本構造函數未設置 _auto=true
// 導致 create() 時不觸發 _validate/_auto 規則
核心差異表
| 特性 | D('User') | new Model('User') |
| 單例模式 | 是(同一請求復用實例) | 否(每次 new 新實例) |
| 自動驗證 _validate | 啟用(_auto=true) | 視版本而定,通常不啟用 |
| 自動完成 _auto | 啟用 | 不啟用 |
| 跨模塊調用 | 支持 `D('Admin.User')` | 需手動處理命名空間 |
| 表前綴解析 | 自動 | 需手動指定或依賴配置 |
最佳實踐
- 控制器/服務層統一用 D():獲得單例、自動驗證、跨模塊便利。
- 只需純數據操作、無驗證邏輯:可用 `M('User')`(ThinkPHP 3.2+ 別名,等同 `new Model()` 但更簡潔)。
- 自定義基類模型:`class BaseModel extends Model { protected $_auto = true; }` 然後 `D('User', 'BaseModel')` 強制啟用驗證。
- 避免在循環/大量實例化中用 D():單例緩存可能導致狀態污染,必要時 `D('User', '', true)` 強制新建(視版本參數)。
ThinkPHP 5/6/8 更新
- TP5+ 棄用 `D()`/`M()`,改用依賴注入:`$this->app->make(UserModel::class)` 或控制器構造函數注入 `UserModel $user`。
- 模型類直接定義 `$autoValidate = true`、`$validate = UserValidate::class`,無需 `_validate` 陣列。
- 建議遷移至新版寫法,利用容器管理生命周期。