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

Yii2含一对多关联的多模型表单更新报错,求解决方法

解决Yii2一对多表单更新报错的问题

看起来你在Yii2中处理主表bukti_penerimaan关联两个子表的一对多表单时,创建功能正常但更新功能报错。这通常是因为更新场景下没有正确配置模型关联、表单字段命名或控制器逻辑,导致Yii2无法识别批量提交的子表数据。下面是一步步的解决方案:

1. 主模型(BuktiPenerimaan)配置

首先要确保主模型正确定义关联关系,并允许接收子表的数组数据:

1.1 定义关联关系

在BuktiPenerimaan模型中添加两个hasMany关联:

use yii\db\ActiveRecord;
use yii\helpers\ArrayHelper;

class BuktiPenerimaan extends ActiveRecord
{
    // ...其他代码

    public function getBuktiPenerimaanInvoices()
    {
        return $this->hasMany(BuktiPenerimaanInvoice::class, ['bukti_penerimaan_id' => 'id']);
    }

    public function getBuktiPenerimaanDebitNotes()
    {
        return $this->hasMany(BuktiPenerimaanDebitNote::class, ['bukti_penerimaan_id' => 'id']);
    }
}

1.2 允许批量赋值子表属性

在模型的rules和scenarios中添加子表属性的权限:

public function rules()
{
    return [
        // 主表字段规则...
        [['buktiPenerimaanInvoices', 'buktiPenerimaanDebitNotes'], 'safe'],
        // 可选:用each validator做更严格的验证
        // ['buktiPenerimaanInvoices', 'each', 'rule' => ['safe']],
    ];
}

public function scenarios()
{
    $scenarios = parent::scenarios();
    // 允许update场景下赋值子表属性
    $scenarios['update'] = array_merge($scenarios['update'], ['buktiPenerimaanInvoices', 'buktiPenerimaanDebitNotes']);
    return $scenarios;
}

1.3 批量保存子模型的方法

添加一个方法处理子模型的新增、更新和删除:

public function saveRelatedModels()
{
    // 处理bukti_penerimaan_invoice
    foreach ($this->buktiPenerimaanInvoices as $model) {
        $model->bukti_penerimaan_id = $this->id;
        if (!$model->save()) {
            return false;
        }
    }
    // 删除用户移除的子记录
    $existingInvoiceIds = ArrayHelper::getColumn($this->buktiPenerimaanInvoices, 'id');
    $invoiceCondition = ['bukti_penerimaan_id' => $this->id];
    if (!empty($existingInvoiceIds)) {
        $invoiceCondition['id'] = ['not in', $existingInvoiceIds];
    }
    BuktiPenerimaanInvoice::deleteAll($invoiceCondition);

    // 同理处理bukti_penerimaan_debit_note
    foreach ($this->buktiPenerimaanDebitNotes as $model) {
        $model->bukti_penerimaan_id = $this->id;
        if (!$model->save()) {
            return false;
        }
    }
    $existingDebitNoteIds = ArrayHelper::getColumn($this->buktiPenerimaanDebitNotes, 'id');
    $debitNoteCondition = ['bukti_penerimaan_id' => $this->id];
    if (!empty($existingDebitNoteIds)) {
        $debitNoteCondition['id'] = ['not in', $existingDebitNoteIds];
    }
    BuktiPenerimaanDebitNote::deleteAll($debitNoteCondition);

    return true;
}

2. 控制器actionUpdate逻辑调整

修改actionUpdate,确保加载现有子模型、处理批量提交数据,并使用事务保证数据一致性:

