Бортовой журнал Ктулху

Заметки по Yii2

В интернете масса статей по созданию блога на Yii2 и я добавлю еще одну. Моя статья выгодно отличается от других тем, что лично для меня она построена на моем же опыте работы с Yii2. Всем прочим - на собственное усмотрение.

Делал для себя заметки по Yii2 когда разбирался с созданием блога, установкой и так далее. Данная статья написана на основе собственного опыта и информации из сети. Пошагово расписано создание блога и некоторые мелочи, все по собственному опыту.

 

 

Установка

https://github.com/yiisoft/yii2/blob/master/docs/guide-ru/start-installation.md

Вы можете установить Yii двумя способами: используя Composer или скачав архив. Первый способ предпочтительнее так как позволяет установить новые расширения или обновить Yii одной командой.

 

composer global require "fxp/composer-asset-plugin:^1.2.0"

composer create-project --prefer-dist yiisoft/yii2-app-basic basic

 

Первая команда устанавливает composer asset plugin, который позволяет управлять зависимостями пакетов bower и npm через Composer. Эту команду достаточно выполнить один раз. Вторая команда устанавливает последнюю стабильную версию Yii в директорию basic. Если хотите, можете выбрать другое имя директории.

Установилось!

В процессе установки требует ключ гитхаба

Нужно зарегистрироваться на Github и перейти в раздел Personal settings => Personal access tokens https://github.com/settings/tokens/new

screenshot 2017 02 25 13 56 41 1 И сгенерить ключ, который будет выглядеть примерно так:

3f9a71509f0c617244f22a209212061712bd7a84

Неплохо описано у этих чуваков:

https://xn--d1acnqm.xn--j1amh/%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8/yii-framework-2-%D0%BA%D0%B0%D0%BA-%D1%8F-%D1%83%D1%81%D1%82%D0%B0%D0%BD%D0%B0%D0%B2%D0%BB%D0%B8%D0%B2%D0%B0%D0%BB-yii2

 

Настройка подключения к БД

После установки необходимо настроить создать БД и настроить подключение. Файл с настройками подключения к БД находится в директории ./config/db.php:

 

return [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=yii2_blog',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
'tablePrefix' => 'tbl_'
];

 


Проектирование БД

Для начала создадим три таблицы:

Категории блога:

 

CREATE TABLE `tbl_category` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,

PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

 

Пользователи блога, они же авторы:

 

