Всем привет! Я попытаюсь подробно описать процесс разработки базовой версии блога с использованием Yii 2(basic) в целях обучения.
Почему basic? Если взяться за advanced, можно легко запутаться на первых парах.
Перед началом я расскажу о ПО:
- web-cервер c модулем PHP и SQL базой данных. В данном случае я рекомендую сборку web-сервера XAMPP, он прост в установки, удобный интерфейс управления..;
- Basic версия Yii 2. Для установки Yii 2, я рекомендую Composer(пакетный менеджер). Овладевая Composer-ом, в последствии вы сохраните целую кучу времени не только с yii, но и с другим ПО. Т.к. Composer загружает yii с GitHub, Composer просит token github, если у вас есть аккаун на GitHub, эта проблема решается очень быстро.
Рекомендации:
- знание ООП в PHP;
- базовое знание SQL;
- если вы не знакомы с паттерном MVC, то перед изучением Yii 2 вы просто обязаны знать что это такое;
- я также рекомендую ознакомиться с документацией, самым важным будет «Первое Знакомство» и «Структура Приложения», остальное можно эффективно изучить в процессе.
1) Настройка БД и создание таблиц
Итак, вы так или иначе установили Yii 2 basic, вы проверили работу стандартного сценария.
Предлагаю начать с настройки БД, я буду использовать MySQL и phpMyAdmin.
Yii нужна информация о БД, заходим в один из файлов конфигурации приложения, а именно в basic\config\db.php:
return [
'class' => 'yii\db\Connection', // Класс реализации подключения
'dsn' => 'mysql:host=localhost;dbname=yii2basic', // Имя базы данных, URL
'username' => 'root', // Логин пользователя этой ДБ
'password' => '', // Пароль пользователя этой ДБ
'charset' => 'utf8', // Кодировка
];
Стандартные параметры совпадают с теми, которые мне нужны. Эти данные будут использованы классом yii\db\Connection (тоже указанным в параметрах) для инициализации и создания экземпляра этого класса, а соответственно классами имеющими доступ к ДБ.
В данном случае нам нужно несколько таблиц:
- таблица для пользователей, т.к. в basic версии стандартно не реализована регистрация/вход через БД;
- таблица для статей в блоге.
Добавлять новые таблицы в БД я буду через миграции, именно они помогут общаться между отдельно взятыми базами данных. Применять миграции не обязательно, но это позволит применять/отменять/применять_повторно/смотреть_историю_миграций в каждой отдельной копии приложения.
Итак, чтобы создать миграцию нужно пообщаться с файлом yii.bat расположенном в basic директории приложения.
Я буду общаться через Командную строку windows
Смена директории:
cd C:\xampp\htdocs\Yii2St\basic
Создание миграций для таблицы user и post(таблица для статей):
Код после cd:
yii migrate/create create_post_table
(Для таблицы post)
yii migrate/create create_user_table
(Для таблицы user)
Yii создаст папку migrations если ее нет. Теперь там хранится 2 файла и класса для двух выше созданных миграций.
Зайдем в
basic\migrations\m170304_160410_create_user_table.
Вносим изменения в функцию «up», делаем такую же таблицу как в версии advanced:
public function up() //Событые создания таблицы
{
$tableOptions = null;
if ($this->db->driverName === 'mysql') { // Тип БД, далее тип таблицы и стандартная кодировка для этой таблицы.
$tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
}
$this->createTable('user', [
'id' => $this->primaryKey(),
'username' => $this->string()->notNull()->unique(),
'auth_key' => $this->string(32)->notNull(),
'password_hash' => $this->string()->notNull(),
'password_reset_token' => $this->string()->unique(),
'email' => $this->string()->notNull()->unique(),
'status' => $this->smallInteger()->notNull()->defaultValue(10),
'created_at' => $this->integer()->notNull(),
'updated_at' => $this->integer()->notNull(),
], $tableOptions);
}
Также вносим изменения и для статей в basic\migrations\m170304_160323_create_post_table:
public function up()
{
$tableOptions = null;
if ($this->db->driverName === 'mysql') {
$tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
}
$this->createTable('post', [
'id' => $this->primaryKey(),
'author_id' => $this->integer()->notNull(), //Автор
'date' => $this->integer()->notNull(),
'category_id' => $this->integer()->notNull(), //Номер категории
'text' => $this->string()->unique(),
'title' => $this->string()->notNull()->unique(), // Название статьи
'abridgment' => $this->string()->notNull(), // Сокращенный текст
'activity' => $this->integer()->notNull()->defaultValue(0), // Активность статьи
], $tableOptions);
}
Далее просто выполняем этот код в консоли:
yii migrate
Если сказать проще, эта команда выполняет методы up в классах миграции + обновляет/добавляет таблицу 'migration', соответственно метод down обращает изменения.
Таблицы готовы.
2) Добавление функции регистрации/входа через ДБ
(О компоненте User и IdentityInterface)
В Yii есть компонент приложения User, он управляет статусом аутентификации пользователя, например:
Yii::$app->user->isGuest; // Возвращает false, если пользователь авторизован
Yii::$app->user->logout(); // Производит логаут пользователя
Yii::$app->user->identity->username // Возвращает username пользователя
// и т.д.
Он требует, чтобы вы указали identity class, который будет содержать текущую логику аутентификации:
$config = [
...
'components' => [
....
'user' => [
'identityClass' => 'app\models\User', // Эта моделать будет использована компонентом User
],
....
]
...
];
В стандартной модели User хранение логинов и паролей(т.д.) выглядит так(код из самой User):
private static $users = [
'100' => [
'id' => '100',
'username' => 'admin',
'password' => 'admin',
'authKey' => 'test100key',
'accessToken' => '100-token',
],
'101' => [
'id' => '101',
'username' => 'demo',
'password' => 'demo',
'authKey' => 'test101key',
'accessToken' => '101-token',
],
];
Естественно регистрацию со стандартной моделью будет трудно реализовать, все что нам нужно для полноценной, удобной связи с БД — это ActiveRecord, он сам определит свойства в классе на основе строк в таблице одноименного названием класса.
К примеру:
$model->id; // Получаем id пользователя
$model->id = 5; // Переопределяем id
$model->save(); // Переопределяем значение в самой БД
Также не стоит забывать, что компонент User требует, чтобы нужный ему класс реализовывал интерфейс IdentityInterface для его личных целей.
basic\models\User:
namespace app\models;
use Yii;
use yii\base\NotSupportedException;
use yii\db\ActiveRecord;
use yii\web\IdentityInterface;
class User extends ActiveRecord implements IdentityInterface
{
// Сейчас в переопределяемых методах ниже возвращаются ID или логины только те, у которых 'status' равен константе SCATUS_ACTIVE
const STATUS_DELETED = 0; // Если пользователь Заблокирован(Удален)
const STATUS_ACTIVE = 10; // Если пользователь Активен
const STATUS_ADMIN = 1; // Если пользователь Администратор(для части II)
// Переопределяем методы для интерфейса
public static function findIdentity($id)
{
return static::findOne(['id' => $id, 'status' => self::STATUS_ACTIVE]);
}
public static function findIdentityByAccessToken($token, $type = null)
{
throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.');
}
public static function findByUsername($username)
{
return static::findOne(['username' => $username, 'status' => self::STATUS_ACTIVE]);
}
public function getId()
{
return $this->getPrimaryKey();
}
public function getAuthKey()
{
return $this->auth_key;
}
public function validateAuthKey($authKey)
{
return $this->getAuthKey() === $authKey;
}
public function validatePassword($password)
{
return Yii::$app->security->validatePassword($password, $this->password_hash);
}
public function setPassword($password)
{
$this->password_hash = Yii::$app->security->generatePasswordHash($password);
}
public function generateAuthKey()
{
$this->auth_key = Yii::$app->security->generateRandomString();
}
}
Для реализации регистрации добавим отдельную модель, хотя почти такой же самый метод регистрации можно реализовать в модели User, постоянно используемую модель лучше не забивать ничем лишним + новая модель будет использоваться видом регистрации.
basic\models\SignupForm.php:
(О методе validate() и rules)
namespace app\models;
use Yii;
use yii\base\Model;
class SignupForm extends Model
{
public $username;
public $email;
public $password;
public function rules() // Эти правила будут использоваться при валидации: формы ввода, с помощью вызова метода validate()
{
return [
['username', 'trim'], // обрезает пробелы и превращает в null если нечего не остается
['username', 'required'], // 'username' обязательно для заполнения
['username', 'unique', 'targetClass' => '\app\models\User', 'message' => 'This username has already been taken.'], // 'username' в модели \app\models\User(то есть в таблице user(вспоминаем ActivityRecords) должна быть уникальна)
['username', 'string', 'min' => 2, 'max' => 255], // 'username' это string переменная со значение от 2 до 255 символов
['email', 'trim'],
['email', 'required'],
['email', 'email'],
['email', 'string', 'max' => 255],
['email', 'unique', 'targetClass' => '\app\models\User', 'message' => 'This email address has already been taken.'],
['password', 'required'],
['password', 'string', 'min' => 6],
];
}
public function attributeLabels() // Используется для локализации
{
return [
'username' => 'Логин',
'email' => 'Электронная почта',
'password' => 'Пароль',
];
}
public function signup() // Регистрация
{
if (!$this->validate()) { // Если валидация вернула false то возвращаем null
return null;
}
$user = new User(); // Используем AcriveRecord User
$user->username = $this->username; // Определяем свойства объекта
$user->email = $this->email;
$user->setPassword($this->password);
$user->generateAuthKey();
$user->created_at() = time();
return $user->save() ? $user : null; // Сохраняем свойства в таблицу(метод ActivityRecord) user если переменная не равна null
}
}
Добавляем действие регистрации в стандартный контроллер
basic\controllers\SiteController.php:
//....
public function actionSignup()
{
$model = new SignupForm(); // Не забываем добавить в начало файла: use app\models\SignupForm; или заменить 'new SignupForm()' на '\app\models\SignupForm()'
if ($model->load(Yii::$app->request->post())) { // Если есть, загружаем post данные в модель через родительский метод load класса Model
if ($user = $model->signup()) { // Регистрация
if (Yii::$app->getUser()->login($user)) { // Логиним пользователя если регистрация успешна
return $this->goHome(); // Возвращаем на главную страницу
}
}
}
return $this->render('signup', [ // Просто рендерим вид если один из if вернул false
'model' => $model,
]);
}
//....
Далее нужно добавить вид signup для ввода формы
basic\views\site\signup.php:
use yii\helpers\Html;
use yii\bootstrap\ActiveForm;
$this->title = 'Регистрация';
$this->params['breadcrumbs'][] = ; // <-- Небольшая навигация
?>
<div class="site-signup">
<h1><?= Html::encode($this->title) ?></h1>
<div class="row">
<div class="col-lg-5">
<?php $form = ActiveForm::begin(['id' => 'form-signup']); ?>
<?= $form->field($model, 'username')->textInput(['autofocus' => true]) ?>
<?= $form->field($model, 'email') ?>
<?= $form->field($model, 'password')->passwordInput() ?>
<div class="form-group">
<?= Html::submitButton('Signup', ['class' => 'btn btn-primary', 'name' => 'signup-button']) ?>
</div>
<?php ActiveForm::end(); ?>
</div>
</div>
</div>
Изменяем навбар
basic\views\layouts
// Изменяем начиная с NavBar::begin(), заканчивая NavBar::end()
NavBar::begin([
'brandLabel' => 'My Company', // Надпись слева
'brandUrl' => Yii::$app->homeUrl, // Url который будет указ в гиперссылке на надписи
'options' => [
'class' => 'navbar-inverse navbar-fixed-top', // Класс bootstrap class="navbar-inverse navbar-fixed-top" в HTML
],
]);
$menuItems = [ // Те, которые будут отображаться всегда
['label' => 'Главная', 'url' => ['/site/index']],
['label' => 'Контакт', 'url' => ['/site/contact']],
];
if(Yii::$app->user->isGuest) // Если пользователь не авторизован
{
$menuItems[] = ['label' => 'Зарегистрироватся', 'url' => ['/site/signup']];
$menuItems[] = ['label' => 'Войти', 'url' => ['/site/login']];
}
else
{
$menuItems[] = ['label' => 'Статьи', 'url' => ['/post']];
$menuItems[] = '<li>'
. Html::beginForm(['/site/logout'], 'post') // Форма логаута, смотрим виджет ActiveForm
. Html::submitButton(
'Выйти (' . Yii::$app->user->identity->username . ')',
['class' => 'btn btn-link logout']
)
. Html::endForm()
. '</li>';
}
echo Nav::widget([ // Выводим результат метода
'options' => ['class' => 'navbar-nav navbar-right'],
'items' => $menuItems // Элементы меню
]);
NavBar::end();
Для локализации нужно указать в конфигурации такой код:
basic\config\web.php:
return [
'id' => 'basic',
'basePath' => dirname(__DIR__),
// ...
'language' => 'ru-RU', // <- Эту строку!
// ...
]
О локализации
3) Добавляем CRUD для статей
CRUD позволит добавить, редактировать, посмотреть и удалить статью. Самым быстрым способом добавления CRUD в нашем случае является gii. Gii автоматически генерирует код, после чего нужно будет просто изменить генерированный код.
Переходим по url с таким get ?r=gii
Для CRUD на нужна модель, я генерирую новую:
(Описание можно посмотреть просто наведя курсор на лейбл формы)
Далее заполняем формы и генерируем сам CRUD:
Проверить CRUD можно через такой get ?r=post
P.S. Конец первой части.
P.P.S. Первая статья, судите строго.
Комментарии (21)
LDZ
19.03.2017 14:49Очередная статья на основе документации. Зачем? Таких только на хабре штук 50, не говоря уже о всём интернете.
Разобрались с фреймворком! Отлично, но зачем этой радостью делиться со всем миром?
Это как если ребёнок научился читать и подходит к каждому встречному и объясняет как читать — если взять и прочитать несколько букв друг за другом, то получится слог или даже слово! Представляешь? Все так просто! По своему опыту гораздо легче учиться читать на детских книжках, а не по взрослым! Если вы знаете как выглядят буквы, то эти написанные слова будут для вас интуитивно понятны
gogolinsky
19.03.2017 15:29-1Миграция для таблицы post удивила очень
'author' => $this->string()->notNull(), //Автор
'date' => $this->string(32)->notNull(),
'TypeK' => $this->integer()->notNull(), //Номер категории
'text' => $this->string(20000)->unique(),
'PostName' => $this->string()->notNull()->unique(), // Название статьи
'abridgment' => $this->string(767)->notNull(), // Сокращенный текст
'activity' => $this->integer()->notNull()->defaultValue(0), // Активность статьи
1. author — строка. Это же будет ссылка на пользователя? Так лучше назвать поле author_id и указывать ID пользователя.
2. TypeK и PostName — не принято поля называть кемелкейсом. Разделяйте слова подчеркиванием — type_k и post_name. Да и вооще, что за typek? Тип категории? Уж лучше category_id. А postname? Мы же в таблице post? зачем превикс делать? Поле лучше назвать просто name или title.
3. date — строка? Вы когда пользователя создавали в миграцие выше, то использовали очень удачное название и тип поля created_at integer. Зачем такое разнообразие наименований? Сделайте тоже числовое поле created_at, так поддерживать легче будет БД в будущем.
4. text — варчар, да еще и уникальный. Большая вероятность, что пост в такой маленький размер не пометится. Сделайте тип text. Ну и уж точно на уникальностьэто поле проверять не надо. Зачем?
5. abridgment — ну и слово вы нашли. Ну это уже очень субъективная моя претензия.
Akuma
19.03.2017 16:52Как уже сказали выше — вы просто сделали рерайт документации. И прада, если уж пишете про то как создать сайт на фреймворке, зачем указывать стрелочками кнопки в ПхпАдмине? :)
А вообще единственное, что здесь новое и полезное — это слово «abridgment». Я такого даже не знал и никогда не встречал, хотя с английским у меня довольно неплохо, особенно техническим :)
kiaplayer
19.03.2017 17:43Для Yii 1.x на оффсайте существует статья с похожим пошаговым процессом создания блога.
Пока там не появится аналогичная статья для Yii 2.x будут вновь и вновь публиковаться подобные посты.sspat
19.03.2017 21:56Есть прекрасный цикл статей Building Your Startup With PHP основанный на Yii2, который продолжает регулярно пополняться.
SerafimArts
19.03.2017 18:57+1если вы не знакомы с паттерном MVC, то перед изучением Yii 2 вы просто обязаны знать что это такое;
Зачем, если Yii не MVC?
http3
19.03.2017 20:08-1Карл, возможно ты не знаешь что такое MVC?
SerafimArts
19.03.2017 21:26+1Возможно как раз знаю, по-этому и говорю, что Yii, Laravel, Symfony — это скорее MVP с примесью ADR, но ни разу не MVC. MVC требует для каждой модели иметь вьюшку и иметь прямую связь событий с ней. Реализация MVC, как и MVVM в вебе возможна лишь, либо на клиенте, либо с использованием веб-сокетов.
http3
19.03.2017 23:11-1Это все ерунда.
Никто ничего не требует.
Вот тут толково автор все разложил по полочкам:
https://habrahabr.ru/post/321050/
https://habrahabr.ru/post/322700/SerafimArts
20.03.2017 07:14+1Прошу заметить, что такие штуки как MVC с активной и пассивной моделью появились как раз из-за километра вот таких вот вбросов "у нас есть новый супер-модный MVC фреймворк", который с MVC (и уж тем более MVCe '79го года) и рядом не стоял. ;)
В любом случае, что же тогда такое MVP, как не этот самый "MVC с пассивным видом" (это из статьи по ссылке)?
http3
20.03.2017 10:44«у нас есть новый супер-модный MVC фреймворк»
Ну да, каждый создатель очередного фреймворка тянет крутые термины и аббревиатуры, чтобы заманить хомячков :)Fesor
20.03.2017 11:52Как жаль что во времена создания MVC (79-ый год) были модны трехбуквенные аббривиатуры… а так было бы MVEC (Model <- View <- Editors <- Controller). И было бы понятнее что это такое… А сейчас MVC значит все что угодно. И если нет единого мнения что это такое — не стоит употреблять этот термин от слова совсем. Тем самым мы все больше и больше будем распространять безграмотность.
http3
19.03.2017 20:03+1TLDR: КГ/АМ
знание PHP на уровне ООП;
Это что за уровень такой? У автора такой?
вы можете сократить рабочий процесс используя IDE, я использую PhpStorm;
Не-не, только хардкор.
Для смены директории вводим следующий код:
(код windows)
cd C:\xampp\htdocs\Yii2St\basic
Видимо, статья расчитана на имбицилов :)
изменить стандартную модель User (models/User.php);
добавить модель для регистрации (+models/SignupForm.php);
Почему бы модели User не содержать регистрацию?.. :)
по этому пишем примерно следующий код
По какому? :)
$user = new User(); // Создаем новый экземпляр User
Да ты чертов гений.
Пожалуйста, заполните формы для регистрации
Лучше так:
«Пожалуйста, покажите формы для регистрации»
Какие-то заклинанья вуду.
Прочитавшие это будут их выполнять словно культ карго.webmasterx
20.03.2017 04:51Почему бы модели User не содержать регистрацию?.. :)
Потому что в advanced шаблоне (в basic наверное тоже), форма регистрация вынесена отдельно. И мне кажется такой подход более гибкий. Так как в противном случае в модели юзера будут появляться поля ненужные юзеру *например повтор пароля и капчи), и придется пилить сценарии чтобы отделять мух от котлет (регистрацию от входа)
Fesor
20.03.2017 11:54Это что за уровень такой? У автора такой?
Я специально для интервью разделил графу "ООП" на "Умеет классы" и "умеет ООП". Специально на такой случай.
SbWereWolf
19.03.2017 20:30+1зачем были сделаны пользователи?
где про блог?
в статье описано что делать, не описано зачем :)) не описано как это работает и как этим пользоваться.
и на мой вкус это только затравка, продолжение будет?
Horik_off
Сижу и пытаюсь придумать термин для феномена «каждый должен написать статью на тему создания простого блога на Yii2». 40% ваших рекомендаций вариации документации к фрэймворку, остальные 60% содержимого собственные? банальные шаги которые и описывать то не стоит. Напишите лучше статью на тему почему хабру нужен именно этот топик.
sspat
Надо отдать должное автору, он первый, кто умудрился написать об этом столько, что пришлось разбить на несколько частей. Так, глядишь, и полный рерайт документации кто-то осилит.
ActivityRecord, ссылки на локалхост… это так прекрасно, что у меня глаза начали мироточить.
Пожалуйста, поймите! Для тех, кому нужно красной стрелочкой показывать на скриншоте из PHPMyAdmin на какую кнопку нажимать, чтобы создать таблицу — ваша статья, содержимое которой тут и там привязано к вашему локальному окружению и тому как оно устроено — не даст ничего! Если они и доберутся до попытки открыть вашу ссылку на локалхост, то на этом точно все закончится. Вся статья сводится к — перепечатайте мой код с экрана, а как он работает и почему — читайте в официальной документации. Получаем added value = 0.
http3
hudson
надо зменить на
т.к. когда автор находится на уровне «знаю», рекомендации уже должны быть в стиле «как писать простой, понятный, тестируемый, пригодный для повторного использования код для %фреймворк»