Yii 1.1: How to use Multiple instances of the same model in the same form

23 followers

When i had created this functionality then i found some difficulties and not got much idea from wiki and forums. so, i think this will be useful for newbie users and save time of other developers when create related functionality. I refer Collecting Tabular Input tutorial but not got clear idea for create/update.

Here in my demo i had two tables as, User(table) can have multiple address ( home, company ) which comes from Address(table)

Model generated by gii and no any change in model.

User Table
---id
---name
---surname
---email
---home_address_id ( data refer to Address table )
---company_address_id ( data refer to Address table )
Address Table
---id
---street
---city
---state

Developer can also use loop for multiple instances here in my demo i explained with two.

Create Controller

public function actionCreate()
{
    $model=new User;  // User Model
    $addressModel_1 = new Address;  // Address Model
    $addressModel_2 = new Address;  // Address Model
 
    if(!empty($_POST))
    {
        // Set attribute for home address
        $addressModel_1->attributes=$_POST['Address'][1];
        // Set attribute for company address
        $addressModel_2->attributes=$_POST['Address'][2];
        // Set attribute for user data
        $model->attributes=$_POST['User'];  
 
        // Validate all three model
        $valid=$addressModel_1->validate();
        $valid=$addressModel_2->validate() && $valid;
        $valid=$model->validate() && $valid;
 
        if($valid)
        { 
            $addressModel_1->save();
            $homeAddressId = $addressModel_1->id;
 
            $addressModel_2->save();
            $companyAddressId = $addressModel_2->id;
 
            // Set saved address as user home id
            $model->home_address_id = $homeAddressId;
            // Set saved address as user company id
            $model->company_address_id = $companyAddressId;  
            $model->save();
            $this->redirect(array('view','id'=>$model->id));
        }
    }
    $this->render('create',array(
        'model'=>$model,
        'addressModel_1'=>$addressModel_1,
        'addressModel_2'=>$addressModel_2,
    ));
}

Update Controller

public function actionUpdate($id)
{
    // Load user data in model
    $model=User::model()->findByPk($id);
    // Load address data in addressModel_1
    $addressModel_1=Address::model()->findByPk($model->home_address_id);  
    // Load address data in addressModel_2
    $addressModel_2=Address::model()->findByPk($model->company_address_id);  
 
    if(!empty($_POST))
    {
        // Set attribute for home address
        $addressModel_1->attributes=$_POST['Address'][1];
        // Set attribute for company address
        $addressModel_2->attributes=$_POST['Address'][2];
        // Set attribute for user data
        $model->attributes=$_POST['User'];
 
        // Validate all three model
        $valid=$addressModel_1->validate(); 
        $valid=$addressModel_2->validate() && $valid;
        $valid=$model->validate() && $valid;
 
        if($valid)
        {       
            $addressModel_1->save();
            $addressModel_2->save();
            $model->save();
        }
    }
    $this->render('update',array(
        'model'=>$model,
        'addressModel_1'=>$addressModel_1,
        'addressModel_2'=>$addressModel_2,
    ));
}

View Form

For create and update same page render as _form, from below view code you can get the idea on how to pass models and set attribute names for elements..

