Yii 1.1: Update two models with one view

13 followers

Suppose to have two models: Users and Emails. You do not want to store email in a Users model. And User can have 0 or many emails. This is the form generated to create a new user (just username).

<?php $form = $this->beginWidget(CActiveForm’, array(id’ => ‘users-form’,
        ‘enableAjaxValidation’ => false      )); ?>
        <?php echo $form->labelEx($model, ‘username); ?>
        <?php echo $form->textField($model, ‘username’, array(size’ => 60, ‘maxlength’ => 250)); ?>
        <?php echo $form->error($model, ‘username); ?>
        <?php echo CHtml::submitButton($model->isNewRecord ? ‘Create’ : ‘Save); ?>
    <?php $this->endWidget(); ?>

First, ... we can add email field to _form.php template passing Emails::model() as model.

<?php $form = $this->beginWidget(CActiveForm’, array(id’ => ‘users-form’,
        ‘enableAjaxValidation’ => false      )); ?>
        <?php echo $form->labelEx($model, ‘username); ?>
        <?php echo $form->textField($model, ‘username’, array(size’ => 60, ‘maxlength’ => 250)); ?>
        <?php echo $form->error($model, ‘username); ?>
        <?php echo $form->labelEx(Emails::model(), ‘email); ?>
        <?php echo $form->textField(Emails::model(), ‘email’, array(size’ => 60, ‘maxlength’ => 250)); ?>
        <?php echo $form->error(Emails::model(), ‘email); ?>
        <?php echo CHtml::submitButton($model->isNewRecord ? ‘Create’ : ‘Save); ?>
    <?php $this->endWidget(); ?>

Second ... we could update actionCreate of UsersController by adding the code like below:

$modelEmail = new Emails;
    $modelEmail->attributes = $_POST['Emails'];
    $modelEmail->iduser = $model->id;
    if ($modelEmail->save())
        $this->redirect(array('view', 'id' => $model->id));

Maybe Users::actionCreate(); will appear like this:

public function actionCreate() {
        $model = new Users;
        if (isset($_POST['Users'])) {
            $model->attributes = $_POST['Users'];
            if ($model->save()) {
                $modelEmail = new Emails;
                $modelEmail->attributes = $_POST['Emails'];
                $modelEmail->iduser = $model->id;
                if ($modelEmail->save())
                    $this->redirect(array('view', 'id' => $model->id));
            }
        }
        $this->render('create', array(
            'model' => $model,
        ));
    }

Total 11 comments

#17674 report it
Rohit Suthar at 2014/07/14 02:39am
simple, short and nice example

this is simple, short code, nice and working example -

http://www.dukaweb.net/2013/11/update-two-models-with-one-view-yii-tutorial.html

#13126 report it
Tahir Yasin at 2013/05/07 01:23am
How to save multiple related models in yii [Complete Solution]

Checkout my blog post to see complete solution to this problem, I'm sure you will love it!

http://scriptbaker.com/how-to-save-multiple-related-models-in-yii-complete-solution/

#6321 report it
binkabir at 2011/12/29 05:29am
using ajax validation to reduce multiply "if statement"

i think its much better to use the default protected function "performAjaxValidation" inside the controller to reduce possibilities of invalid user inputs in either of the models. just edit the protected method like these.

public function actionCreate
 $user = new User; 
$email = new Email ; 
 
$this->performAjaxValidation($user,$email); 
 
