8

How to use multiple models in one form in Yii2?

My case: ER diagram

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):

enter image description here

jflizandro
  • 588
  • 1
  • 5
  • 15

3 Answers3

9

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.

arogachev
  • 31,868
  • 6
  • 105
  • 113
  • Doesn't work. In my case the form doesn't load, so then does not even enter in IF. Sorry for my bad english. – jflizandro Jan 26 '16 at 16:51
  • @jflizandro I updated the answer with more detailed explanation. – arogachev Jan 26 '16 at 16:55
  • I think the problem is in view, because when I comment the line that creates the checkboxList, the error disappears and the form is loaded correctly. – jflizandro Jan 26 '16 at 17:48
  • The checkboxList must receive the same model as the other fields ($form->field($model, 'agenda_id')->checkboxList...). – nicolascolman Feb 27 '18 at 18:19
3
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

Dedy Kurniawan
  • 276
  • 2
  • 3
  • 8
0

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