<div class="form">
<?php $form=$this->beginWidget('CActiveForm', array(
    'id'=>'user-form',
    'enableAjaxValidation'=>false,
)); ?>
 
    <p class="note">Fields with <span class="required">*</span> are required.</p>
 
    <div class="row">
        <?php echo $form->labelEx($model,'name'); ?>
        <?php echo $form->textField($model,'name',array('size'=>50,'maxlength'=>50)); ?>
        <?php echo $form->error($model,'name'); ?>
    </div>
    <div class="row">
        <?php echo $form->labelEx($model,'surname'); ?>
        <?php echo $form->textField($model,'surname',array('size'=>50,'maxlength'=>50)); ?>
        <?php echo $form->error($model,'surname'); ?>
    </div>
    <div class="row">
        <?php echo $form->labelEx($model,'email'); ?>
        <?php echo $form->textField($model,'email',array('size'=>50,'maxlength'=>50)); ?>
        <?php echo $form->error($model,'email'); ?>
    </div>
 
    //////////////////////////////////////////////////////
    <h2>Home Address Below</h2> 
 
    <div class="row">
        <?php echo $form->labelEx($addressModel_1,'[1]street'); ?>
        <?php echo $form->textField($addressModel_1,'[1]street'); ?>
        <?php echo $form->error($addressModel_1,'[1]street'); ?>
    </div>
    <div class="row">
        <?php echo $form->labelEx($addressModel_1,'[1]city'); ?>
        <?php echo $form->textField($addressModel_1,'[1]city'); ?>
        <?php echo $form->error($addressModel_1,'[1]city'); ?>
    </div>  
    <div class="row">
        <?php echo $form->labelEx($addressModel_1,'[1]state'); ?>
        <?php echo $form->textField($addressModel_1,'[1]state'); ?>
        <?php echo $form->error($addressModel_1,'[1]state'); ?>
    </div>
 
    //////////////////////////////////////////////////////
    <h2>Company Address Below</h2>
 
    <div class="row">
        <?php echo $form->labelEx($addressModel_2,'[2]street'); ?>
        <?php echo $form->textField($addressModel_2,'[2]street'); ?>
        <?php echo $form->error($addressModel_2,'[2]street'); ?>
    </div>
    <div class="row">
        <?php echo $form->labelEx($addressModel_2,'[2]city'); ?>
        <?php echo $form->textField($addressModel_2,'[2]city'); ?>
        <?php echo $form->error($addressModel_2,'[2]city'); ?>
    </div>  
    <div class="row">
        <?php echo $form->labelEx($addressModel_2,'[2]state'); ?>
        <?php echo $form->textField($addressModel_2,'[2]state'); ?>
        <?php echo $form->error($addressModel_2,'[2]state'); ?>
    </div>
 
    <div class="row buttons">
        <?php echo CHtml::submitButton($model->isNewRecord ? 'Create' : 'Save'); ?>
    </div>
 
<?php $this->endWidget(); ?>
 
</div><!-- form -->

Demo

The demo looks like,

All field validation

For every single entry in User table there will be two entry in Address table (home and company).

I hope this will help to many...

Total 19 comments

#17690 report it
vijay p s at 2014/07/15 02:28am
Error

I got this error when i run it..

Fatal error: Call to a member function isAttributeRequired() on a non-object in C:\xampp\htdocs\yii\framework\web\helpers\CHtml.php on line 1414
#15997 report it
kiran sharma at 2014/01/08 04:24am
@prasanth

You can put jQuery code as on tick checkbox it will copy all first address field into second (disabled) address field. Then on submit it will not give validation err.

#15996 report it
prashant.tyagi at 2014/01/08 01:56am
@Kiran

Hi thanks for article

i have to show form with home address fields and then a checkbox for mailing address 'same as above'. user fill home address and then check the checkbox as 'same as above' or user uncheck the checkbox then an another address fileds same as above in same form appears.when both address fields shows it works fine but when user ticks 'same as above'.it gives validation for hidden address fields.

i had solved this problem by putting conditional validation in controller but how can i solve it for client site validation?

#13972 report it
codesutra at 2013/07/10 01:33am
use loop for multiple instances

Thanks for nice article.

Can you please share a code for multiple instances with loop.Since, i have some confusion about this part

// Validate all three model
        $valid=$addressModel_1->validate(); 
        $valid=$addressModel_2->validate() && $valid;
        $valid=$model->validate() && $valid;

Here are some issue with the validation message for multiple instance for same model in loop.While validating multiple instance i don't get validation message for all model instance in this case.

Thanks in Advance.

#12837 report it
Nur Rochim at 2013/04/15 08:56am
Great!

great, thanks your wiki... i like it

#10484 report it
kiran sharma at 2012/10/31 04:09am
@Imre

@Imre, don't understand what you want to explain, and your suggest code can't fulfill above requirement..

#9351 report it
Imre at 2012/08/06 05:00am
would use relations more.

would simplify code using relations more.

$model=new User;  // User Model
    $addressModel_1 = new Address;  // Address Model
    $addressModel_2 = new Address;  // Address Model
 
    if(!empty($_POST)){
        // snip
 
        // if validation fails display unsaved addresses
        $model->home_address = $addressModel_1;
        $model->company_address = $addressModel_2;
    }
    this->render('create',array(
        'model'=>$model,
    ));
#9325 report it
Maurizio Domba Cerin at 2012/08/03 06:19am
@kiran123

Even with those fields as required, as I wrote before it could happen that one address gets saved when the other has a problem... so you would end with orphan addresses in that table..


Now the code is much much better...

This example is very good for simple needs, for example when there should always be only 2 addresses.

