在 yii2 中,一个接口,不希望暴露 SQL 错误在接口响应中,但是又希望暴露字段本身的验证错误在接口响应中,应该如何实现了?
1、现有的实现如下,存在字段本身的验证失败后,接口响应中永远是:数据库操作失败,请联系管理员,用户体验不友好。如图1
if ($animation->save()) {
return [
'code' => 10000,
'message' => '设置动画属性成功',
'data' => $animation,
];
}
return [
'code' => 10004,
'message' => '数据库操作失败,请联系管理员',
];
2、希望如下实现,1、不暴露数据库异常(如 SQL 错误、PDO 异常等) —— 避免安全风险。2、保留模型字段的验证错误(如 required、string 长度等) —— 给前端明确反馈。
3、实现 可复用的 BaseController::safeSave() 方法,可以帮助你在 Yii2 中统一处理模型保存,确保:字段验证错误会返回清晰提示;SQL 或系统异常不会暴露给前端,而是记录日志并返回友好消息。
/**
* 安全保存模型:支持字段验证错误输出,系统异常捕获
*
* @param ActiveRecord $model 模型实例
* @param array $options 额外参数支持:
* - 'successMessage' 成功时返回的提示信息
* - 'failMessage' 验证失败时返回的提示信息
* - 'exceptionMessage' 异常时返回的提示信息
* - 'isUpdate' 是否更新操作(true/false)
*
* @return array
*/
protected function safeSave(ActiveRecord $model, array $options = []): array
{
$successMessage = $options['successMessage'] ?? '保存成功';
$failMessage = $options['failMessage'] ?? '字段验证失败';
$exceptionMessage = $options['exceptionMessage'] ?? '数据库操作失败,请联系管理员';
$isUpdate = $options['isUpdate'] ?? false;
try {
if (!$model->validate()) {
return [
'code' => 10200,
'message' => $failMessage . ': ' . ModelHelper::getFirstError($model),
];
}
if (!$isUpdate) {
$model->save(false); // insert
} elseif ($model->getDirtyAttributes()) {
// 如果是更新操作,检查是否变动再 save(false)
$model->save(false);
}
return [
'code' => 10000,
'message' => $successMessage,
'data' => $model,
];
} catch (Throwable $e) {
Yii::error([
'exception' => get_class($e),
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTraceAsString(),
], 'application.model.save.exception');
return [
'code' => 10500,
'message' => $exceptionMessage,
];
}
}
/api/helpers/ModelHelper.php
<?php
namespace api\helpers;
use yii\base\Model;
class ModelHelper
{
/**
* 获取模型验证失败时的第一个错误信息
* @param Model $model
* @return string
*/
public static function getFirstError(Model $model): string
{
// 验证失败:$errors 是一个包含错误信息的数组
$errors = $model->getErrors();
$firstMessage = '验证失败';
if (!empty($errors)) {
// 获取第一个属性名
$firstAttribute = array_key_first($errors);
$firstFieldErrors = $errors[$firstAttribute] ?? [];
// 获取第一个属性的第一个错误
$firstMessage = is_array($firstFieldErrors) && isset($firstFieldErrors[0])
? $firstFieldErrors[0]
: $firstMessage;
}
return $firstMessage;
}
}
4、现有的实现如下,存在字段本身的验证失败后,接口响应:字段验证失败: 动画时长必须是一个数字。 符合预期。如图2


近期评论