In Yii2, an interface does not want to expose the SQL error in the interface response, but also hopes that the validation error of the exposed field itself is in the interface response, how should it be implemented?
1. The existing implementation is as follows. After the verification of the field itself fails, the interface response will always be: the database operation fails, please contact the administrator, the user experience is not friendly. as shown in Figure 1
if ($animation->save()) {
return [
'code' => 10000,
'message' => '设置动画属性成功',
'data' => $animation,
];
}
return [
'code' => 10004,
'message' => '数据库操作失败,请联系管理员',
];
2. Hope to achieve the following, 1. Do not expose database exceptions (such as SQL errors, PDO exceptions, etc.) – avoid security risks. 2. Keep the validation errors of the model fields (such as required, string length, etc.) – give the front end clear feedback.
3. Implement the reusable BaseController::SafeSave() method, which can help you to process the model saving in yii2, and ensure that the field verification error will return a clear prompt; sql Or system exceptions are not exposed to the frontend, but logs and return friendly messages.
/**
* 安全保存模型:支持字段验证错误输出,系统异常捕获
*
* @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. The existing implementation is as follows. After the validation of the field itself fails, the interface responds: the field validation fails: The animation time must be a number. in line with expectations. as shown in Figure 2

