Yii2 создание блога (Часть 6 – тэги для постов)

Из этой статье вы узнаете как работать с сущностями отношение у которых Многие-ко-Многим (Many to Many) на примере добавления функционала тэгов к статьям.

Для начала создадим необходимые таблицы для хранения тэгов и связей тэг-пост. Правила генерации опишем в миграции.

<?php

use yii\db\Schema;
use yii\db\Migration;

class m141116_104920_tags extends Migration
{
    public function up()
    {
        $tableOptions = null;
        if ($this->db->driverName === 'mysql') {
            $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_general_ci ENGINE=InnoDB';
        }

        $this->createTable('{{%tags}}', [
            'id' => Schema::TYPE_PK,
            'title' => Schema::TYPE_STRING . ' NOT NULL',
        ], $tableOptions);

        $this->createTable('{{%tag_post}}', [
            'tag_id' => Schema::TYPE_INTEGER,
            'post_id' => Schema::TYPE_INTEGER
        ], $tableOptions);

        $this->createIndex('FK_tag', '{{%tag_post}}', 'tag_id');
        $this->addForeignKey(
            'FK_tag_post', '{{%tag_post}}', 'tag_id', '{{%tags}}', 'id', 'SET NULL', 'CASCADE'
        );

        $this->createIndex('FK_post', '{{%tag_post}}', 'post_id');
        $this->addForeignKey(
            'FK_post_tag', '{{%tag_post}}', 'post_id', '{{%post}}', 'id', 'SET NULL', 'CASCADE'
        );
    }

    public function down()
    {
        $this->dropTable('{{%tags}}');
        $this->dropTable('{{%tag_post}}');
    }
}

После этого сгенерируем модели при помощи Gii-генератора “Тэг”. Параметры для генератора:
Table Name: “tags”
Model Class: “Tags”
Use Table Preffix
Generate Relations

И модель для связи “Тэг-Пост”. Параметры для генератора:
Table Name: “tag_post”
Model Class: “TagPost”
Use Table Preffix
Generate Relations

После этого вносим корректировки в модель постов, добавляем метод, который будет возвращать тэги, закреплённые за постом. Т.к. в Yii2 нет связи многик-ко-многим (many to many), она заменяется на две hasMany.

/**
 * @return ActiveQuery
 */
public function getTagPosts()
{
    return $this->hasMany(TagPost::className(), ['post_id' => 'id']);
}

Для хранения массива идентификаторов присвоенных посту тэгов добавим в класс защищённую переменную и пару публичных метода на установку и получения идентификаторов тэгов:

/**
 * Список тэгов, закреплённых за постом.
 * @var array
 */
protected $tags = [];

/**
 * Устанавлиает тэги поста.
 * @param $tagsId
 */
public function setTags($tagsId)
{
	$this->tags = (array) $tagsId;
}

/**
 * Возвращает массив идентификаторов тэгов.
 */
public function getTags()
{
	return ArrayHelper::getColumn(
		$this->getTagPost()->all(), 'tag_id'
	);
}

А так же вносим корректировки в правила валидации rules():

[['publish_date', 'tags'], 'safe'],

Для сохранения выбранных в форме редактирования тэгов добавляем обработчик на событие

public function afterSave($insert, $changedAttributes);

Который предварительно удаляет все закреплённые за постом тэги из связывающей таблицы, формируют новый массив и записывает его в БД:

/**
 * @inheritdoc
 */
public function afterSave($insert, $changedAttributes)
{
	TagPost::deleteAll(['post_id' => $this->id]);
	$values = [];
	foreach ($this->tags as $id) {
		$values[] = [$this->id, $id];
	}
	self::getDb()->createCommand()
		->batchInsert(TagPost::tableName(), ['post_id', 'tag_id'], $values)->execute();

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

После того, как модели подготовленны, переходим к генерации GRUD со стороны панели управления. Для этого можно так же воспользоваться инструментом кодо-генерации gii.

Параметры для CRUD Generator
Model Class: common\models\Tags
Controller Class: backend\controllers\TagsController

Зададим права доступа для контроллера backend\controllers\TagsController, что бы доступ к нему для неавторизованных пользователей был закрыт. Для этого добавляем правила в метод behaviors():

'access' => [
	'class' => AccessControl::className(),
	'rules' => [
		[
			'actions' => ['index', 'view', 'create', 'update', 'delete'],
			'allow' => true,
			'roles' => ['@'],
		],
	],
],

В контроллере поста правим action на update и create, добавляем возвращаемый параметр:

'category' => Category::find()->all()

Он понадобится для вывода списка доступных тэгов в формах редактирования и создания поста.

В саму форму поста добавляем элемент отвечающий за вывод списка с мультивыбором:

<?= $form->field($model, 'tags')->listBox(
	ArrayHelper::map($tags, 'id', 'title'),
	[
		    'multiple' => true
	]
) ?>

Правим шаблоны update.php и create.php добавляя в них передачу списка тэгов на рендеринг:

<?= $this->render('_form', [
	'model' => $model,
	'category' => $category,
	'tags' => $tags,
	'authors' => $authors
]) ?>

На этом закончен раздел по работе с тэгами в панели администрирования. Перейдём к выводу списка тэгов на лицевой части сайта.

Для вывода тэгов необходимо отредактировать два шаблона вывод краткого поста post/shortView.php и полного поста post/view.php, добавив в него следующий код:

<div class="tags">
    Тэги: <?php foreach($model->getTagPost()->all() as $post) : ?>
        <?= $post->getTag()->one()->title ?>
    <?php endforeach; ?>
</div>

Более подробный код можно посмотреть в репозитории.