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
相关产品推荐
相关产品推荐

