Виджет для данных Yii2 GridView для связи многие ко многим (many-to-many)

В реляционных базах данных таких как SQL есть три вида связей: 1. Один к одному, 2. Один ко многим (многие к одному) и 3. Многие ко многим.

Рассмотрим ситуацию когда нам необходимо связать книги и их авторов, например, одна книга может иметь много авторов и один автор может иметь много книг, поэтому здесь вырисовывается связь многие ко многим.

Создадим три таблицы:

--
-- Структура таблицы `books`
--

CREATE TABLE IF NOT EXISTS `books` (
`id` int(11) NOT NULL,
  `title` varchar(255) NOT NULL,
  `content` text,
  `img` varchar(255) DEFAULT NULL,
  `date` date DEFAULT NULL
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1;

-- --------------------------------------------------------

--
-- Структура таблицы `authors`
--

CREATE TABLE IF NOT EXISTS `authors` (
`id` int(11) NOT NULL,
  `name` varchar(255) NOT NULL,
  `surname` varchar(255) NOT NULL,
  `birthday` date DEFAULT NULL,
  `phone` varchar(255) DEFAULT NULL
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1;

-- --------------------------------------------------------

--
-- Структура таблицы `authors_books`
--

CREATE TABLE IF NOT EXISTS `authors_books` (
  `author_id` smallint(6) DEFAULT NULL,
  `book_id` smallint(6) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Итак, нам необходимо создать на основе этих трех таблиц наш виджет GridView для административной части нашего сайта.

Для этого, с помощью генератора gii сгенерируем модель для книг Books.php, авторов Authors.php и промежуточной талицы AuthorsBooks.php.

Пропишем в модели Books.php связи с нашими таблицами, добавим атрибут authors:

namespace common\models;

use Yii;

/**
 * This is the model class for table "books".
 *
 * @property int $id
 * @property string $title
 * @property string|null $content
 * @property string|null $author_id
 * @property string|null $img
 * @property string|null $date
 */
class Books extends \yii\db\ActiveRecord
{
    /**
     * {@inheritdoc}
     */
    public static function tableName()
    {
        return 'books';
    }

    /**
     * {@inheritdoc}
     */
    public function rules()
    {
        return [
            [['title'], 'required'],
            [['content'], 'string'],
            [['date'], 'safe'],
            [['title', 'img'], 'string', 'max' => 255],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function attributeLabels()
    {
        return [
            'id' => 'ID',
            'title' => 'Title',
            'content' => 'Content',
            'img' => 'Img',
            'date' => 'date',
	          'authors' => 'Authors'
        ];
    }

    /**
     * {@inheritdoc}
     * @return BooksQuery the active query used by this AR class.
     */
    public static function find()
    {
        return new BooksQuery(get_called_class());
    }
	
	public function getAuthorsBooks()
    {
        return $this->hasMany(authorsBooks::className(), ['book_id' => 'id']);
    }

    public function getAuthors()
    {
        return $this->hasMany(Authors::className(), ['id' => 'author_id'])
            ->via('authorsBooks');
    }

}

Далее, нам необходимо сгененрировать модель BooksSearch.php и произвести в ней некоторые дополнительные изменения:

namespace common\models;

use yii\base\Model;
use yii\data\ActiveDataProvider;
use common\models\Books;

/**
 * BooksSearch represents the model behind the search form of `common\models\Books`.
 */
class BooksSearch extends Books
{
    /**
     * {@inheritdoc}
     */
    public $authors;
	
    public function rules()
    {
        return [
            [['id'], 'integer'],
            [['title', 'content', 'img', 'date', 'authors'], 'safe'],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function scenarios()
    {
        // bypass scenarios() implementation in the parent class
        return Model::scenarios();
    }

    /**
     * Creates data provider instance with search query applied
     *
     * @param array $params
     *
     * @return ActiveDataProvider
     */
    public function search($params)
    {
        $query = Books::find();

        // add conditions that should always apply here

        $dataProvider = new ActiveDataProvider([
            'query' => $query,
        ]);

        $this->load($params);

        if (!$this->validate()) {
            // uncomment the following line if you do not want to return any records when validation fails
            // $query->where('0=1');
            return $dataProvider;
        }
	      $query->joinWith(['authors']);
        // grid filtering conditions
        $query->andFilterWhere([
            'id' => $this->id,
            'date' => $this->date,
        ]);

        $query->andFilterWhere(['like', 'title', $this->title])
            ->andFilterWhere(['like', 'content', $this->content])
            ->andFilterWhere(['like', 'img', $this->img])
	          ->andFilterWhere(['in', 'authors.id', $this->authors]);
        return $dataProvider;
    }
}

В модели BooksSearch.php мы добавили свойство public $authors и связь с нашей таблицей $query->joinWith([‘authors’]). Для того, что бы выполнялся поиск по нашим данным, нам не следует использовать в фильтре sql запрос LIKE, так как нам приходит массив авторов, а необходимо использовать sql запрос IN, то есть andFilterWhere([‘in’, ‘authors.id’, $this->authors]).

Осталось вывести наши данные в вид и непосредственно отобразить их в нашем GridView:

use yii\helpers\Html;
use yii\grid\GridView;
use common\models\BooksSerch;
use common\models\Authors;
use yii\widgets\Pjax;

/* @var $this yii\web\View */
/* @var $searchModel app\models\BooksModel */
/* @var $dataProvider yii\data\ActiveDataProvider */

$this->title = 'Books';
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="books-index">

    <h1><?= Html::encode($this->title) ?></h1>

    <p>
        <?= Html::a('Create Books', ['create'], ['class' => 'btn btn-success']) ?>
    </p>

    <?php // echo $this->render('_search', ['model' => $searchModel]); ?>
	<?php Pjax::begin(); ?>
        <?= GridView::widget([
        'dataProvider' => $dataProvider,
        'filterModel' => $searchModel,
        'columns' => [
            ['class' => 'yii\grid\SerialColumn'],

            'id'
            'title',
            'content:ntext',
            'img',
            'date',
		[
		    'attribute'=>'authors',
		    'label'=>'Authors',
		    'format'=>'text', // Возможные варианты: raw, html
		    'content'=> function($data){
		    $authors = [];
		    foreach ($data->authors as $author) {
		        $authors[] = $author->name;
		    }
		    $authors = implode(", ", $authors);
		    return $authors;
		    },
		    'filter'=> yii\helpers\ArrayHelper::map(Authors::find()->all(), 'id', 'name'),
		],
            ['class' => 'yii\grid\ActionColumn'],
        ],
    ]); ?>
	<?php Pjax::end(); ?>

Оборачиваем это все в виджет Pjax и получаем работу GridView без перезагрузки.

Добавить комментарий