Save or Update Multiple related model values in one Tabular input form

In this wiki I will show you how to save/update a model with many related model For example: How to set a product to multiple categories only in one post form ?

Schema database table

Product:id,title,... Category:id,title,... product_to_category: id,productId,categoryId,...

You have to add the below code in your Model and View (Suppose you have already creates the default MVC for Product)

So,

Model:

//1)add validator to allow multiple values
 public function rules() {
        return array(
            ...
            array('toCat', 'type', 'type' => 'array', 'allowEmpty' => false),
            ...
        );
    }
    
    
//2)stored and inserted values
public $toCat = [];
public $toCat_stored = [];

//retrieve the related ids
public function afterFind() {
    $this->toCat_stored = [];
    foreach ($this->productsToCategories as $r) { //productsToCategories is relation with table product_to_category
        $this->toCat[] = $r->CategoryId;
    }

    $this->toCat_stored = $this->ItemToCat;

    parent::afterFind();
}

//3) save the selected categories, remove the unselected categories
protected function afterSave() {

        if (!$this->toCat) //if nothing selected set it as an empty array
            $this->ItemToCat = array();

        //save the new selected ids that are not exist in the stored ids
        $ids_to_insert = array_diff($this->toCat, $this->toCat_stored);
        
        foreach ($ids_to_insert as $nid) {
            $m = new ProductToCategory();
            $m->productId = $this->id;
            $m->CategoryId = $nid;
            $m->save();
        }

        //remove the stored ids that are not exist in the selected ids
        $ids_to_delete = array_diff($this->ItemToCat_stored, $this->ItemToCat);


        foreach ($ids_to_delete as $did) {
            if ($d = ProductToCategory::model()->findByAttributes(['productId' => $this->id, 'CategoryId' => $did])) {
                $d->delete();
            }
        }

        parent::afterSave();
}

Controller: nothing special, save model and render view as usually

View: (_form.php)

In your form widget add the below code before submit button

<div>
        <?php
        $allcats = CHtml::listData(Category::model()->findAll(),'id','title');

        echo '<table style="width:50%;"><thead><tr><th>category</th><th>checked</th></tr><thead><tbody>';
        foreach ($allcats as $catId => $catTitle) :
            $isChecked = in_array($catId, $model->toCat);
            echo '<tr>';
            echo '<td>' . $catTitle . '</td>';
            echo '<td>' . CHtml::checkBox('Product[toCat][]', $isChecked, array('value' => $catId)) . '</td>';
            echo '</tr>';
        endforeach;
        echo '</tbody></table>';
        ?>
    </div>

Enjoy it!