When i had created this functionality then i found some difficulties and not got much idea from wiki and forums. so, i think this will be useful for newbie users and save time of other developers when create related functionality. I refer Collecting Tabular Input tutorial but not got clear idea for create/update.
Here in my demo i had two tables as, User(table) can have multiple address ( home, company ) which comes from Address(table)
Model generated by gii and no any change in model.
User Table ---id ---name ---surname ---email ---home_address_id ( data refer to Address table ) ---company_address_id ( data refer to Address table ) Address Table ---id ---street ---city ---state
Developer can also use loop for multiple instances here in my demo i explained with two.
public function actionCreate() { $model=new User; // User Model $addressModel_1 = new Address; // Address Model $addressModel_2 = new Address; // Address Model if(!empty($_POST)) { // Set attribute for home address $addressModel_1->attributes=$_POST['Address'][1]; // Set attribute for company address $addressModel_2->attributes=$_POST['Address'][2]; // Set attribute for user data $model->attributes=$_POST['User']; // Validate all three model $valid=$addressModel_1->validate(); $valid=$addressModel_2->validate() && $valid; $valid=$model->validate() && $valid; if($valid) { $addressModel_1->save(); $homeAddressId = $addressModel_1->id; $addressModel_2->save(); $companyAddressId = $addressModel_2->id; // Set saved address as user home id $model->home_address_id = $homeAddressId; // Set saved address as user company id $model->company_address_id = $companyAddressId; $model->save(); $this->redirect(array('view','id'=>$model->id)); } } $this->render('create',array( 'model'=>$model, 'addressModel_1'=>$addressModel_1, 'addressModel_2'=>$addressModel_2, )); }
public function actionUpdate($id) { // Load user data in model $model=User::model()->findByPk($id); // Load address data in addressModel_1 $addressModel_1=Address::model()->findByPk($model->home_address_id); // Load address data in addressModel_2 $addressModel_2=Address::model()->findByPk($model->company_address_id); if(!empty($_POST)) { // Set attribute for home address $addressModel_1->attributes=$_POST['Address'][1]; // Set attribute for company address $addressModel_2->attributes=$_POST['Address'][2]; // Set attribute for user data $model->attributes=$_POST['User']; // Validate all three model $valid=$addressModel_1->validate(); $valid=$addressModel_2->validate() && $valid; $valid=$model->validate() && $valid; if($valid) { $addressModel_1->save(); $addressModel_2->save(); $model->save(); } } $this->render('update',array( 'model'=>$model, 'addressModel_1'=>$addressModel_1, 'addressModel_2'=>$addressModel_2, )); }
For create and update same page render as _form, from below view code you can get the idea on how to pass models and set attribute names for elements..
<div class="form"> <?php $form=$this->beginWidget('CActiveForm', array( 'id'=>'user-form', 'enableAjaxValidation'=>false, )); <p class="note">Fields with <span class="required">*</span> are required.</p> <div class="row"> <?php echo $form->labelEx($model,'name'); <?php echo $form->textField($model,'name',array('size'=>50,'maxlength'=>50)); <?php echo $form->error($model,'name'); </div> <div class="row"> <?php echo $form->labelEx($model,'surname'); <?php echo $form->textField($model,'surname',array('size'=>50,'maxlength'=>50)); <?php echo $form->error($model,'surname'); </div> <div class="row"> <?php echo $form->labelEx($model,'email'); <?php echo $form->textField($model,'email',array('size'=>50,'maxlength'=>50)); <?php echo $form->error($model,'email'); </div> ////////////////////////////////////////////////////// <h2>Home Address Below</h2> <div class="row"> <?php echo $form->labelEx($addressModel_1,'[1]street'); <?php echo $form->textField($addressModel_1,'[1]street'); <?php echo $form->error($addressModel_1,'[1]street'); </div> <div class="row"> <?php echo $form->labelEx($addressModel_1,'[1]city'); <?php echo $form->textField($addressModel_1,'[1]city'); <?php echo $form->error($addressModel_1,'[1]city'); </div> <div class="row"> <?php echo $form->labelEx($addressModel_1,'[1]state'); <?php echo $form->textField($addressModel_1,'[1]state'); <?php echo $form->error($addressModel_1,'[1]state'); </div> ////////////////////////////////////////////////////// <h2>Company Address Below</h2> <div class="row"> <?php echo $form->labelEx($addressModel_2,'[2]street'); <?php echo $form->textField($addressModel_2,'[2]street'); <?php echo $form->error($addressModel_2,'[2]street'); </div> <div class="row"> <?php echo $form->labelEx($addressModel_2,'[2]city'); <?php echo $form->textField($addressModel_2,'[2]city'); <?php echo $form->error($addressModel_2,'[2]city'); </div> <div class="row"> <?php echo $form->labelEx($addressModel_2,'[2]state'); <?php echo $form->textField($addressModel_2,'[2]state'); <?php echo $form->error($addressModel_2,'[2]state'); </div> <div class="row buttons"> <?php echo CHtml::submitButton($model->isNewRecord ? 'Create' : 'Save'); </div> <?php $this->endWidget(); </div><!-- form -->
The demo looks like,

For every single entry in User table there will be two entry in Address table (home and company).
I hope this will help to many...
Total 15 comments
great, thanks your wiki... i like it
@Imre, don't understand what you want to explain, and your suggest code can't fulfill above requirement..
would simplify code using relations more.
Even with those fields as required, as I wrote before it could happen that one address gets saved when the other has a problem... so you would end with orphan addresses in that table..
Now the code is much much better...
This example is very good for simple needs, for example when there should always be only 2 addresses.
Thanks for the contribution.
Just one note for those interested... to be really, really sure that all the data will be saved in different tables... transactions should be used.
yes that was the problem as user data could save without address data(but in my model home_address_id, company_address_id was required so it will not save data without that fields)...
Anyway the controller code is updated and made all three models validate before save, and article image made smaller.
will take care for this type of mistakes next time :)
thankx for pointing to that.
Please do not post very big images as it's difficult to read the wiki and especially comments... I manually edited your comment to set a fixed width.
As for the validation maybe it works for you but then you are using different or additional code as I really don't see how it could work as expected from your code above.
For example: following your code in Create Controller... you are first calling $addressModel_1->save(); so this address will be saved even if there will be validation errors for the second address or user.
Another example: you have the line if($model->save()) redirect... so even if there are validation errors for any or both of the addresses the user would be saved and the user redirected.
All this of course if the validation is done on the server side as the enableAjaxValidation is set to false above... if that would be set to true... than the validation would occur on every field input.
@mdomba , @Francois.Gannaz thankx for comment,
The validation code is also working fine, as the controller get post data it will check validation on xxxModel->save method(validation onclick submit button).
and all validation is working fine as i am providing two screenshot below of create and update page,
The official tutorial for Yii, called "The Definitive Guide to Yii", has a chapter on this subject: "Working with forms: Collecting Tabular Input".
The method used in the official tutorial can attach an unlimited number of objects. It is far more compact than the method presented here. For now, I would not recommend to follow this wiki page. Yet it would be a good idea to rewrite it so that it extends the tutorial, with more details and a link towards the chapter, but this rewrite would be hard work and would need a better comprehension of Yii's mecanisms.
BTW, there's a big bug in the controller shown above: no validation is performed. More exactly, the data will be partially saved, with no error message, when an address is not valid. The worse part is that it will crash when you try to update it (
findByPk()will returnfalse, though it will be used as an object).Problem here is not the database pattern as much as the code you suggested above.
I understand you want to provide a very simple code for new users to understand, but if they try to use the above code they will get more problems than reading a more complex example.
Check a bit your code and see what happens if any of the models gets a validation error?
you don't see the good point? ( because you haven't see the good point, you just focused on tightly normalized pattern ) how you will be achive this functionality with tightly normalized pattern? this fulfill all functionalities as i created database as i don't use that much tightly normalized pattern, what will from your side?
Every good pattern is good to share, but teaching programmer to do something wrong is bad because you need to write something twice. If you lose time you lose money.
yes my friend, you might be right as you watch this article as database normalization point of view and as you compare with search performance.
but currently for demo purpose i am not using that much normalization, and i created demo app just for tutorials...
and it will be definitely useful to users wheather you give plus point or minus ;)
and as you say (Useless tutorial), one can not underestimate this just for tightly normalization issue.
cheers..
This is design pattern fail, because you need to use 2 joins of same table to get data of company address and home address, database over kill and search performance issues.
If you use this database design pattern you use 3 tables,
User Table id name surname email
User To Address Table user_id address_id
Address Table id street city state
yes i also just think so... i mark that newbie find some difficulties on startup so currently my focus is to help newbie as currently much growth in new users.
Absolute newbies can learn something from this post. Simple recipes like this shall keep coming to encourage more developers to start working on this amazing framework.
Leave a comment
Please login to leave your comment.