Thanks for the contribution.

Just one note for those interested... to be really, really sure that all the data will be saved in different tables... transactions should be used.

#9322 report it
kiran sharma at 2012/08/03 05:28am
@mdomba

yes that was the problem as user data could save without address data(but in my model home_address_id, company_address_id was required so it will not save data without that fields)...

Anyway the controller code is updated and made all three models validate before save, and article image made smaller.

will take care for this type of mistakes next time :)

thankx for pointing to that.

#9320 report it
Maurizio Domba Cerin at 2012/08/03 04:50am
@kiran123

Please do not post very big images as it's difficult to read the wiki and especially comments... I manually edited your comment to set a fixed width.

As for the validation maybe it works for you but then you are using different or additional code as I really don't see how it could work as expected from your code above.

For example: following your code in Create Controller... you are first calling $addressModel_1->save(); so this address will be saved even if there will be validation errors for the second address or user.

Another example: you have the line if($model->save()) redirect... so even if there are validation errors for any or both of the addresses the user would be saved and the user redirected.

All this of course if the validation is done on the server side as the enableAjaxValidation is set to false above... if that would be set to true... than the validation would occur on every field input.

#9312 report it
kiran sharma at 2012/08/03 01:27am
@mdomba , @Francois.Gannaz

@mdomba , @Francois.Gannaz thankx for comment,

The validation code is also working fine, as the controller get post data it will check validation on xxxModel->save method(validation onclick submit button).

and all validation is working fine as i am providing two screenshot below of create and update page,

All field validation

Partial field validation

#9292 report it
François Gannaz at 2012/08/02 04:27am
"Collecting Tabular Input"

The official tutorial for Yii, called "The Definitive Guide to Yii", has a chapter on this subject: "Working with forms: Collecting Tabular Input".

The method used in the official tutorial can attach an unlimited number of objects. It is far more compact than the method presented here. For now, I would not recommend to follow this wiki page. Yet it would be a good idea to rewrite it so that it extends the tutorial, with more details and a link towards the chapter, but this rewrite would be hard work and would need a better comprehension of Yii's mecanisms.

BTW, there's a big bug in the controller shown above: no validation is performed. More exactly, the data will be partially saved, with no error message, when an address is not valid. The worse part is that it will crash when you try to update it (findByPk() will return false, though it will be used as an object).

#9289 report it
Maurizio Domba Cerin at 2012/08/02 03:33am
Validation errors?

Problem here is not the database pattern as much as the code you suggested above.

I understand you want to provide a very simple code for new users to understand, but if they try to use the above code they will get more problems than reading a more complex example.

Check a bit your code and see what happens if any of the models gets a validation error?

#9278 report it
kiran sharma at 2012/08/01 09:40am
@iivano71

you don't see the good point? ( because you haven't see the good point, you just focused on tightly normalized pattern ) how you will be achive this functionality with tightly normalized pattern? this fulfill all functionalities as i created database as i don't use that much tightly normalized pattern, what will from your side?

#9277 report it
Igor Ivanovic at 2012/08/01 09:32am
I don`t see the good point ?

Every good pattern is good to share, but teaching programmer to do something wrong is bad because you need to write something twice. If you lose time you lose money.

#9276 report it
kiran sharma at 2012/08/01 09:22am
@iivano71

yes my friend, you might be right as you watch this article as database normalization point of view and as you compare with search performance.

but currently for demo purpose i am not using that much normalization, and i created demo app just for tutorials...

and it will be definitely useful to users wheather you give plus point or minus ;)

and as you say (Useless tutorial), one can not underestimate this just for tightly normalization issue.

cheers..

#9275 report it
Igor Ivanovic at 2012/08/01 09:13am
Useless tutorial

This is design pattern fail, because you need to use 2 joins of same table to get data of company address and home address, database over kill and search performance issues.

If you use this database design pattern you use 3 tables,

User Table id name surname email

User To Address Table user_id address_id

Address Table id street city state

#9270 report it
kiran sharma at 2012/08/01 07:18am
@raheelk2k

yes i also just think so... i mark that newbie find some difficulties on startup so currently my focus is to help newbie as currently much growth in new users.

#9269 report it
raheelk2k at 2012/08/01 07:12am
Nice

Absolute newbies can learn something from this post. Simple recipes like this shall keep coming to encourage more developers to start working on this amazing framework.

Leave a comment

Please to leave your comment.

Write new article