When developing a Yii 1.1 application, you may need to save data from multiple related models with a single form submission. A common example is a parent-child relationship, where one parent record can have multiple child records.
For example, imagine a form where a user enters information about a father and then adds one or more children. Instead of asking the user to save the father first and then save each child separately, you can provide a single Save button that saves the parent and all related child records together.
This guide explains how to save multiple related models in Yii using Yii ActiveRecord, a HAS_MANY relationship, and the ESaveRelatedBehavior extension.
Scenario: Saving a Parent and Multiple Child Models
Suppose you have two related database tables: a fathers table and a children table.
The desired user experience is:
- The user enters the parent model information.
- The user enters information for one or more child models.
- The user can dynamically add additional child fields.
- The user can remove child fields before submitting the form.
- The user clicks the Save button only once.
- Yii saves the parent record and its related child records together.
This is a common requirement when working with Yii nested forms, tabular input, and ActiveRecord relationships.
Traditional Approach: Save Models Inside a Transaction
One way to save multiple related models is to manually start a database transaction, save the parent model, retrieve its primary key, assign that key to each child model, and then save the child models inside a loop.
$transaction = Yii::app()->db->beginTransaction();try { if (!$father->save()) { throw new Exception('Unable to save father.'); } foreach ($children as $child) { $child->father_id = $father->id; if (!$child->save()) { throw new Exception('Unable to save child.'); } } $transaction->commit();} catch (Exception $e) { $transaction->rollback();}
This approach can work well when you need complete control over the saving process. However, if your application frequently needs to save related ActiveRecord models, repeating this logic can make controllers longer and harder to maintain.
A Reusable Approach with ESaveRelatedBehavior
Yii has a number of extensions that address common ActiveRecord requirements. One option for Yii 1.1 is ESaveRelatedBehavior, developed by sluderitz.
The extension allows you to save related HAS_MANY and MANY_MANY ActiveRecord models together with the main model. It provides methods such as saveWithRelated() and saveRelated(). The extension also processes the save operation inside a transaction.
For this example, we will use a HAS_MANY relationship between Father and Child.
Database Table Structure
Our example uses two tables. The children.father_id column acts as the foreign key that connects every child record to its parent.
CREATE TABLE IF NOT EXISTS `fathers` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=latin1;CREATE TABLE IF NOT EXISTS `children` ( `id` int(11) NOT NULL AUTO_INCREMENT, `father_id` int(11) NOT NULL, `name` varchar(255) NOT NULL, `age` int(11) NOT NULL, PRIMARY KEY (`id`), KEY `father_id` (`father_id`)) ENGINE=InnoDB DEFAULT CHARSET=latin1;ALTER TABLE `children`ADD CONSTRAINT `children_ibfk_1`FOREIGN KEY (`father_id`)REFERENCES `fathers` (`id`)ON DELETE CASCADE;
Using a foreign key establishes the database relationship between the parent and child records. In Yii ActiveRecord, this type of one-to-many relationship is represented using HAS_MANY on the parent model and BELONGS_TO on the child model.
Father Model
The Father model defines a HAS_MANY relationship with the Child model.
<?phpclass Father extends CActiveRecord{ public static function model($className=__CLASS__) { return parent::model($className); } public function tableName() { return 'fathers'; } public function rules() { return array( array('name', 'required'), array('name', 'length', 'max' => 255), array('id, name', 'safe', 'on' => 'search'), ); } public function relations() { return array( 'children' => array( self::HAS_MANY, 'Child', 'father_id' ), ); } public function attributeLabels() { return array( 'id' => 'ID', 'name' => 'Name', ); } public function behaviors() { return array( 'ESaveRelatedBehavior' => array( 'class' => 'application.components.ESaveRelatedBehavior' ) ); }}
The important part is the relations() method:
'children' => array( self::HAS_MANY, 'Child', 'father_id'),
This tells Yii that one Father record can have multiple Child records. Yii ActiveRecord supports BELONGS_TO, HAS_ONE, HAS_MANY, and MANY_MANY relationship types.
Child Model
The child model uses a BELONGS_TO relationship because each child belongs to one father.
<?phpclass Child extends CActiveRecord{ public static function model($className=__CLASS__) { return parent::model($className); } public function tableName() { return 'children'; } public function rules() { return array( array('father_id, name, age', 'required'), array('father_id, age', 'numerical', 'integerOnly' => true), array('name', 'length', 'max' => 255), array('id, father_id, name, age', 'safe', 'on' => 'search'), ); } public function relations() { return array( 'father' => array( self::BELONGS_TO, 'Father', 'father_id' ), ); } public function attributeLabels() { return array( 'id' => 'ID', 'father_id' => 'Father', 'name' => 'Name', 'age' => 'Age', ); }}
Saving the Parent and Child Models
Once the relationship is configured and the behavior has been added to the parent model, the controller can assign the submitted child data to the children relation and call saveWithRelated().
public function actionCreate(){ $model = new Father; $this->performAjaxValidation($model); if (isset($_POST['Father'])) { $model->attributes = $_POST['Father']; if (isset($_POST['Child'])) { $model->children = $_POST['Child']; } if ($model->saveWithRelated('children')) { $this->redirect(array( 'view', 'id' => $model->id )); } else { $model->addError( 'children', 'Error occurred while saving children.' ); } } $this->render('create', array( 'model' => $model, ));}
The key line is:
$model->saveWithRelated('children');
The extension handles the parent and specified related models as part of the saving process. For a HAS_MANY relation, the related data can be supplied as an array of model objects or as arrays containing the attributes of the related records.
Updating Existing Related Models
The same approach can be used when updating an existing parent model.
public function actionUpdate($id){ $model = $this->loadModel($id); if (isset($_POST['Father'])) { $model->attributes = $_POST['Father']; if (isset($_POST['Child'])) { $model->children = $_POST['Child']; } if ($model->saveWithRelated('children')) { $this->redirect(array( 'view', 'id' => $model->id )); } else { $model->addError( 'children', 'Error occurred while saving children.' ); } } $this->render('update', array( 'model' => $model, ));}
One important consideration is that the extension's standard update behavior handles existing related records as part of the relationship save process. If you need to preserve existing related records and append new records instead, the extension provides an append option.
Creating Dynamic Child Fields with AJAX
In many Yii applications, users need to add an unknown number of child records. Instead of displaying a fixed number of child fields, you can use JavaScript and AJAX to dynamically add child forms.
The following controller action renders a child form based on an index:
public function actionLoadChildByAjax($index){ $model = new Child; $this->renderPartial( 'child/_form', array( 'model' => $model, 'index' => $index, ), false, true );}
The parent form can then request a new child form whenever the user clicks Add Child.
Father Form
<?php$form = $this->beginWidget('CActiveForm', array( 'id' => 'father-form', 'focus' => array($model, 'name'), 'enableClientValidation' => true, 'enableAjaxValidation' => true,)); ?><p class="note"> Fields with <span class="required">*</span> are required.</p><?php echo $form->errorSummary($model); ?><div class="row"> <?php echo $form->labelEx($model, 'name'); ?> <?php echo $form->textField( $model, 'name', array( 'size' => 60, 'maxlength' => 255 ) ); ?> <?php echo $form->error($model, 'name'); ?></div><?php echo CHtml::link( 'Add Child', '#', array('id' => 'loadChildByAjax')); ?><div id="children"></div><div class="row buttons"> <?php echo CHtml::submitButton( $model->isNewRecord ? 'Create' : 'Save' ); ?></div><?php $this->endWidget(); ?>
Child Form
The child form uses an index so Yii can receive multiple child records from the same HTML form submission.
<div class="crow" style="margin-bottom:20px; width:100%; clear:left;"> <div class="row" style="width:200px; float:left;"> <?php echo CHtml::activeLabelEx( $model, '[' . $index . ']name' ); ?> <?php echo CHtml::activeTextField( $model, '[' . $index . ']name', array( 'size' => 20, 'maxlength' => 255 ) ); ?> <?php echo CHtml::error( $model, '[' . $index . ']name' ); ?> </div> <div class="row" style="width:200px; float:left;"> <?php echo CHtml::activeLabelEx( $model, '[' . $index . ']age' ); ?> <?php echo CHtml::activeTextField( $model, '[' . $index . ']age' ); ?> <?php echo CHtml::error( $model, '[' . $index . ']age' ); ?> </div> <div class="row" style="width:100px; float:left;"> <br> <?php echo CHtml::link( 'Delete', '#', array( 'onclick' => 'deleteChild(this); return false;' ) ); ?> </div></div>
Why Use This Approach?
Saving related models through a reusable behavior can make your Yii 1.1 application easier to maintain when the same parent-child saving pattern appears in multiple places.
- Less repetitive controller code: You do not have to manually write the same save loop for every parent-child form.
- Cleaner ActiveRecord workflow: Related data is assigned through the model relationship.
- Transaction support: ESaveRelatedBehavior processes the save operation within a transaction.
- HAS_MANY support: Multiple child records can be submitted with one parent record.
- MANY_MANY support: The extension also supports many-to-many relationships.
- Validation support: Related models are validated as part of the saving workflow.
According to the Yii extension documentation, ESaveRelatedBehavior supports both HAS_MANY and MANY_MANY relations and can accept related data as arrays or model objects.
Yii Multiple Related Models: Important Considerations
Validate Child Models
Do not validate only the parent model. Child model validation is equally important when accepting tabular form input from users.
Use Database Transactions
When several database records must be saved as one logical operation, transaction handling helps prevent a partially completed save. If one part of the operation fails, the transaction can be rolled back.
Protect Mass Assignment
When assigning arrays from $_POST, make sure the model's validation rules and safe attributes are configured appropriately. Never assume that every submitted field should be accepted by the model.
Be Careful with Existing Child Records
When updating a parent with existing HAS_MANY records, understand how your chosen behavior handles existing related rows, deleted rows, and newly added rows. Test these cases before deploying the functionality to production.
Frequently Asked Questions
How do I save multiple related models in Yii?
In Yii 1.1, you can define an ActiveRecord relationship such as HAS_MANY, assign the submitted related data to the relationship, and use a solution such as ESaveRelatedBehavior with saveWithRelated(). This allows the parent and related records to be saved as part of the same workflow.
What is ESaveRelatedBehavior in Yii?
ESaveRelatedBehavior is a Yii 1.1 extension that adds methods for saving related ActiveRecord models. It supports HAS_MANY and MANY_MANY relationships and provides methods including saveWithRelated() and saveRelated().
What is a HAS_MANY relationship in Yii?
A HAS_MANY relationship represents a one-to-many relationship. For example, one father can have multiple children. Yii defines these relationships through the relations() method of an ActiveRecord model.
Can Yii save parent and child records with one button?
Yes. A form can submit the parent fields and multiple child fields together. The controller can assign the submitted child data to the parent relationship and save the parent with its related records.
How do I add multiple child fields dynamically in Yii?
You can use JavaScript or AJAX to generate additional child form fields. Each child should use a unique numeric index so the submitted data can be processed as a collection of related records.
Should I use a database transaction when saving related models?
Transactions are useful when several database changes should succeed or fail as one logical operation. ESaveRelatedBehavior itself processes its related-model save operation within a transaction.
Can ESaveRelatedBehavior save MANY_MANY relationships?
Yes. The extension supports both HAS_MANY and MANY_MANY relationships. For many-to-many relationships, the relation must be configured with the appropriate junction table and foreign keys.
Can I use this approach when updating related records?
Yes. saveWithRelated() can be used when updating a parent model as well as when creating a new one. However, you should test how existing related records should be handled, especially if users can add or remove child records.
Is this approach suitable for Yii 2?
This article specifically targets Yii 1.1 and the ESaveRelatedBehavior extension discussed here. Yii 2 has a different ActiveRecord architecture and should be implemented using Yii 2-compatible patterns and extensions.
Conclusion
Saving a parent model together with multiple child records is a common requirement in Yii applications. You can implement it manually with database transactions and loops, but a reusable ActiveRecord behavior can reduce repetitive code.
With a properly configured HAS_MANY relationship, dynamic child fields, validation, and saveWithRelated(), you can create a form where users enter the parent and child information and save everything with a single submission.
If you are maintaining a legacy Yii 1.1 application or building a PHP application that needs custom relational workflows, understanding ActiveRecord relationships, nested forms, tabular input, and database transactions can make these implementations much easier to manage.
Need Help With Your Yii or PHP Application?
Working with legacy Yii applications, complex ActiveRecord relationships, custom forms, or database workflows? ScriptBaker helps businesses build, maintain, and modernize custom web applications and software systems.
From PHP and Yii development to custom web applications, APIs, SaaS platforms, and application modernization, our development team can help you turn complex requirements into maintainable solutions.