public function actionUpdate($id)
{
    $model = $this->findModel($id);
    // 加载已有的子模型
    $existingInvoices = $model->buktiPenerimaanInvoices;
    $existingDebitNotes = $model->buktiPenerimaanDebitNotes;

    if ($model->load(Yii::$app->request->post())) {
        // 批量加载子模型数据
        $newInvoices = BuktiPenerimaanInvoice::createMultiple(BuktiPenerimaanInvoice::class, $existingInvoices);
        BuktiPenerimaanInvoice::loadMultiple($newInvoices, Yii::$app->request->post());
        $model->buktiPenerimaanInvoices = $newInvoices;

        $newDebitNotes = BuktiPenerimaanDebitNote::createMultiple(BuktiPenerimaanDebitNote::class, $existingDebitNotes);
        BuktiPenerimaanDebitNote::loadMultiple($newDebitNotes, Yii::$app->request->post());
        $model->buktiPenerimaanDebitNotes = $newDebitNotes;

        // 事务处理:主表和子表要么都保存成功,要么回滚
        $transaction = Yii::$app->db->beginTransaction();
        try {
            if ($model->save() && $model->saveRelatedModels()) {
                $transaction->commit();
                return $this->redirect(['view', 'id' => $model->id]);
            } else {
                $transaction->rollBack();
            }
        } catch (\Exception $e) {
            $transaction->rollBack();
            throw $e;
        }
    }

    // GET请求时,将现有子模型赋值给主模型属性,用于渲染表单
    $model->buktiPenerimaanInvoices = $existingInvoices;
    $model->buktiPenerimaanDebitNotes = $existingDebitNotes;

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

3. 视图层表单调整(_form.php)

确保子表字段以数组格式命名(比如[0]invoice_number),让Yii2识别为批量数据。推荐使用yii2-dynamic-form扩展实现动态添加/删除子行:

3.1 注册动态表单资源(如果使用扩展)

use yii\widgets\ActiveForm;
use yii\helpers\Html;
use yii\dynamicform\DynamicFormWidget;

3.2 渲染子表表单(以bukti_penerimaan_invoice为例)

<?php $form = ActiveForm::begin(['id' => 'dynamic-form']); ?>

<!-- 主表字段 -->
<?= $form->field($model, 'your_main_field')->textInput() ?>

<!-- 子表Invoice动态表单 -->
<div class="panel panel-default">
    <div class="panel-heading">
        <h4>Invoices</h4>
        <?= Html::button('<i class="glyphicon glyphicon-plus"></i> Add Invoice', ['class' => 'btn btn-success btn-sm pull-right add-item-invoice']) ?>
    </div>
    <div class="panel-body">
        <?php DynamicFormWidget::begin([
            'widgetContainer' => 'dynamicform_wrapper_invoice',
            'widgetBody' => '.container-items-invoice',
            'widgetItem' => '.item-invoice',
            'limit' => 10,
            'insertButton' => '.add-item-invoice',
            'deleteButton' => '.remove-item-invoice',
            'model' => $model->buktiPenerimaanInvoices[0] ?? new BuktiPenerimaanInvoice(),
            'formId' => 'dynamic-form',
            'formFields' => ['invoice_number', 'amount'],
        ]); ?>

        <div class="container-items-invoice">
            <?php foreach ($model->buktiPenerimaanInvoices as $index => $invoiceModel): ?>
                <div class="item-invoice panel panel-default">
                    <div class="panel-heading">
                        <h5>Invoice #<?= $index+1 ?></h5>
                        <button type="button" class="pull-right remove-item-invoice btn btn-danger btn-xs"><i class="glyphicon glyphicon-minus"></i></button>
                        <div class="clearfix"></div>
                    </div>
                    <div class="panel-body">
                        <!-- 更新场景下隐藏子模型ID,用于识别是更新还是新增 -->
                        <?php if (!$invoiceModel->isNewRecord): ?>
                            <?= Html::activeHiddenInput($invoiceModel, "[{$index}]id") ?>
                        <?php endif; ?>
                        <?= $form->field($invoiceModel, "[{$index}]invoice_number")->textInput() ?>
                        <?= $form->field($invoiceModel, "[{$index}]amount")->textInput(['type' => 'number']) ?>
                    </div>
                </div>
            <?php endforeach; ?>
        </div>
        <?php DynamicFormWidget::end(); ?>
    </div>
</div>

<!-- 同理渲染Debit Note的动态表单 -->
<!-- ...代码结构和Invoice类似,替换为BuktiPenerimaanDebitNote相关字段 -->

<div class="form-group">
    <?= Html::submitButton($model->isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?>
</div>

<?php ActiveForm::end(); ?>

常见报错排查点

  • 字段命名错误:子表字段必须用[index]前缀,比如BuktiPenerimaanInvoice[0][invoice_number],否则Yii2无法识别为批量数据。
  • 子模型验证失败:检查子模型(BuktiPenerimaanInvoice/BuktiPenerimaanDebitNote)的rules,确保提交的字段都被允许赋值。
  • 外键约束错误:确保子模型的bukti_penerimaan_id在保存时被正确赋值(主模型的saveRelatedModels方法中已处理)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 03:38:23