Yii2 AJAX错误返回HTML的根本原因是错误处理未适配JSON格式,需同时配置请求识别、响应格式和errorAction三部分,并在actionError中显式设置statusCode。

Yii2 AJAX请求500错误却返回HTML页面
默认ErrorHandler只在 YII_DEBUG = false 且请求带 Accept: application/json 或是AJAX时才返回JSON,但curl、Postman、jQuery默认不发这个header,结果你看到的是整页HTML源码,前端根本没法解析。
别指望客户端加header——API接口必须服务端兜底。最直接的改法是在入口文件 web/index.php 的 (new yii\web\Application($config))->run(); 前插入:
Yii::$app->on(\yii\base\Application::EVENT_BEFORE_REQUEST, function ($event) {
if (Yii::$app->request->isAjax || strpos(Yii::$app->request->getContentType(), 'application/json') !== false) {
Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
}
});
- 这个设置只影响正常响应格式,不接管异常流;所以仍需配合下面的errorAction
- 不要用
Yii::$app->response->format = Response::FORMAT_JSON全局设——它对错误不生效 - 如果项目分前后台,建议只为API模块单独配置
errorHandler,避免干扰Web页面
控制器里抛异常后前端收不到message字段
直接 throw new \Exception('xxx') 会触发PHP致命错误页;而 throw new \yii\web\BadRequestHttpException('xxx') 才能被ErrorHandler捕获并结构化输出。但即使这样,actionError 里若只写 return ['message' => $error['message']],HTTP状态码仍是200,前端可能误判为成功。
正确做法是在 SiteController::actionError() 中显式设状态码,并统一结构:
public function actionError()
{
$error = Yii::$app->errorHandler->error;
if ($error) {
Yii::$app->response->statusCode = $error['exception']->statusCode ?: 500;
return [
'code' => $error['exception']->statusCode ?: 500,
'message' => $error['message'],
];
}
}
- 必须在
config/web.php里配好'errorHandler' => ['errorAction' => 'site/error'] - 继承
\yii\base\UserException的自定义异常(如业务校验失败)会被当成4xx处理,不会进500分支 - 别在
actionError里用echo或die(),会绕过Response格式控制
CSRF验证失败导致AJAX报400却没提示
常见现象:表单提交正常,但AJAX POST直接500或400,控制台显示 HttpException:400 Unable to verify your data submission。根源不是没传token,而是传了错的token——JS里用 = \yii::$app->request->csrfToken ?> 会生成新token,跟页面meta里那个不一致。
正确取法永远是从DOM里读:
var csrfToken = $('meta[name="csrf-token"]').attr('content');
$.ajax({
url: '/api/submit',
type: 'POST',
headers: { 'X-CSRF-Token': csrfToken },
// ...
});
- 确保页面
<head>有<meta name="csrf-token" content="xxx">(Yii默认渲染) - 控制器中禁用CSRF仅限极少数场景(如公开短信接口),否则应保留验证,只修正token传递方式
- 如果用
FormData提交,记得手动append:formData.append('_csrf', csrfToken)
ActiveForm异步验证返回空数组或验证码失效
启用 enableAjaxValidation 后,ActiveForm::validate() 默认校验全部字段,但验证码每次验证都会刷新,导致第二次验证必然失败。
解决方法是限定校验字段,把验证码排除在外:
public function actionValidateForm()
{
$model = new LoginForm();
$model->load(Yii::$app->request->post());
Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
// 只校验 userID 和 password,跳过 code 字段
$errors = \yii\widgets\ActiveForm::validate($model, ['userID', 'password']);
return $errors;
}
- 视图中
validationUrl必须指向这个专用动作,不能复用actionLogin - 如果模型规则里
'code' => 'captcha'是必需的,那它必须由最终提交动作(非验证动作)来校验 - 别在验证动作里调用
$model->validate()全量校验——它会触发captcha重生成
actionError 里忘了设 statusCode,看着JSON结构对了,状态码却是200。


















