Когда я изучал yii, мне, конечно же, помог в освоении нового фреймворка стандартный учебник по созданию блога. Немного освоившись стал переходить на более современную версию — Yii2, и в этом случае уже не нашел такого замечательного пособия. Разобравшись с основами, подумал, как бы было хорошо для начинающих, или мигрирующих с первой версии yii, существование точно такого же блога, но реализованного с помощью инструмента yii2. Конечно, в сети уже реализовано большое количество блогов на yii2, есть готовые расширения для создания поддержки тегов, комментариев и др. Но от этого вход в yii2 не становиться легче. В связи, с чем задумал реализовать порт блога с yii на yii2 (бета:https://github.com/tilhom/myblog_yii2).
Рабочий пример можно посмотреть здесь

Основные моменты реализации:

По стандартным рецептам, которых уже много в сети, устанавливаем Advanced template в папку доступную из сети. В результате у нас будет каркас готового веб-приложения с frontend и backend частями. Далее, можно по учебнику yii1.1.* создания блога разворачивать приложение подобное блоку первой версии yii.

Создаем таблицы для нашего блока из готовой модели yii 1.1.*(находится здесь: yii/demos/blog/protected/data). Перед тем, как запустить sql, необходимо сделать поправки:
— для упрощения убрать префикс tbl_,
— исключить таблицу tbl_user, так как в шаблоне advanced уже есть из коробки модель User
— сделать соответственные поправки в создании внешних ключей в таблицах post и comments:

sql таблиц
CREATE TABLE lookup
(
id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(128) NOT NULL,
code INTEGER NOT NULL,
type VARCHAR(128) NOT NULL,
position INTEGER NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

CREATE TABLE post
(
id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(128) NOT NULL,
content TEXT NOT NULL,
tags TEXT,
status INTEGER NOT NULL,
create_time INTEGER,
update_time INTEGER,
author_id INTEGER NOT NULL,
CONSTRAINT FK_post_author FOREIGN KEY (author_id)
REFERENCES user (id) ON DELETE CASCADE ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

CREATE TABLE comment
(
id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
content TEXT NOT NULL,
status INTEGER NOT NULL,
create_time INTEGER,
author VARCHAR(128) NOT NULL,
email VARCHAR(128) NOT NULL,
url VARCHAR(128),
post_id INTEGER NOT NULL,
CONSTRAINT FK_comment_post FOREIGN KEY (post_id)
REFERENCES post (id) ON DELETE CASCADE ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

CREATE TABLE tag
(
id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(128) NOT NULL,
frequency INTEGER DEFAULT 1
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

Создаем модели наших таблиц и сгенерируем коды операций CRUD. Для этого запускаем генератор gii в backend -e: http://localhost/blog/backend/web/index.php?r=gii

Соответственно заполняем поля:

Table Name: *
Namespace: common\models

Жмём [Preview] и убираем галочку с создания модели Migration.php она нам не понадобиться, убедившись, что не отмечен пункт overwrite модели User.php жмём на далее [Generate]

В результате у нас получиться в папке common\models следующие модели:

common/models/Comment.php
common/models/Lookup.php
common/models/Post.php
common/models/Tag.php

После того, как были созданы модели, мы можем использовать Crud Generator для генерации кода операций CRUD для них. Сделаем это в backend -е, так как CRUD — это операция для админки. Создадим CRUD моделей Post и Comment.

Для Post:
Model Class: common\models\Post
Search Model Class: common\models\PostSearch
Controller Class: backend\controllers\PostController

Для Comment:
Model Class: common\models\Comment
Search Model Class: common\models\CommentSearch
Controller Class: backend\controllers\CommentController

Далее, в учебнике первой версии настраивается аутентификация, здесь же Вы, возможно, заметили, что каркас приложения advanced уже реализует аутентификацию, посредством таблицы user БД и модели User.

Доработка модели Post.

Изменение метода rules():

return [
            [['title', 'content', 'status'], 'required'],
            ['title','string','max'=>128],
            ['status','in', 'range'=>[1,2,3]],
            ['tags', 'match', 'pattern'=>'/^[\w\s,]+$/',
            'message'=>'В тегах можно использовать только буквы.'],
            ['tags', function($attribute,$params){
                $this->tags=Tag::array2string(array_unique(Tag::string2array($this->tags)));
            }],

        ];


Изменение метода relations():

     public function getComments()
    {
        return $this->hasMany(Comment::className(), ['post_id' => 'id'])
        ->where('status = '. Comment::STATUS_APPROVED)
                    ->orderBy('create_time DESC');
    }

    public function getCommentCount()
    {
        return $this->hasMany(Comment::className(), 
            ['post_id' => 'id'])->where(['status'=>Comment::STATUS_APPROVED])->count();
    }

    public function getAllCommentCount()
    {
        return $this->hasMany(Comment::className(), 
            ['post_id' => 'id'])->where(['status'=>Comment::STATUS_APPROVED])->count();
    }


Добавляем в модель помощники:

— свойство url:

    public function getUrl()
    {
        return Yii::$app->urlManager->createUrl([ 'post/view', 
            'id'=>$this->id,
            'title'=>$this->title]);
    }


— текстовое представление для статуса:

   public static function items($type)
    {
        if(!isset(self::$_items[$type]))
            self::loadItems($type);
        return self::$_items[$type];
    }
 
    public static function item($type,$code)
    {
        if(!isset(self::$_items[$type]))
            self::loadItems($type);
        return isset(self::$_items[$type][$code]) ? self::$_items[$type][$code] : false;
    }

    private static function loadItems($type)
    {
        self::$_items[$type]=[];
        $models=self::find()->where(['type'=>$type])->orderBy('position')->all();
        foreach ($models as $model)
            self::$_items[$type][$model->code]=$model->name;
    }



Создание и редактирование записей происходит в части приложения backend, чтобы только авторизированные пользователи могли пользоваться интерфейсом, изменим правила доступа в контроллерах.

Настройка правил доступа

  'access' => [
                    'class' => AccessControl::className(),
                    'rules' => [
                        [
                            'allow' => true,
                            'roles' => ['@'],
                        ],
                    ],
                ],


Правки в действиях create и update
Дейтвуем по инструкциям учебника с поправками: для status — выпадающий список со всеми возможными состояниями записи:

 <?= $form->field($model, 'status')->dropDownList(Lookup::items('PostStatus'),['prompt'=>'Select...']) ?>


В приведённом коде для получения списка статусов используется вызов Lookup::items('PostStatus').

Далее изменим класс Post таким образом, чтобы он автоматически выставлял некоторые атрибуты (такие, как create_time и author_id) непосредственно перед сохранением записи в БД.

 'timestamp' => [
                        'class' => TimestampBehavior::className(),
                        'attributes' => [
                            ActiveRecord::EVENT_BEFORE_INSERT => ['create_time', 'update_time'],
                            ActiveRecord::EVENT_BEFORE_UPDATE => ['update_time'],
                        ],
                    ],
                    [
                        'class' => BlameableBehavior::className(),
                        'createdByAttribute' => 'author_id',
                        'updatedByAttribute' => 'author_id',
                    ],


При сохранении записи мы хотим также обновить информацию о частоте использования тегов в таблице tag. Мы можем реализовать это в методе afterSave(), который автоматически вызывается после успешного сохранения записи в БД.

    public function afterSave($insert, $changedAttributes)
    {
        parent::afterSave($insert, $changedAttributes);
        Tag::updateFrequency($this->_oldTags, $this->tags);
   
    }

    public function afterFind()
    {
        parent::afterFind();
        $this->_oldTags=$this->tags;
    }


Отображение записей будет происходить в части frontend. Для этого в этой части приложения с помощью gii создаем CRUD для модели Post и удаляем ненужные действия create, update, delete. Вводим изменения в действия index и view. При просмотре постов разметка страницы разделена на два столбца. Для чего созданы два макета column1.php и column2.php, которые в приложении переключаются, например index — column2, view — column1.

Управление комментариями
Дорабатываем модель Comment. Вносим соответствующие изменения в метод rules().

public function rules()
    {
        return [
        [['content', 'author', 'email'], 'required'],
        [['author', 'email', 'url'], 'string', 'max' => 128],
        ['email','email'],
        [['content'], 'string'],
        ['url','url'],
        [['status', 'create_time', 'post_id'], 'integer'],
        ];
    }



Чтобы автоматом прописывала дату создания комментария, создаём в модели поведение:

public function behaviors(){
        return [
        'timestamp' => [
        'class' => TimestampBehavior::className(),
        'attributes' => [
        ActiveRecord::EVENT_BEFORE_INSERT => ['create_time'],
        ],
        ]   
        ];
    }


Подправляем метод attributeLabels() по учебнику
И далее точно по учебнику реализуем создание и отображение комментариев в контроллере и представлении view frontend части приложения:

namespace frontend\controllers;
/* * */
class PostController extends Controller
{
/* * */
public function actionView($id)
    {
        $this->layout='column1';
        $model = $this->findModel($id);
        //$comment=$this->newComment($model);
        $comment=new Comment();
        if($comment->load($_POST) && $model->addComment($comment))
            {
                if($comment->status==Comment::STATUS_PENDING){
                    Yii::$app->getSession()->setFlash('warning','Thank you for your comment. Your comment will be posted once it is approved.');
                }
                return $this->refresh();
            }
        return $this->render('view',array(
            'model'=>$model,
            'comment'=>$comment,
        ));
    }



Код представления:

<?php

use yii\helpers\Html;
use yii\widgets\DetailView;

/* @var $this yii\web\View */
/* @var $model common\models\Post */

$this->title = $model->title;
$this->params['breadcrumbs'][] = ['label' => 'Posts', 'url' => ['index']];
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="post-view">
   <?php  echo $this->context->renderPartial('_item', array(
    'model'=>$model
));?>

    <div id="comments" class="row-fluid">
    <?php
    if($model->commentCount>=1): ?>
        <h4>
            <?php echo $model->commentCount>1 ? $model->commentCount . ' comments' : 'One comment'; ?>
        </h4>
    
        <?php echo $this->context->renderPartial('_comments',array(
            'post'=>$model,
            'comments'=>$model->comments,
        )); ?>
    <?php endif; ?>

        <?php echo $this->context->renderPartial('/comment/_form',array(
            'model'=>$comment,
        )); ?>
   


</div><!-- comments -->

</div>


Код _item.php:

<?php 
use \yii\helpers\Html;
?>
<div>
	
	<h1><?php echo Html::a(Html::encode($model->title), $model->url); ?></h1>
	
	<p class="meta">Posted by <?php echo $model->author->username . ' on ' . date('F j, Y',$model->create_time); ?></p>
	<p class='lead'>
		<?php
			echo $model->content;
		?>
	<p>
	<div>
		<p>
			<strong>Tags:</strong>
			<?php echo  $model->tags; ?>
		</p>
		<?php echo Html::a('Permalink', $model->url); ?> |
		<?php echo Html::a("Comments ({$model->commentCount})",$model->url.'#comments'); ?> |
		Last updated on <?php echo date('F j, Y',$model->update_time); ?>
	</div>
</div>


Код _comments.php:

<?php 
use \yii\helpers\Html;
foreach($comments as $comment): ?>
<div class="well" id="c<?php echo $comment->id; ?>">
	<div class="row">
		<div class="col-md-8">
			<h4><?php echo $comment->authorLink; ?> says:</h4>
		</div>
		<div class="col-md-4 text-right">
			<?php echo Html::a("#{$comment->id}", $comment->getUrl(),[ 
				'class'=>'cid',
				'title'=>'Permalink to this comment!',
				]); 
				 ?>
		</div>
	</div>
	<hr style="margin:2px 0px;">
	<p class='lead'>
		<?php echo nl2br(Html::encode($comment->content)); ?>
	</p>
	<h5>
		<?php echo date('F j, Y \a\t h:i a',$comment->create_time); ?>
	</h5>
</div><!-- comment -->
<?php endforeach; ?>


И форма создания нового комментария:

<?php
use \yii\helpers\Html;
use \yii\widgets\ActiveForm;
?>

<div class="panel panel-success">
	<div class="panel-heading">
		<h3 class="panel-title">Leave a Comment</h3>
	</div>
	<div class="panel-body">
		<?php $form = ActiveForm::begin(); ?>
		<?php echo $form->field($model,'author')->textInput(); ?>
		<?php echo $form->field($model,'email')->textInput(); ?>
		<?php echo $form->field($model,'url')->textInput(); ?>
		<?php echo $form->field($model,'content')->textArea(array('rows'=>6, 'cols'=>50)); ?>
		<div class="form-actions text-center">
			<?php echo Html::submitButton('Save',['class' => 'btn btn-success btn-block']); ?>
		</div>

		<?php ActiveForm::end(); ?>

	</div>
</div>


Для управления комментариями из админки дорабатываем, ранее созданный CRUD для модели Comment. Действие index создано с помощью виджета GridView, в столбце которого реализовано одобрение комментариев с помощью метода approve():

<?= GridView::widget([
        'dataProvider' => $dataProvider,
        'filterModel' => $searchModel,
        'columns' => [
            ['class' => 'yii\grid\SerialColumn'],

            [
                'attribute'=>'id',
                'contentOptions'=>['style'=>'width:64px;'],
            ],
            'content:ntext',
           [
                'attribute'=>'status',
                'format'=>'raw',
                'value'=>function ($model){
                    $text=\common\models\Lookup::item('CommentStatus',$model->status);
                    $url=Url::to(["comment/approve","id"=>$model->id]);
                    Url::remember();
                    return $text=='Pending Approval'?Html::a($text,$url):$text;
                },
                'contentOptions'=>['style'=>'width:136px;'],               
            ],
            'create_time:datetime',
            'author',

            [
                'class' => 'yii\grid\ActionColumn',
                'header' => 'Actions',
                'contentOptions'=>['style'=>'width:96px;'],
            ],
        ],
    ]); ?> 


Ну и напоследок, пока, реализован виджет списка последних комментариев, который отображается в frontend-е приложении при просмотре постов.
Добавляем в модель Comment статический метод:

public static function findRecentComments($limit=10)
    {
        return static::find()->where('status='.self::STATUS_APPROVED)
                    ->orderBy('create_time DESC')
                    ->limit($limit)
                    ->with('post')->all();
    }


И реализуем widget:

class RecentComments extends Widget
{
	public $comments;
	public function init()
	{
		parent::init();
		$this->comments =  Comment::findRecentComments();
		//print('<pre>'); var_dump($this->comments);print('<pre>');die; 
	}

	public function run()
	{
		return $this->render('recent-comments');
	}
	
}


С представлением:

<div class="panel panel-default">
	<div class="panel-heading">
		<h3 class="panel-title">Recent Comments</h3>
	</div>
	<div class="panel-body">
		<ul class="list-unstyled">
		<?php foreach($this->context->comments as $comment): ?>
				<li><span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
				<?php echo $comment->authorLink; ?> on
					<?php echo Html::a(Html::encode($comment->post->title), $comment->getUrl()); ?>
				</li>
			<?php endforeach; ?>
		</ul>
	</div>

</div>


Конечно, это не есть подробное руководство, тем более есть готовый учебник создания бога для первой версии yii, здесь же приведены основные моменты кодирования под Yii2. Надеюсь, для начинающих это будет полезно, реализовать свой блог на Yii2 в учебных целях.

Комментарии (1)


  1. Graid
    19.10.2015 15:50

    Код не отформатирован, а текст практически полностью унесен с разных уголков yiiframework.ru.