CREATE TABLE `tbl_user` (

`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`login` varchar(40) NOT NULL,
`password` varchar(100) NOT NULL,
`email` varchar(100) NOT NULL,
`nickname` varchar(255) NOT NULL,
`about` text,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

 

Посты блога:

 

CREATE TABLE `tbl_post` (

`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`anons` text,
`content` mediumtext,
`category_id` int(10) unsigned DEFAULT NULL,
`author_id` int(10) unsigned DEFAULT NULL,
`publish_status` enum('draft','publish') NOT NULL DEFAULT 'draft',
`publish_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

PRIMARY KEY (`id`),
CONSTRAINT `FK_post_category` FOREIGN KEY (`category_id`) REFERENCES `tbl_category` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT `FK_post_author` FOREIGN KEY (`author_id`) REFERENCES `tbl_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

 

База данных готова, переходим к созданию моделей. В этом нам поможет Yii code generator: http://localhost/index.php?r=gii

Создание модели БД

Переходим в gii, выбираем model generator, ставим опцию "Use Table Prefix".

В поля “Table Name” и “Model Class” поочерёдно вводим имена таблиц и имена генерируемых классов и жмите кнопку генерировать:

tbl_category: Category
tbl_user: User
tbl_post: Post

Note: во время генерации модели User перезапишите имеющийся файл User.php

На выходе генератора получаем классы представляющие модели для таблиц в БД.

Начинаем вводить "tbl ..."

screen 991dd7f84a50b84667550a03bdc890e0

table name: tbl_user
model name: User

При создании моделей подставляются уродские имена типа TblPost, но пока я не знаю как это обойти. Если сделать прост Post, то не генерится CRUD
Открываем /models/Post.php и исправляем имена методов с TblPost на Post. После этого работает.

Можно просто Post но не юзать table prefix

Когда модели созданы

/var/www/yii/basic/models/Category.php

/var/www/yii/basic/models/Post.php

/var/www/yii/basic/models/User.php

Контроллеры

Создаем контроллер

https://кодер.укр/записи/yii-2-вывод-списка-записей-с-помощью-виджета-listview-подробный-пример

Заходим в создание контроллера
Пишем в первом поле:
app\controllers\%controller_name%Controller

Вместе с контроллером будет создан и view

Как создавать CRUD

https://yiiframework.com.ua/ru/doc/guide/2/start-gii/

Заходим в CRUD Generator.

Model Class для Post указываем как app\models\TblPost.

Search Model Class - app\models\TblPostSearch.

Controller Class - app\controllers\TblPostController.

View Path - @app/views/TblPost.

Заходим в Controller Generator.

Controller Class - app\controllers\TblPostController.

Action IDs - index.

View Path - @app/views/TblPost.

screen d733242958ee339a0c69971754bdb186

Для проверки работы зайти по этим ссылкам (могут отличаться от ваших в зависимости от настроек):
http://yii.my/basic/web/index.php?r=post

http://yii.my/basic/web/index.php?r=post/index

http://test.my/basic/web/index.php?r=consumer

Откроется индексная страница контроллера post.

Один момент - убедиться в правильности пути к шаблону, у меня сразу выдало ошибку, оказалось, папка с шаблонами начинается с большой буквы.

Но страница пустая, нужно вывести список постов.

Список постов

В этой части есть варианты как я делал по инструкции и в тестовом задании.

Модель [yii/basic/models/Post.php]

Для получения записей из БД используем ActiveRecords

use yii\data\ActiveDataProvider;

public function getPosts()
{
return new ActiveDataProvider([
'query' => Post::find()
]);
}

-= или как в тестовом =-
Consumer.php

В модели никакой выборки, только описание полей

 

<?php

namespace app\models;
use yii\db\ActiveRecord;
use Yii;
use yii\web\UploadedFile;

/**
* This is the model class for table "{{%consumer}}".
*
* @property integer $consumerId
* @property integer $groupId
* @property string $login
* @property string $password
* @property string $email
* @property string $expirationDateAndTime
* @property string $imageExtention
*/
class Consumer extends \yii\db\ActiveRecord
{
public $file;
/**
* @inheritdoc
*/
public static function tableName()
{
return '{{%consumer}}';
}

/**
* @inheritdoc
*/
public function rules()
{
return [
[['groupId', 'login', 'password', 'email'], 'required'],
[['groupId'], 'integer'],
[['expirationDateAndTime'], 'safe'],
[['login', 'password', 'email'], 'string', 'max' => 254],
];
}

/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'consumerId' => 'Consumer ID',
'groupId' => 'Group ID',
'login' => 'Login',
'password' => 'Password',
'email' => 'Email',
'expirationDateAndTime' => 'Expiration Date And Time',
'imageExtention' => 'Image Extention',
];
}
}

 

 

Контроллер [yii/basic/controllers/PostController.php]

Создаем метод indexAction()

и делаем выборку
$post = new Post(); $list = $post->find()->all();
var_dump($list);

 

public function actionIndex()
{
$post = new Post();
return $this->render('index', [
'posts' => $post->getPosts(),
]);
}

 

-= или как в тестовом =-
ConsumerController.php

В контроллере только вывод в шаблон

 

<?php

namespace app\controllers;
use app\models\Consumer;