if(isset($_POST['User'],$_POST['Email'] )){
 $user->attributes = $_POST['User']; 
 $email->attributes = $_POST['Email]; 
  //save the models and redirect;
}
$this->render('yourViewFile',array(
            'userModel'=>$user,'emailModel'=>$email
        ));

now in the protected method.

protected function performAjaxValidation($user,$email)
    {
        if(isset($_POST['ajax']))
        {
              //you pass pass multiply models into the CactiveForm::validate
 
            echo CActiveForm::validate(array($user,$email));
 
            Yii::app()->end();
 
        }
#6240 report it
urecheatu007 at 2011/12/21 05:53am
we have validation scenarios

Well said. There is however a less used feature for the validation rules: scenarios. You can declare rules to be run only for certain scenarios: some infos here

#6239 report it
redguy at 2011/12/21 05:32am
@mucha1306: this should work, but...

this is what I ment when I wrote "or at least add if( $model->validate() && $modelEmail->validate() ) before saving anything... it is not ideal but could work in some cases..."

however there are still cases when this will fail (simple egzample: date format of entered date is different from db engine date format - validation will pass if you do not provide strict rule, but inserting data will thor exception). Another egzample: Emails model could have 'iduser' marked as 'required' because it must refer to existing user. When creating new user with email you can't provide iduser to validate Emails record, because User is not created yet so validation fill fail. The only solution is to skip validating iduser as required and relay on developers that everywhere they will use this model - iduser will be filled somehow... That's why transactions are always better.

On the other hand there are number of databases/engines (MySQL MyISAM tables are one of them) which do not support transactions (but accept 'begin transaction'/'commit'/'rollback' silently). You have to keep this in mind and use this construction with validation of both records first. The world is not ideal.. ;)

#6238 report it
mucha1306 at 2011/12/21 04:49am
Validations first approach

i use this method:

public function actionCreate() {
        $model = new Users;
        if (isset($_POST['Users'])) { //form has been submited
            $model->attributes = $_POST['Users'];
            $modelEmail = new Emails;
            $modelEmail->attributes = $_POST['Emails'];
            if ($model->validate() && $modelEmail->validate()) {
                $modelEmail->save(false);   //second validation pass is not needed
                $modelEmail->iduser = $model->id;
                $modelEmail->save(false);
                $this->redirect(array('view', 'id' => $model->id));
 
            }
        }
        $this->render('create', array(
            'model' => $model,
        ));
    }
#6228 report it
urecheatu007 at 2011/12/20 04:11pm
might be better to redirect to edit if you don't want transactions

Redirecting to the edit action for the model you make sure that even if the related field was not inserted in the database you do not try to create the same model again. It's not perfect but it saves you some headaches if you don't use transactions.

On the other hand that's why you have model validation to ensure that every piece of data entered by the user is in the right format and safe for insertion. So, one could tweak the logic of the action to ensure first that ALL data is valid before runing the insert, so you know in advance if there are problems. You can see here the validation method.

#6210 report it
sensorario at 2011/12/19 12:31pm
Thank you very much =)

Thank you very much redguy =)

#6208 report it
redguy at 2011/12/19 11:44am
it should rather be
$model = new Users;
if (isset($_POST['Users'])) {
    $transaction=Yii::app()->db->beginTransaction();
    try {
        $model->attributes = $_POST['Users'];
        if ($model->save()) {
            $modelEmail = new Emails;
            $modelEmail->attributes = $_POST['Emails'];
            $modelEmail->iduser = $model->id;
            if ($modelEmail->save()) {
                $transaction->commit();
                $this->redirect(array('view', 'id' => $model->id));
            }
        }
        //something went wrong...
        $transaction->rollBack();
    }
    catch(Exception $e) { // an exception is raised if a query fails
        //something was really wrong - exception!
        $transaction->rollBack();
 
        //you should do sth with this exception (at least log it or show on page)
        Yii::log( 'Exception when saving data: ' . $e->getMessage(), CLogger::LEVEL_ERROR );
    }
}
#6206 report it
sensorario at 2011/12/19 11:21am
If email is not valid ...

If email is not valid ... the system do not save the email and do not redirect right!

This:

            if ($modelEmail->save())
                $this->redirect(array('view', 'id' => $model->id));

Can be updated:

            $modelEmail->save();
            $this->redirect(array('view', 'id' => $model->id));

But I must update this wiki. Have you a suggestion?

#6205 report it
redguy at 2011/12/19 11:06am
without db transaction it doesn't make much sense

What if user record is saved to database but emails not (for example entered e-mail is not a valid e-mail...)? then you will see same form again with error message that e-mail was invalid, but any try to submit it again will fail with error that such user already exist in database (first call created it).

you should use database transaction and rollback it when saving Emails fail or at least add if( $model->validate() && $modelEmail->validate() ) before saving anything... it is not ideal but could work in some cases...

in fact - saving multiple models with one action always should involve database transaction or you will end up with inconsistent database and strange errors.

Leave a comment

Please to leave your comment.

Write new article
  • Written by: sensorario
  • Updated by: SebK
  • Category: Tips
  • Yii Version: 1.1
  • Votes: +10 / -2
  • Viewed: 32,402 times
  • Created on: Dec 19, 2011
  • Last updated: May 3, 2012
  • Tags: database, model, view, form