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:
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.
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.
Total 10 comments
Thank you for this wonderful article. I was stuck in this problem itself and because of this I could manage.
Regards,
I would personally go with transaction.
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
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 ;)
You are the very best ... I user this to translate all my website into any language.
is described here: http://www.yiiframework.com/forum/index.php/topic/30928-validation-perform-if-not-in-specific-scenario/
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).
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?
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.
If the FK is required, you need to change that part of the controller. Here's one way of doing it:
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
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 -->Leave a comment
Please login to leave your comment.