-->

Yii2 multiple models in one form

2019-06-22 10:48发布

问题:

How to use multiple models in one form in Yii2?

My case:

In my create action I can save into agenda_fiscalizacao table, but in update I receive this error when I try to load the form:

Call to a member function formName() on array    

My Update Action:

public function actionUpdate($id)
{
    $model = $this->findModel($id);
    $modelAgenda = AgendaFiscalizacao::findAll(['fiscalizacao_id' => $id]);

    if ($model->load(Yii::$app->request->post()) && Model::loadMultiple($modelAgenda, Yii::$app->request->post())) {
        $valid = $model->validate();
        $valid = $modelAgenda->validade() && $valid;

        if ($valid) {
            $model->save(false);
            $modelAgenda->save(false);
            return $this->redirect(['view', 'id' => $model->id]);
        }
    }

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

My form view

<?= $form->field($modelAgenda, 'agenda_id')->checkboxList(Agenda::combo(), ['class' => 'checkbox']) ?>
<?= $form->field($model, 'bioma_id')->dropDownList(Bioma::combo(), ['prompt' => $prompt]) ?>
<?= $form->field($model, 'nome')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'tipo_doc')->radioList(['CPF'=>'CPF', 'CNPJ'=>'CNPJ'], ['class' => 'radio']) ?>
<?= $form->field($model, 'n_doc')->widget(MaskedInput::className(), ['mask' => ['999.999.999-99', '99.999.999/9999-99']]) ?>
<?= $form->field($model, 'observacao')->textarea(['rows' => 7]) ?>    

What could be wrong?

EDIT (full error):

回答1:

1) If you mean working with multiple models of the same type, the error is in this line:

$valid = $modelAgenda->validade() && $valid;

First, it should be $modelAgenda->validate(), second $modelAgenda contains array of models, validate() method can be called only on single model.

For validating multiple models Yii2 suggests using built-in method validateMultiple():

use yii\base\Model;

...

$valid = Model::validateMultiple($modelAgenda) && $valid;

Working with multiple models is well covered in official docs (Collecting Tabular Input).

Note that they recommend to index models array by id before like this:

$models = YourModel::find()->index('id')->all();

2) If you need just two models of different type, don't use findAll() because it's for finding multiple models and always returns array (even on empty result). Use new for create action and findOne() for update action to initialize models. Let's say you initialized two models, $firstModel and $secondModel, then you can load and save them like this:

$isSuccess = false;

Yii::$app->db->transaction(function () use ($isSuccess) {
    $areLoaded = $firstModel->load(Yii::$app->request->post()) && $secondModel->load(Yii::$app->request->post();
    $areSaved = $firstModel->save() && $secondModel->save();
    $isSuccess = $areLoaded && $areSaved;
});

if ($isSuccess) {
    return $this->redirect(['view', 'id' => $model->id]);
}

Transaction is added in case of saving of second model will fail (so the first model also will not be saved).

Alternatively, you can declare transactions inside your model, for example:

return [
    'admin' => self::OP_INSERT,
    'api' => self::OP_INSERT | self::OP_UPDATE | self::OP_DELETE,
    // the above is equivalent to the following:
    // 'api' => self::OP_ALL,
];

Then just use:

$firstModel->scenario = 'scenarioForTransaction';
$secondModel->scenario = 'scenarioForTransaction';
$areLoaded = $firstModel->load(Yii::$app->request->post()) && $secondModel->load(Yii::$app->request->post();
$areSaved = $firstModel->save() && $secondModel->save();

if ($areLoaded && $areSaved) {
    return $this->redirect(['view', 'id' => $model->id]);
}

For more than two models, it's better to use loops.

P.S. I'd recommend to separate saving to different controllers / actions and call it via AJAX, it will be more user friendly.

For saving relations read - Saving Relations.



回答2:

public function actionUpdate($id)
{
    $model = $this->findModel($id);
    $modelAgenda = AgendaFiscalizacao::findAll(['fiscalizacao_id' => $id]);

    if ($model->load(Yii::$app->request->post()) && $modelAgenda->load(Yii::$app->request->post()) && Model::validateMultiple([$model, $modelAgenda])) {

            $model->save(false);
            $modelAgenda->save(false);
            return $this->redirect(['view', 'id' => $model->id]);

    }

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

You can refer following link for example : http://blog.dedikisme.com/blog/2014/10/13/yii2-building-a-single-form-with-multiple-models



回答3:

You are not rendering $modelAgenda from view/update.php to view/_form.php file while using render in update file.