You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Yii2图片上传时change事件未完成即触发submit报错问题排查

Yii2 配合 jQuery 自动提交图片上传报错问题修复

问题背景

开发中通过监听文件选择框的change事件,选中图片后自动触发表单提交,后端抛出Attempt to read property "name" on null错误,最初怀疑是前端事件提前触发导致,相关代码如下:

视图文件 create.php

<?php \yii\bootstrap4\ActiveForm::begin([
    'options' => [
        'enctype' => 'multipart/form-data',
        'id' => 'dynamic-form'
    ]
]) ?>

<button class="btn btn-primary btn-file">
    Select Thumbnail
    <input type="file" id="recipeThumbnail" name="recipe">
</button>

<?php \yii\bootstrap4\ActiveForm::end() ?>

JS 文件 app.js

$(function () {
    'use strict';
    $('#recipeThumbnail').change(ev => {
        $(ev.target).closest('form').trigger('submit');
    })
});

模型文件 Recipe.php

public function save($runValidaiton = true, $attributeNames = null)
{
    $isInsert = $this->isNewRecord;
    if ($isInsert) {
        $this->recipe_id = Yii::$app->security->generateRandomString(16);
        $this->name = $this->recipe->name;
    }
    $saved = parent::save($runValidaiton, $attributeNames);
    if (!$saved) {
        return false;
    }
    if ($isInsert) {
        $recipePath = Yii::getAlias('@frontend/web/storage/thumbnail/' . $this->recipe_id . '.jpg');
        if (!is_dir(dirname($recipePath))) {
            FileHelper::createDirectory(dirname($recipePath));
        }
        $this->recipe->saveAs($recipePath);
    }

    return true;
}

控制器文件 RecipeController.php

public function actionCreate()
{
    $model = new Recipe();

    $model->recipe = UploadedFile::getInstanceByName('recipe');
    
    if ($this->request->isPost) {
        if (Yii::$app->request->isPost && $model->save()) {
            return $this->redirect(['view', 'recipe_id' => $model->recipe_id]);
        }

    } else {
        $model->loadDefaultValues();
    }

    return $this->render('create', [
        'model' => $model,
    ]);
}

根因定位

该错误和前端change事件触发时机无关:jQuery对file类型input的change事件,默认就是在用户完成文件选择、输入框值更新完成后才触发,不存在提前执行的问题。报错完全来自后端逻辑缺陷,核心问题有两个:

  1. 控制器代码执行顺序错误:获取上传文件实例的代码写在了POST请求判断外,GET请求访问页面、上传大小超限、文件上传失败等场景下,UploadedFile::getInstanceByName('recipe')会返回null,该null值会被直接赋值给$model->recipe
  2. 模型层缺少空值校验:save方法中未判断$this->recipe是否为有效上传实例,直接访问其name属性、调用saveAs方法,只要上传实例为null就会抛出属性访问错误

修复方案

1. 调整控制器代码执行顺序

将上传文件获取逻辑移到POST请求判断内部,仅在确实收到表单提交请求时才处理上传文件:

public function actionCreate()
{
    $model = new Recipe();
    if ($this->request->isPost) {
        // 仅POST请求下获取上传实例
        $model->recipe = UploadedFile::getInstanceByName('recipe');
        if ($model->save()) {
            return $this->redirect(['view', 'recipe_id' => $model->recipe_id]);
        }
    } else {
        $model->loadDefaultValues();
    }

    return $this->render('create', [
        'model' => $model,
    ]);
}

2. 模型层增加校验逻辑

首先在模型的rules()方法中添加上传文件验证规则,从框架层面拦截无效上传:

public function rules()
{
    return [
        // 其他原有规则...
        [['recipe'], 'file', 'skipOnEmpty' => false, 'extensions' => 'jpg,jpeg,png', 'maxSize' => 2*1024*1024],
    ];
}

然后修改save方法,增加空值判断,避免直接访问null的属性:

public function save($runValidaiton = true, $attributeNames = null)
{
    $isInsert = $this->isNewRecord;
    if ($isInsert) {
        $this->recipe_id = Yii::$app->security->generateRandomString(16);
        // 确认上传实例有效再读取属性
        if ($this->recipe) {
            $this->name = $this->recipe->name;
        }
    }
    $saved = parent::save($runValidaiton, $attributeNames);
    if (!$saved) {
        return false;
    }
    if ($isInsert && $this->recipe) {
        $recipePath = Yii::getAlias('@frontend/web/storage/thumbnail/' . $this->recipe_id . '.jpg');
        if (!is_dir(dirname($recipePath))) {
            FileHelper::createDirectory(dirname($recipePath));
        }
        $this->recipe->saveAs($recipePath);
    }

    return true;
}

3. 前端可选兜底校验

如果需要确认事件触发时机,可以在change回调中增加文件存在性判断,确认选中有效文件后再提交:

$(function () {
    'use strict';
    $('#recipeThumbnail').change(ev => {
        // 校验是否选中了有效文件
        if (ev.target.files && ev.target.files.length > 0) {
            $(ev.target).closest('form').trigger('submit');
        }
    })
});

排查思路总结

  • 前端事件类问题优先在回调入口打日志调试,通过打印选中的文件列表即可确认事件触发时机是否正常,不要盲目猜测事件执行顺序
  • 后端属性访问报错优先定位报错行,确认调用属性的变量来源、赋值逻辑,顺着赋值链路排查即可快速定位空值来源
  • 涉及文件上传的逻辑必须做两层校验:控制器层判断请求类型再处理上传,模型层做非空判断+文件规则验证,避免异常场景下直接报错

内容的提问来源于stack exchange,提问作者yai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 13:48:21