How to use a single form to collect data for two or more models?

52 followers

Assume we want to use a single HTML form to collect input for both model A and model B, and we want to display input errors (if any) in the same error summary box. We can define the following action code:

public function actionCreate()
{
    $a=new A;
    $b=new B;
    if(isset($_POST['A'], $_POST['B']))
    {
        // populate input data to $a and $b
        $a->attributes=$_POST['A'];
        $b->attributes=$_POST['B'];
 
        // validate BOTH $a and $b
        $valid=$a->validate();
        $valid=$b->validate() && $valid;
 
        if($valid)
        {
            // use false parameter to disable validation
            $a->save(false);
            $b->save(false);
            // ...redirect to another page
        }
    }
 
    $this->render('create', array(
        'a'=>$a,
        'b'=>$b,
    ));
}

To add these new fields to your Create form, add your 2nd table fields no stored in a 2nd model.

A's create.php:

<?php echo $this->renderPartial('_form', array('a'=>$a, 'b'=>$b)); ?>

You usually place the specific fileds in the _form.php file if you are working off Gii created CRUD files.

<?php echo CHtml::beginForm(); ?>
 
    <?php echo CHtml::errorSummary(array($a,$b)); ?>
 
    <!-- ...input fields for $a, $b... -->
 
    <div class="row">
        <?php echo $form->labelEx($a,'a_field'); ?>
        <?php echo $form->textField($a,'a_field'); ?>
        <?php echo $form->error($a,'a_field'); ?>
    </div>
 
    <div class="row">
        <?php echo $form->labelEx($b,'b_field'); ?>
        <?php echo $form->textField($b,'b_field'); ?>
        <?php echo $form->error($b,'b_field'); ?>
    </div>
 
 
<?php echo CHtml::endForm(); ?>

The above approach can also be used if we have more than two models to deal with.

Links ΒΆ

Chinese version
Ajax Validation Version

Total 8 comments

#8130 report it
cnaboul at 2012/05/12 05:32am
Tks a lot

You are the very best ... I user this to translate all my website into any language.

#7771 report it
boaz at 2012/04/17 02:57pm
Another solution

is described here: http://www.yiiframework.com/forum/index.php/topic/30928-validation-perform-if-not-in-specific-scenario/

#7767 report it
boaz at 2012/04/17 08:51am
gerhard@ecolar.co.za: consider using scenarios

gerhard@ecolar.co.za: consider limiting the 'required' rule on $b for specific scenario, but not the 'initial creation scenario'. For example, consider having user and profile AR/tables. On registration, you need date to be fed into both. Yet, as already noted in the comments before this one, that will be a problem if 'profile' is validated before is has the user_id filled in. Yet, you need the 'user' to be saved before in order to get this id before you even attempt initial validation of 'profile' (just as you noted...).

So, if you limit the 'require' rule on the 'user_id' for specific scenarios but NOT the 'registration' scenario, then you can get along with this (validate both, save 'user', grab its user_id, save 'profile'). this has the downside of needing to put all scenarios in the 'required' rule for 'user_id', and maintaining that.

Another option I just thought of is to note in the user's session (or your favorite persistent per-user storage) the fact that 'user' object has been created already and not attempt to save it each time 'profile' objects failed validation (after creation of 'user' row in the DB).

#7270 report it
gerhard@ecolar.co.za at 2012/03/08 10:03am
$b FK problem

Hi daniel.odonnell

I have a problem with your second method, because it keeps on creating $a records in the database until $b validates.

Your first method is better because it does not create any records until both $a and $b validate. Of course the required FK is then a problem.

Any new ideas? Maybe transactions?

#7206 report it
daniel.odonnell at 2012/03/03 08:05pm
If $b has FK from $a's auto_increment PK (additional comment)

jmcmasterj's method works, but not if $b's FK is required (which is of course likely).

The problem is the validation. If you try to validate both $a and $b before saving you'll get an error.

// validate BOTH $a and $b
$valid=$a->validate();
$valid=$b->validate() && $valid; // $b won't be valid because the FK hasn't been set yet.
if($valid)  
{  
    if($a->save(false))  
    {  
        $b->a_id = $a->id;  
        $b->save(false);  
        $this->redirect(array('somewhere'));  
    }  
}

If the FK is required, you need to change that part of the controller. Here's one way of doing it:

$valid=$a->validate();
 
if($valid)  
     {  
          if($a->save(false))  
                {  
                    $b->a_id = $a->id;  
                    if ($b->save()) // not false because it hasn't been validated
                    {  
                       $this->redirect(array('somewhere'));
                    }
    }
#4754 report it
pligor at 2011/08/11 08:58am
a question on a little bit more complex implementation

Please check this question which is directly related with the solution proposed in this wiki: http://www.yiiframework.com/forum/index.php?/topic/22539-passing-posted-data-from-a-form-in-further-steps/

In case this comment is not accepted please ask me to remove it. I just wanted to link the followers of this wiki to my forum question

#4284 report it
nafa at 2011/06/21 11:55pm
help..

i already try to follow this wiki, but unfortunately its not work for me.. is there anyone can help me. thanks.. =) this is my code..

KategoriController

public function actionCreate()
    {
        $model=new Kategori;
                $sub=new Sub;

        if(isset($_POST['Kategori'], $POST['Sub']))
        {
                    // populate input data to $a and $b
                    $model->attributes=$_POST['Kategori'];
                    $sub->attributes=$_POST['Sub'];

                    // validate BOTH $a and $b
                    $valid=$model->validate();
                    $valid=$sub->validate() && $valid;

                    if($valid)
                    {
                        if($model->save(false))
                        {
                            $sub->kategori_id = $model->id;
                            $sub->save(false);
                            $this->redirect(array('view','id'=>$model->id));
                        }
                    }
                }

        $this->render('create', array(
                    'model'=>$model,
                    'sub'=>$sub,
                ));
    }

Kategori create.php

<?php echo $this->renderPartial('_form', array('model'=>$model, 'sub'=>$sub)); ?>

kategori _form.php

<div class="form">

<?php $form=$this->beginWidget('CActiveForm', array(
    'id'=>'kategori-form',
    'enableAjaxValidation'=>true,
)); ?>

    <p class="note">Fields with <span class="required">*</span> are required.</p>

    <?php echo $form->errorSummary(array($model, $sub)); ?>

    <div class="row">
        <?php echo $form->labelEx($model,'kategori'); ?>
        <?php echo $form->textField($model,'kategori',array('size'=>45,'maxlength'=>45)); ?>
        <?php echo $form->error($model,'kategori'); ?>
    </div>

        <div class="row">
        <?php echo $form->labelEx($sub,'sub'); ?>
        <?php echo $form->textField($sub,'sub',array('size'=>45,'maxlength'=>45)); ?>
        <?php echo $form->error($sub,'sub'); ?>
    </div>

    <div class="row">
                <?php echo $form->labelEx($sub,'kategori_id'); ?>
        <?php echo $form->textField($sub,'kategori_id'); ?>
                <?php echo $form->error($sub,'kategori_id'); ?>
    </div>

    <div class="row buttons">
        <?php echo CHtml::submitButton($model->isNewRecord ? 'Create' : 'Save'); ?>
    </div>

<?php $this->endWidget(); ?>

</div><!-- form -->
#946 report it
jmcmasterj at 2010/01/22 05:07pm
If $b has FK from $a's auto_increment PK
if($valid)  
{  
    if($a->save(false))  
    {  
        $b->a_id = $a->id;  
        $b->save(false);  
        $this->redirect(array('somewhere'));  
    }  
}

Leave a comment

Please to leave your comment.

Write new article