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).
$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.
$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 10 comments
Checkout my blog post to see complete solution to this problem, I'm sure you will love it!
http://tahiryasin.wordpress.com/2013/05/06/how-to-save-multiple-related-models-in-yii-complete-solution/
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.
now in the protected method.
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
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.. ;)
i use this method:
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.
Thank you very much redguy =)
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?
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 login to leave your comment.