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

81 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 ΒΆ

Ajax Validation Version

Total 15 comments

#16098 report it
Trydents at 2014/01/19 08:29pm
the relation is the problem

C isn't problem

the relation A with a record of B and the relation of C with another B record

#16096 report it
sensorario at 2014/01/19 05:25pm
Three models?

Just add C to the code.

#16094 report it
Trydents at 2014/01/19 03:07pm
one question

But if

A has a ralation with B

And A has a relation with C but this last one has another relation with B

how can i build the action create??

#14865 report it
Shahcheraghean at 2013/09/17 12:32am
How to do so for two or more action of one controller?

So Thank`s for this article. according to above, How to use 2 or more action of one controller in just one view file?

#14297 report it
ezekielnoob at 2013/08/02 02:58pm
How bout Bitwise operators?
if($a->validate() & $b->validate());

in this case php won't be using its typical short-circuit

#13263 report it
Sankalp Singha at 2013/05/19 11:21am
Thank you so much!

Thank you for this wonderful article. I was stuck in this problem itself and because of this I could manage.

Regards,

#9743 report it
Elecen at 2012/09/07 06:42pm
4 ways to save $a and $b

I would personally go with transaction.

if ($a->validate()) {
  $transaction = Yii::app()->db->beginTransaction();
  $success = $a->save(false);
  $b->fk_a = $a->id_a;
  $success = $success ? $b->save(false) : $success;
  if (!success)
    $transaction->commit();
  else
    $transaction->rollBack();
}

Alternately if you can't use transactions you can save $a save $b and if $b fails delete $a (I don't like it though 'cause it abuses one id if you're using AUTO_INCREMENT

As a third option and best if you can't use transactions I would go with scenarios

if ($a->validate()) {
  $b->scenario = 'errors_check';
  if ($b->validate()) {
    $a->save(false);
    $b->fk_a = $a->id_a;
    $b->save(false);
  }
}

Of course errors_check scenario should be valid on all rules except your foreign key rule.

Last but not least you can always create a FormModel. Of course in your case it will duplicate lots of code (bad) but for more complex rules sets with more AR classes used on a form, FormModels are quite cool (FormModels are Models that don't have a physical table in the database ;)

#8130 report it
nabil abdouali 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 Liebenberg 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
Dan O&#39;Donnell 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
  • Written by: qiang
  • Updated by: Yang He
  • Category: Tutorials
  • Yii Version: 1.1
  • Votes: +107 / -1
  • Viewed: 125,718 times
  • Created on: Feb 27, 2009
  • Last updated: Jun 29, 2012
  • Tags: model validation