没有不值得去解决的问题,也没有不值得去学习的技术!

How to Hide SQL Errors but Expose Field Validation Errors in a Yii2 API Response

With the current implementation, when field validation fails, the API response reads: Field validation failed: Animation duration must be a number. This meets expectations.

1. The current implementation is as follows. When field validation fails, the API response always says: Database operation failed, please contact the administrator. This is a poor user experience. See Figure 1.

The current implementation is as follows. When field validation fails, the API response always says: Database operation failed, please contact the administrator. This is a poor user experience.
Figure 1
PHP
        if ($animation->save()) {
            return [
                'code' => 10000,
                'message' => '设置动画属性成功',
                'data' => $animation,
            ];
        }
        return [
            'code' => 10004,
            'message' => '数据库操作失败,请联系管理员',
        ];

2. The desired implementation is as follows: 1. Do not expose database exceptions (such as SQL errors, PDO exceptions, etc.) — to avoid security risks. 2. Preserve model field validation errors (such as required, string length, etc.) — to provide clear feedback to the frontend.

3. Implementing a reusable BaseController::safeSave() method can help you uniformly handle model saving in Yii2, ensuring that: field validation errors return clear messages; SQL or system exceptions are not exposed to the frontend, but are instead logged and return a friendly message.

PHP

    /**
     * 安全保存模型:支持字段验证错误输出,系统异常捕获
     *
     * @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
<?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. With the current implementation, when field validation fails, the API response reads: Field validation failed: Animation duration must be a number. This meets expectations. See Figure 2.

With the current implementation, when field validation fails, the API response reads: Field validation failed: Animation duration must be a number. This meets expectations.
Figure 2

关于作者 · 持续学习与技术实践

我是一名拥有 15 年以上开发经验的技术从业者,自 2013 年起持续运营个人技术博客,记录工作与个人项目中遇到的真实问题,以及解决问题过程中的探索、实践和思考。

除了技术写作,我也在持续开发和维护自己的独立项目,探索新技术和新工具在实际场景中的应用。我始终相信,没有不值得去解决的问题,也没有不值得去学习的技术。

如果这篇文章对你有所帮助,欢迎浏览博客中的其他文章,也欢迎交流技术经验与想法。

关于我  ·  GitHub  ·  邮件联系