class ConsumerController extends \yii\web\Controller
{

public function actionIndex()
{
return $this->render('index');
}
--- добавлено для сохранения формы ---
public function actionAdd()
{

$model = new EntryForm();

if ($model->load(Yii::$app->request->post()) && $model->validate()) {
// успех, пишем в базу
$model->login = $_POST['EntryForm']['login'];
$model->password = md5($_POST['EntryForm']['password']);
$model->email = $_POST['EntryForm']['email'];
$model->groupId = $_POST['EntryForm']['groupId'];
$model->expirationDateAndTime = $_POST['EntryForm']['expirationDateAndTime'];
$model->file = UploadedFile::getInstance($model, 'file');
// $model->imageExtention = $model->file->extension;
$filename = $model->file->baseName. rand() . '.' . $model->file->extension;
$model->imageExtention = $filename;
// вытащить последний id пользователя для корректного названия файла

// save file
$model->file->saveAs('uploads/' . $filename);

// save model
$model->save();

return $this->render('success', ['model' => $model]);
} else {
// страница отображается первый раз или ошибка
return $this->render('addForm', ['model' => $model]);
}
}



Выводим список статей в шаблон

Шаблоны списка [yii/basic/views/post/index.php] и анонса поста [yii/basic/views/post/shortView.php]

Анонс


<?php
use yii\helpers\Html;
?>
<h1><?= $model->title ?></h1>
<div class="content">
<?= $model->anons ?>
</div>
<?= Html::a('Читать далее', ['post/view', 'id' => $model->id], ['class' => 'btn btn-success']) ?>

 

Эти анонсы выводим циклом в списке

Список


<?php
/* @var $this yii\web\View */
use yii\helpers\Html;
?>
<h1>Список статей</h1>

<p>
You may change the content of this page by modifying
the file <code><?= __FILE__; ?></code>.
</p>

<div class="col-sm-8 post-index">

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

<?php
foreach ($posts->models as $post) {
echo $this->render('shortView', [
'model' => $post
]);
}
?>
</div>

 

-= или выводим через ListView как в тестовом =-
делаем выборку из базы и сразу же выводим списком

 

<?php

use app\models\Consumer;
use yii\widgets\ListView;
use yii\data\ActiveDataProvider;

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

 

Как устроена сортировка по определенному полю:


$dataProvider = new ActiveDataProvider([
'query' => Consumer::find()->orderBy(['(login)' => SORT_DESC]),
]);


Как добавить постраничную навигацию и другой вариант сортировки:
http://docs.mirocow.com/doku.php?id=yii2:activedataprovider

 

$dataProvider = new ActiveDataProvider([
'query' => Consumer::find(),
'sort'=>array(
'defaultOrder'=>['login' => SORT_DESC],
),
'pagination' => [
'pageSize' => 3,
'validatePage' => false,
],
]);


P.S. первый вариант сортировки более рабочий

Вывод через виджет ListView:


echo ListView::widget([
'dataProvider' => $dataProvider,
'itemView' => '_list',
]);

 

В дополнение создаем шаблон _list.php для вывода элемента с таким содержимым:

 

<?php
use yii\helpers\Html;
use yii\helpers\HtmlPurifier;
?>

<div class="news-item">
<?= HtmlPurifier::process($model->email) ?>
</div>

Итого, у нас есть список анонсов статей.

Нужно добавить еще пагинацию.

Постраничная навигация

Нашел два способа:

1. в контроллере
http://www.webapplex.ru/postranichnaya-navigacziya-v-yii-2.x

http://www.yiiframework.com/doc-2.0/yii-data-pagination.html

2. в модели через ActiveRecord

http://stackoverflow.com/questions/23336269/how-to-create-pager-in-yii2

Юзаю 2-й, т.к. я делаю выборку через ActiveRecord

В модели (Post.php) в метод выборку постов добавляем:

 

public function getPosts()

{
return new ActiveDataProvider([
'query' => Post::find(),
'pagination' => array('pageSize' => 3)
]);
}

 

Сразу выборка ограничилась до 3 постов, теперь нужно вывести постраничную навигацию.

В views/post/index.php добавляем


echo \yii\widgets\LinkPager::widget([
'pagination'=>$posts->pagination
]);

 

Все, постраничная навигация работает.

Теперь статья детально

Статья детально

https://github.com/Georgynet/Blog-Yii2/blob/master/common/models/Post.php

https://sllite.ru/2014/10/yii2-%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D0%B1%D0%BB%D0%BE%D0%B3%D0%B0-%D1%87%D0%B0%D1%81%D1%82%D1%8C-5-%D0%B2%D1%8B%D0%B2%D0%BE%D0%B4-%D0%BF%D0%BE%D1%81%D1%82%D0%BE%D0%B2-%D0%B8/

yii/basic/models/Post.php

Создаем метод для одного поста с выборкой по id

 

public function getPost($id)
{
return new ActiveDataProvider([
'query' => Post::findOne($id)
]);
}

 

yii/basic/controllers/PostController.php

 

public function actionView($id)
{
$post = new Post();
return $this->render('detail', [
'article' => $post->getPost($id),
]);
}


К этому методу можно достучаться по такому url:
index.php?r=post/post&id=1

Рендерим в шаблон detail
yii/basic/views/post/detail.php

 

<?php
use yii\helpers\Html;
?>
<h1><?= Html::encode($article->query->title);?></h1>
<div><?= Html::encode($article->query->anons);?></div>
<div><?= Html::encode($article->query->content);?></div>

 

Хлебные крошки

http://www.webapplex.ru/vizdzhet-breadcrumbs-(xlebnyie-kroshki)-na-yii-2.x

Виджет хлебных крошек по умолчанию уже есть в файле yii/basic/views/layouts/main.php

 

<div class="container">
<?= Breadcrumbs::widget([
'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
]) ?>
<?= $content ?>
</div>

 

Если же его нет, можно подключить в любое представление таким образом:

