Using multiple models (Intermediate)
This recipe explains how to use multiple models within a single form.
Getting ready
We'll use the form we have developed in the Building multipage forms recipe.
Create a new table order. This will represent the orders placed by the users.
CREATE TABLE IF NOT EXISTS 'order' ( 'id' int(11) NOT NULL AUTO_INCREMENT, 'user_id' int(11) NOT NULL, 'product_name' varchar(255) NOT NULL, 'quantity' int(11) NOT NULL, PRIMARY KEY ('id'), KEY 'user_id' ('user_id') ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
Create a model for this table using the Gii tool.
How to do it...
Let's add this new model to the actionCreate
method developed for multiple forms. We will need to add one more step/page to the action as follows:
... $this->checkPageState($model, Yii::app()->request->getPost('User',array())); $this->setPageState('order', Yii::app()->request->getPost('Order', array()));
... ... if($model->validate()) { $view = '_page4'; $order = new Order(); $order->attributes = $this->getPageState( 'order', array() ); }
... else if(isset($_POST['submit'])) { $model = new User('page3'); $order = new Order(); $model->attributes = $this->getPageState( 'page', array() ); $order->attributes = $_POST['Order']; if($model->validate()) { $model->save(); $order->user_id = $model->id; if($order->validate()) { $order->save(); $this->redirect(array('view', 'id'=>$model->id)); } } $view = '_page4'; }
Next, create a view for _page4
.
... <div class="row"> <?php echo $form->labelEx($order,'product_name'); ?> <?php echo $form->textField($order,'product_name'); ?> <?php echo $form->error($order,'product_name'); ?> </div> <div class="row"> <?php echo $form->labelEx($order,'quantity'); ?> <?php echo $form->numberField ($order,'quantity'); ?> <?php echo $form->error($order,'quantity'); ?> </div> <div class="row buttons"> <?php echo CHtml::submitButton('Back', array( 'name'=>'page3')); ?> <?php echo CHtml::submitButton('submit', array( 'name'=>'submit')); ?> </div> ...
Change the submit button in the view of _page3
to the following:
<?php echo CHtml::submitButton('Next', array( 'name'=>'page4' )); ?>
We are done; try the new form in your browser. It will look something like the following screenshot:
How it works...
We introduced an order model in the fourth page of our multipage form with the following code:
if($model->validate()) { $view = '_page4'; $order = new Order(); $order->attributes = $this->getPageState('order', array()); }
First we check if the data we received in the third step for the model User
is valid. If the model validates, we set our view page to _page4
. Then we create a new model named Order
. On the third line, we simply check if there's any saved state for the model Order
; this is useful if the user moves back after filling the data of Order
.
In the same way, we have added some code in the third step to check if the user has moved back from _page4
after filling the form fields. So we save the current state of _page4
. Notice the use of Yii::app()->request->getPost('User', array())
line of code. The getPost()
method is used to get the named POST
value. The first parameter is the name of the POST
field and the second parameter is the default value if the POST
field with the given name is not set. Similarly, you can use the getParam()
method to get the parameters of GET
.
Next, in the final step, we assign the saved state to the User
model, and $_POST
data to the Order
model. As we have already loaded a save state on the page, $_POST
will hold the updated data (if any) or the data in that saved state.
Then we validate the User
model with if($model->validate())
. After successful validation, we save the User
model and assign the user's ID to the Order
model with $order->user_id = $model->id
. Then we save the Order
model too. If it's successful, we redirect to view the page; else, we redirect to the fourth page to solve the form errors.
There's more...
We could use the transactional feature of the database while storing the data; that is, if the saving of the order model fails, we could discard the User model entirely by rolling back the transaction as follows:
if($model->validate()) { $transaction = Yii::app()->db->beginTransaction(); try { $model->save(); $order->user_id = $model->id; if(!$order-> save()) throw new Exception('Order data invalid'); $transaction->commit(); $this->redirect(array('view', 'id'=>$model->id)); } catch(Exception e) { $transaction->rollBack(); $view = 'page4'; } }
For more details on database transaction visit the website en.wikipedia.org/wiki/Database_transaction.
Transactional queries support in Yii can be looked up on the website http://www.yiiframework.com/doc/guide/1.1/en/database.dao#using-transactions.