Yii 2.0: Book Has Author - Many to Many relations using Kartik\Select2


I could make this work following same useful tips from this post.

But I really fell that my code could be 'smarter' (I'm a begginer), so please comment if you have any good idea to improve it.

First, you need Books, Author, and BookHasAutor db_table, Model, and CRUD.

In your models, you should declare the many_many relation, as in API:

// models\books.php
public function getBookHasAuthor()
   return $this->hasOne(BookHasAuthor::className(), ['book_id' => 'id']);
public function getAuthors()
   return $this->hasMany(Authors::className(), ['id' => 'author_id'])->via('bookHasAuthor');
//you can also declare this relations in models\authors.php

In the same books model, we need same adicional code:

// declare authorsId property
public $authorIds = [];
// you need a getter for select2 dropdown
public function getdropAuthor()
    $data = Author::find()->asArray()->all();
    return ArrayHelper::map($data, 'id', 'name');
// You will need a getter for the current set o Authors in this Book
public function getAuthorIds()
   $this->authorIds = \yii\helpers\ArrayHelper::getColumn(
   return $this->authorIds;
// You need to save the relations in BookHasAuthor table (adicional code for updates)
public function afterSave($insert)
   $actualAuthors = [];
   $authorExists = 0; //for updates
   if (($actualAuthors = BookHasAuthor::find()
    ->andWhere("book_id = $this->id")
    ->all()) !== null) {
      $actualAuthors = ArrayHelper::getColumn($actualAuthors, 'author_id');
      $authorExists = 1; // if there is authors relations, we will work it latter
   if (!empty($this->despIds)) { //save the relations
      foreach ($this->despIds as $id) {
         $actualAuthors = array_diff($actualAuthors, [$id]); //remove remaining authors from array
     $r = new BookHasAuthor();
     $r->book_id = $this->id;
     $r->author_id = $id;
   if ($authorExists == 1) { //delete authors tha does not belong anymore to this book
    foreach ($actualAuthors as $remove) {
      $r = BookHasAuthor::findOne(['author_id' => $remove, 'book_id' => $this->id]);
   parent::afterSave($insert); //don't forget this

In Your BooksController, we need same changes:

public function actionUpdate($id)
   $model = $this->findModel($id);
   $model->authorIds = $model->getAuthorIds(); //could it be automatically??
public function actionDelete($id)
   $model = $this->findModel($id);
   if ($model->load(Yii::$app->request->post())) {
     BookHasAuthor::deleteAll('book_id = :bookId', [':bookId' => $model->id]);
     $model->delete(); // you need it if you have restrict relations in your db

Finally, in your _form.php, let's use Select2:

<?= $form->field($model, 'AuthorIds')->widget(Select2::classname(), [
   'options' => ['multiple' => true]

That's all.

Total 2 comments

#18422 report it
ashfinlayson at 2014/10/27 04:43pm
Tidy up books model

In your books model you can simplify. In my example I am doing a many to many relation between Entity and Location with a join model (table) EntityLocation

// models\ar\Location
public function getEntities()
   // Return entities related to this location
   return $this->hasMany(Entity::className(), ['id' => 'location_id'])
                ->viaTable(EntityLocation::tableName(), ['entity_id' => 'id']);

Example of a controller action:

// find one location
$l = Location::find()->one();
// get array of entities related to that location
$entities = $l->entities;
#17390 report it
thiagoc7 at 2014/06/02 07:18pm
you need to declare authorIds as safe in model::rules()

Like this:

// models\books.php
public function rules()
        return [
            [['authorIds'], 'safe'],

Leave a comment

Please to leave your comment.

Write new article