В реляционных базах данных таких как SQL есть три вида связей: 1. Один к одному, 2. Один ко многим (многие к одному) и 3. Многие ко многим.
Рассмотрим ситуацию Yii2 когда нам необходимо связать книги и их авторов, например, одна книга может иметь много авторов и один автор может иметь много книг, поэтому здесь вырисовывается связь многие ко многим.
Создадим три таблицы:
-- -- Структура таблицы `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:
<?php 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 и произвести в ней некоторые дополнительные изменения:
<?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:
<?php 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 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 без перезагрузки.