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).
<?php $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.
<?php $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,
));
}
without db transaction it doesn't make much sense
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.
If email is not valid ...
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?
it should rather be
$model = new Users; if (isset($_POST['Users'])) { $transaction=Yii::app()->db->beginTransaction(); try { $model->attributes = $_POST['Users']; if ($model->save()) { $modelEmail = new Emails; $modelEmail->attributes = $_POST['Emails']; $modelEmail->iduser = $model->id; if ($modelEmail->save()) { $transaction->commit(); $this->redirect(array('view', 'id' => $model->id)); } } //something went wrong... $transaction->rollBack(); } catch(Exception $e) { // an exception is raised if a query fails //something was really wrong - exception! $transaction->rollBack(); //you should do sth with this exception (at least log it or show on page) Yii::log( 'Exception when saving data: ' . $e->getMessage(), CLogger::LEVEL_ERROR ); } }
Thank you very much =)
Thank you very much redguy =)
might be better to redirect to edit if you don't want transactions
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.
Validations first approach
i use this method:
public function actionCreate() { $model = new Users; if (isset($_POST['Users'])) { //form has been submited $model->attributes = $_POST['Users']; $modelEmail = new Emails; $modelEmail->attributes = $_POST['Emails']; if ($model->validate() && $modelEmail->validate()) { $modelEmail->save(false); //second validation pass is not needed $modelEmail->iduser = $model->id; $modelEmail->save(false); $this->redirect(array('view', 'id' => $model->id)); } } $this->render('create', array( 'model' => $model, )); }
@mucha1306: this should work, but...
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.. ;)
we have validation scenarios
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
using ajax validation to reduce multiply "if statement"
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.
public function actionCreate $user = new User; $email = new Email ; $this->performAjaxValidation($user,$email); if(isset($_POST['User'],$_POST['Email'] )){ $user->attributes = $_POST['User']; $email->attributes = $_POST['Email]; //save the models and redirect; } $this->render('yourViewFile',array( 'userModel'=>$user,'emailModel'=>$email ));
now in the protected method.
protected function performAjaxValidation($user,$email) { if(isset($_POST['ajax'])) { //you pass pass multiply models into the CactiveForm::validate echo CActiveForm::validate(array($user,$email)); Yii::app()->end(); }
How to save multiple related models in yii [Complete Solution]
Checkout my blog post to see complete solution to this problem, I'm sure you will love it!
http://scriptbaker.com/how-to-save-multiple-related-models-in-yii-complete-solution/
simple, short and nice example
this is simple, short code, nice and working example -
http://www.dukaweb.net/2013/11/update-two-models-with-one-view-yii-tutorial.html
Create is ok but what about update
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.