Handling Many To Many Relation

Anyone know how to deal with many to many relationships in Yii2?

I’ve found this excellent article http://www.larryullman.com/2010/08/10/handling-related-models-in-yii-forms/ but this is for Yii 1.

Many thanks in advance

Docs: https://github.com/yiisoft/yii2/blob/master/docs/guide/active-record.md#relations-with-pivot-table

1 Like

Thanks for your reply.

I’ve already read it, but my main problem is the form. How do I present the field if I’ve no attribute (like in yii1) in the model?

I can manage one to many:

Example (Employee and Organization)

Employee model




public function getOrganizationList() { 

  return ArrayHelper::map(Organization::find()->asArray()->all(), 'Id', 'Name');

}



Employee _form




<?= $form->field($model, 'Organization_Id')->dropDownList($model->organizationList) ?>



But many to many:

Example (Article and Category)

Article model




public function getCategoryList() { 

  return ArrayHelper::map(Category::find()->asArray()->all(), 'Id', 'Name');

}



Article _form




<?= $form->field($model, '######')->dropDownList($model->categoryList) ?>



the ‘######’ is my problem and how to do multiple selection

Right now I’m using virtual attributes, like


public $item_ids;

<?= $form->field($record, 'item_ids')->checkboxList(...); ?>

And then in aftersave I grab those and create pivot items.

But I’m pretty sure there can be easier way to do it (like using relation name as a field name)

As far as I know, there is no relation name in yii2

Is there already an example how to save related records for a model?

http://www.yiiframework.com/forum/index.php/topic/52592-handling-many-to-many-relation/page__view__findpost__p__243022

Could anyone provide some code how to do the saving? I think the ids that come from the post request need to be walk through. Then new entries have to be created or missing ids have to be deleted in the pivot table/model.

Or is there any magic already built in Yii?

As a newbie I’m currently don’t know how to it. And it would be helpful for other beginners.

At least this can be done easier (now?):

Many-to-many relation, i.e. using a pivot table for articles and categories (so three tables are involved).

In article/_form.php:




<?= $form->field($model, 'categories')->checkboxList(

  ArrayHelper::map(Category::find()->all(), 'id', 'name')) ?>



And in Article.php:




public function getCategories() {

   return $this->hasMany(Category::className(), ['id' => 'category_id'])->viaTable(

  'category_article_mapping_table', ['article_id' => 'id']);

}



However, displaying might easy, but saving is still a problem for me. I think I have to use a transaction and do there all the needed inserts and deletions. But I don’t now if I can use ActiveRecord of if I build my own sql statements.

I use this code to update 3 table models relations:

In Article.php





public function afterSave($insert, $changedAttributes)

    {

        $oldCategories = ArrayHelper::map($this->categories, 'id', 'id');


        $categories = Yii::$app->request->post('Article')['categories'];


        $idsToInsert = array_diff($categories, $oldCategories);

        $idsToDelete = array_diff($oldCategories, $categories);


        // --- Insert ---

        foreach ($idsToInsert as $id) {

            $mArticleCategory = new category_article_mapping_table();

            $mArticleCategory->article_id = $this->id;

            $mArticleCategory->category_id = $id;


            $mArticleCategory->save(false);

        }


        // --- Delete ---

        foreach ($idsToDelete as $id) {

            category_article_mapping_table::deleteAll(['category_id' => $id, 'article_id' => $this->id]);

        }


        return parent::afterSave($insert, $changedAttributes);

    }




Hope it helps…

Thank you Radek.

I have found another approach. It is a bit more generic but not so efficient yet. It could be improved, I think. I have put it into the pivot model class since it could be used in ‘both directions’ (as it is a many-to-many relation), so it is in the middle now. However, since it is generic it should be somewhere else.




    /**

     * Links a list of models to a parent model by creating entries into a mapping table. All existing relations

     * will be deleted. This method is for many-to-many relations, so parent and child might be confusing.

     *

     * @param ActiveRecord $parentModel The model that is the parent.

     * @param String $propertyName The name of the property at the parent model to which the list of child models belong.

     * @param String|String[] $childModelIds The ids of the child models.

     */

    public static function setLinks(ActiveRecord $parentModel, $propertyName, $childModelIds)

    {

        $getter = 'get' . $propertyName;

        $linkedModelClassName = $parentModel->$getter()->modelClass;


        $parentModel->unlinkAll($propertyName, true);


        if (empty($childModelIds)) return;


        foreach ($childModelIds as $id) {

            $childModel = $linkedModelClassName::findOne($id);

            $parentModel->link($propertyName, $childModel);

        }

    }



It uses the ability of link() that detects pivot relations. And I have to note that gii has generated the getCategorys() and getArticles() methods in the model classes (using the viaTable method). Without [font=“Courier New”]$parentModel->$getter()->modelClass[/font] wouldn’t work.

This function could be used like this:




SomeClass::setLinks($category, 'articles', Yii::$app->request->post('Category')['articles']);



or




SomeClass::setLinks($article, 'categorys', Yii::$app->request->post('Article')['categorys']);



There should also be a transaction, surrounding the whole update stuff.

What do you think about this? Or does one would like to improve it?