  • подкл. класс use yii\widgets\Breadcrumbs;
  • подкл вижет


<div class="container">
<?= Breadcrumbs::widget([
'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
]) ?>
<?= $content ?>
</div>

 

  • передаем массив с параметрами

 


$this->params['breadcrumbs'][] = [
'template' => "<li><b>{link}</b></li>\n", // шаблон для этой ссылки
'label' => 'Категория', // название ссылки
'url' => ['/category'] // сама ссылка
];
$this->params['breadcrumbs'][] = ['label' => 'Подкатегория', 'url' => ['/category/subcategory']];

 

 

ЧПУ

В файле настроек yii/basic/config/web.php раскомментировать или добавить


'urlManager' => [
'enablePrettyUrl' => true,
'showScriptName' => false,
'rules' => [
],
],


в .htaccess добавить

 

RewriteEngine on

# If a directory or a file exists, use the request directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# Otherwise forward the request to index.php
RewriteRule . index.php

 

 

Ссылки на детальные статьи формируются таким образом:


<?= Html::a('Читать далее', ['post/view', 'id' => $model->id], ['class' => 'btn btn-success']) ?>


post - название контроллера статей

view - название метода actionView($id)

Список категорий

по аналогии со списком постов

yii/basic/models/Category.php


/**
* @return ActiveDataProvider
* get categories list
*/
public function getCategories()
{
return new ActiveDataProvider([
'query' => Category::find(),
'pagination' => array('pageSize' => 5)
]);
}

/**
* @param $id
* @return ActiveDataProvider
* get category by id
*/
public function getCategory($id)
{
return new ActiveDataProvider([
'query' => Category::findOne($id)
]);
}

 

yii/basic/controllers/CategoryController.php

 

public function actionIndex()
{
$category = new Category();
return $this->render('index', [
'categories' => $category->getCategories(),
]);
}

 

yii/basic/views/category/index.php

Здесь я сделаю немного не так как с постами. Напомню, у поста есть шаблон shortView, где выводится анонс, этот шаблон используется в списке постов. В категориях я обойдусь без него, выведу циклом в одном шаблоне.

 

foreach ($categories->models as $category) {
echo '<li class="list-group-item">';
echo Html::a($category->title, ['category/view', 'id' => $category->id], ['class' => 'list-group-item']);
echo '</li>';
}

 

еще один нюанс: вывод постов по категориям

В

yii/basic/models/Post.php

добавляем переменную с id категории и условие для выборки.

<div class="container">
<?= Breadcrumbs::widget([
'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
]) ?>
<?= $content ?>
</div>

0

Громоздко, но работает, позже отрефакторим.

yii/basic/controllers/PostController.php

 

<div class="container">
<?= Breadcrumbs::widget([
'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
]) ?>
<?= $content ?>
</div>

1

 

Добавляем в индексный контроллер переменные с id категории и все работает.

URL с таким параметром выведет список постов по категориям

/post?cid=2


Продолжение следует ...