save()返回false的根本原因有三:一是验证失败,需查$model->geterrors();二是数据库写入中断(如唯一索引冲突、not null字段为空),此时geterrors()为空;三是模型未正确实例化或场景不匹配导致规则未生效。

查 save() 返回 false 的根本原因
Yii 模型调用 save() 后返回 false,不是“保存失败”这么笼统,而是明确告诉你:验证没过,或数据库写入被中断。框架不会默默吞掉错误,但也不会自动抛异常——你得主动问它为什么。
最直接的办法是立刻检查 $model->getErrors():
- 如果有内容,说明是验证失败,错误在
rules()或自定义验证逻辑里; - 如果返回空数组,但
save()仍是false,大概率是数据库层拒绝了(如唯一索引冲突、字段超长、外键约束失败、NULL 写入 NOT NULL 字段); - 若连
getErrors()都报错(比如调用在 null 对象上),说明模型根本没成功实例化,先回头检查new XxxModel()或XxxModel::findOne()是否返回了有效对象。
验证失败时 getErrors() 为什么为空
常见但容易忽略的情况:场景(scenario)不匹配。比如你在 rules() 里写了 ['email', 'email', 'on' => 'register'],但没设 $model->scenario = 'register',那这条规则压根不执行,save() 会跳过校验直接走 DB 写入——而 DB 层报错后,getErrors() 仍为空。
实操建议:
- 打印
$model->scenario确认当前值,别依赖默认值; - 用
$model->scenarios()查看该场景实际启用哪些属性,确认你要校验的字段是否在列表里; - 临时把规则里的
'on'改成'on' => ['default', 'register'],排除场景过滤干扰; - 若用了动态添加规则(
addRule()),注意它只对当前实例生效,且不会自动注入到scenarios()映射中。
数据库写入失败但没报错信息
Yii 默认把 PDO 异常转为 yii\db\Exception,但如果你捕获了异常却没打印 $e->getMessage() 或 $e->errorInfo,就只能看到 false。尤其要注意 MySQL 的 SQLSTATE 和原生错误码:
-
23000类错误(如1062 Duplicate entry)说明唯一约束触发; -
HY000+1364 Field doesn't have a default value是字段没给值且不允许 NULL; -
01000警告(如截断字符串)有时会被静默忽略,需检查PDO::ATTR_ERRMODE是否设为PDO::ERRMODE_EXCEPTION; - 在
web/index.php开头加ini_set('display_errors', '1');并确保YII_DEBUG为true,否则异常堆栈可能被 ErrorHandler 吞掉。
关联模型或事务中 save 失败难定位
当模型带关联(如 hasOne)、或包裹在事务里调用 save(),失败点可能不在主模型本身:
- 调用
$model->link()或$model->unlink()后没检查返回值,它们也返回bool; - 事务中某个
save()失败但没rollback(),后续操作继续跑,最终报错位置和根源脱节; -
afterSave()里抛异常会导致事务回滚,但错误堆栈从钩子里开始,容易误判为主模型问题; - 用
ActiveRecord::updateAll()或原生 SQL 更新时,绕过了模型验证和事件,但失败同样返回false,需单独查Yii::$app->db->getSchema()->getLastInsertID()或getRawSql()辅助排查。
真正卡住的地方往往不是 save() 这一行,而是它前面那个没做判空的 findOne()、那个被 scenario 过滤掉的规则、或者事务里第三个模型悄悄失败了。别只盯着返回值,要顺着数据流往回推:对象有没有、场景对不对、验证跑没跑、SQL 执行了没、异常打出来没。











