Создание сайта на Yii 2 (блога, basic) — Часть 1

image

Всем привет! Я попытаюсь подробно описать процесс разработки базовой версии блога с использованием 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->text()->notNull(),
            'title' => $this->string()->notNull()->unique(), // Название статьи
            'abridgment' => $this->text()->notNull(), // Сокращенный текст
            'activity' => $this->integer()->notNull()->defaultValue(0), // Активность статьи
        ], $tableOptions);
    }

Далее просто выполняем этот код в консоли:

yii migrate

Если сказать проще, эта команда выполняет методы up в классах миграции + обновляет/добавляет таблицу 'migration', соответственно метод down обращает изменения.

Таблицы готовы.

image

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'][] = $this->title; // <-- Небольшая навигация
?>
<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 на нужна модель, я генерирую новую:

(Описание можно посмотреть просто наведя курсор на лейбл формы)

image

Далее заполняем формы и генерируем сам CRUD:

image

Проверить CRUD можно через такой get ?r=post

P.S. Конец первой части.
P.P.S. Первая статья, судите строго.
Share post

Comments 23

    +8
    Сижу и пытаюсь придумать термин для феномена «каждый должен написать статью на тему создания простого блога на Yii2». 40% ваших рекомендаций вариации документации к фрэймворку, остальные 60% содержимого собственные​ банальные шаги которые и описывать то не стоит. Напишите лучше статью на тему почему хабру нужен именно этот топик.
      +3
      Надо отдать должное автору, он первый, кто умудрился написать об этом столько, что пришлось разбить на несколько частей. Так, глядишь, и полный рерайт документации кто-то осилит.

      Начнем с изменения модели, наследуем ActivityRecord

      Переходим по данному url localhost/Yii2St/basic/web/index.php?r=gii


      ActivityRecord, ссылки на локалхост… это так прекрасно, что у меня глаза начали мироточить.

      Пожалуйста, поймите! Для тех, кому нужно красной стрелочкой показывать на скриншоте из PHPMyAdmin на какую кнопку нажимать, чтобы создать таблицу — ваша статья, содержимое которой тут и там привязано к вашему локальному окружению и тому как оно устроено — не даст ничего! Если они и доберутся до попытки открыть вашу ссылку на локалхост, то на этом точно все закончится. Вся статья сводится к — перепечатайте мой код с экрана, а как он работает и почему — читайте в официальной документации. Получаем added value = 0.
        0
        «каждый должен написать статью на тему создания простого блога на Yii2»
        Не, не так:
        «каждый должен написать статью на тему создания простого блога на %фреймворк, который я знаю%»
          +1
          %фреймворк, который я знаю%

          надо зменить на
          %фреймворк, который я на днях начал изучать%

          т.к. когда автор находится на уровне «знаю», рекомендации уже должны быть в стиле «как писать простой, понятный, тестируемый, пригодный для повторного использования код для %фреймворк»
        0
        Очередная статья на основе документации. Зачем? Таких только на хабре штук 50, не говоря уже о всём интернете.
        Разобрались с фреймворком! Отлично, но зачем этой радостью делиться со всем миром?

        Это как если ребёнок научился читать и подходит к каждому встречному и объясняет как читать — если взять и прочитать несколько букв друг за другом, то получится слог или даже слово! Представляешь? Все так просто! По своему опыту гораздо легче учиться читать на детских книжках, а не по взрослым! Если вы знаете как выглядят буквы, то эти написанные слова будут для вас интуитивно понятны
          –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 — ну и слово вы нашли. Ну это уже очень субъективная моя претензия.
            0
            Всем спасибо, зарефакторю.
              0
              Как уже сказали выше — вы просто сделали рерайт документации. И прада, если уж пишете про то как создать сайт на фреймворке, зачем указывать стрелочками кнопки в ПхпАдмине? :)

              А вообще единственное, что здесь новое и полезное — это слово «abridgment». Я такого даже не знал и никогда не встречал, хотя с английским у меня довольно неплохо, особенно техническим :)
                0
                Для Yii 1.x на оффсайте существует статья с похожим пошаговым процессом создания блога.
                Пока там не появится аналогичная статья для Yii 2.x будут вновь и вновь публиковаться подобные посты.
                  0
                  Есть прекрасный цикл статей Building Your Startup With PHP основанный на Yii2, который продолжает регулярно пополняться.
                  +1
                  если вы не знакомы с паттерном MVC, то перед изучением Yii 2 вы просто обязаны знать что это такое;

                  Зачем, если Yii не MVC?

                    –1
                    Карл, возможно ты не знаешь что такое MVC?
                      +1

                      Возможно как раз знаю, по-этому и говорю, что Yii, Laravel, Symfony — это скорее MVP с примесью ADR, но ни разу не MVC. MVC требует для каждой модели иметь вьюшку и иметь прямую связь событий с ней. Реализация MVC, как и MVVM в вебе возможна лишь, либо на клиенте, либо с использованием веб-сокетов.

                        –1
                        Это все ерунда.
                        Никто ничего не требует.
                        Вот тут толково автор все разложил по полочкам:
                        https://habrahabr.ru/post/321050/
                        https://habrahabr.ru/post/322700/
                          +1

                          Прошу заметить, что такие штуки как MVC с активной и пассивной моделью появились как раз из-за километра вот таких вот вбросов "у нас есть новый супер-модный MVC фреймворк", который с MVC (и уж тем более MVCe '79го года) и рядом не стоял. ;)


                          В любом случае, что же тогда такое MVP, как не этот самый "MVC с пассивным видом" (это из статьи по ссылке)?

                            0
                            «у нас есть новый супер-модный MVC фреймворк»

                            Ну да, каждый создатель очередного фреймворка тянет крутые термины и аббревиатуры, чтобы заманить хомячков :)
                              0

                              Как жаль что во времена создания MVC (79-ый год) были модны трехбуквенные аббривиатуры… а так было бы MVEC (Model <- View <- Editors <- Controller). И было бы понятнее что это такое… А сейчас MVC значит все что угодно. И если нет единого мнения что это такое — не стоит употреблять этот термин от слова совсем. Тем самым мы все больше и больше будем распространять безграмотность.

                    +1
                    TLDR: КГ/АМ

                    знание PHP на уровне ООП;

                    Это что за уровень такой? У автора такой?

                    вы можете сократить рабочий процесс используя IDE, я использую PhpStorm;

                    Не-не, только хардкор.

                    Для смены директории вводим следующий код:
                    (код windows)
                    cd C:\xampp\htdocs\Yii2St\basic

                    Видимо, статья расчитана на имбицилов :)

                    изменить стандартную модель User (models/User.php);
                    добавить модель для регистрации (+models/SignupForm.php);

                    Почему бы модели User не содержать регистрацию?.. :)

                    по этому пишем примерно следующий код

                    По какому? :)

                    $user = new User(); // Создаем новый экземпляр User

                    Да ты чертов гений.

                    Пожалуйста, заполните формы для регистрации

                    Лучше так:
                    «Пожалуйста, покажите формы для регистрации»

                    Какие-то заклинанья вуду.
                    Прочитавшие это будут их выполнять словно культ карго.
                      0
                      Почему бы модели User не содержать регистрацию?.. :)

                      Потому что в advanced шаблоне (в basic наверное тоже), форма регистрация вынесена отдельно. И мне кажется такой подход более гибкий. Так как в противном случае в модели юзера будут появляться поля ненужные юзеру *например повтор пароля и капчи), и придется пилить сценарии чтобы отделять мух от котлет (регистрацию от входа)
                        0
                        Это что за уровень такой? У автора такой?

                        Я специально для интервью разделил графу "ООП" на "Умеет классы" и "умеет ООП". Специально на такой случай.

                        +1
                        зачем были сделаны пользователи?
                        где про блог?

                        в статье описано что делать, не описано зачем :)) не описано как это работает и как этим пользоваться.

                        и на мой вкус это только затравка, продолжение будет?
                          0
                          После «Далее заполняем формы и генерируем сам CRUD:» — картинка та же самая. Поправьте пожалуйста.
                            0
                            Спасибо.

                          Only users with full accounts can post comments. Log in, please.