В этой записи мы создадим страницу регистрации пользователей, миграцию для таблицы user
, сделаем авторизацию через базу данных и внесем новые изменения в User
класс.
Эта запись относится к курсу Yii2 basic.
План действий:
- создать новую миграцию и запустить её (мы это уже умеем)
- переписать
models/User
класс на использованиеActiveRecord
- создать форму регистрации, модель для проверки полей формы
Миграция для таблицы user
Для начала перейдем в папку проекта и создадим новую миграцию с названием «create_user_table»:
$ cd /var/www/yii-dev/ $ php yii migrate/create create_user_table
Открываем файл m170903_114309_create_user_table
внутри папки migrations
и заменяем все что там есть внутри на следующее:
<?php use yii\db\Migration; /** * Handles the creation of table `user`. */ class m170903_114309_create_user_table extends Migration { public function up() { $tableOptions = null; if ($this->db->driverName === 'mysql') { // http://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci $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(), 'created_at' => $this->integer()->notNull(), 'updated_at' => $this->integer()->notNull(), ], $tableOptions); } public function down() { $this->dropTable('{{%user}}'); } }
Далее нам нужно запустить миграцию:
$ php yii migrate/up
Вводите «yes» и нажимаете «Enter».
У вас должно вывестись следующее:
*** applying m170903_114309_create_user_table > create table {{%user}} ... done (time: 0.013s) *** applied m170903_114309_create_user_table (time: 0.022s) 1 migration was applied. Migrated up successfully.
Значить все окей. Теперь нужно изменить User
класс.
Изменения в User классе
Новый вариант класса User
:
Под кодом ниже будет подробное описание изменений, которые были сделаны в нём.
<?php namespace app\models; use Yii; use yii\base\NotSupportedException; use yii\behaviors\TimestampBehavior; use yii\db\ActiveRecord; use yii\web\IdentityInterface; /** * User model * * @property integer $id * @property string $username * @property string $password_hash * @property string $password_reset_token * @property string $email * @property string $auth_key * @property integer $status * @property integer $created_at * @property integer $updated_at * @property string $password write-only password */ class User extends ActiveRecord implements IdentityInterface { /** * @inheritdoc */ public static function tableName() { return '{{%user}}'; } /** * @inheritdoc */ public function behaviors() { return [ TimestampBehavior::className(), ]; } /** * @inheritdoc */ public static function findIdentity($id) { return static::findOne(['id' => $id]); } /** * @inheritdoc */ public static function findIdentityByAccessToken($token, $type = null) { throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.'); } /** * Finds user by username * * @param string $username * @return static|null */ public static function findByUsername($username) { return static::findOne(['username' => $username]); } /** * Finds user by password reset token * * @param string $token password reset token * @return static|null */ public static function findByPasswordResetToken($token) { if (!static::isPasswordResetTokenValid($token)) { return null; } return static::findOne(['password_reset_token' => $token]); } /** * Finds out if password reset token is valid * * @param string $token password reset token * @return bool */ public static function isPasswordResetTokenValid($token) { if (empty($token)) { return false; } $timestamp = (int) substr($token, strrpos($token, '_') + 1); $expire = Yii::$app->params['user.passwordResetTokenExpire']; return $timestamp + $expire >= time(); } /** * @inheritdoc */ public function getId() { return $this->getPrimaryKey(); } /** * @inheritdoc */ public function getAuthKey() { return $this->auth_key; } /** * @inheritdoc */ public function validateAuthKey($authKey) { return $this->getAuthKey() === $authKey; } /** * Validates password * * @param string $password password to validate * @return bool if password provided is valid for current user */ public function validatePassword($password) { return Yii::$app->security->validatePassword($password, $this->password_hash); } /** * Generates password hash from password and sets it to the model * * @param string $password */ public function setPassword($password) { $this->password_hash = Yii::$app->security->generatePasswordHash($password); } /** * Generates "remember me" authentication key */ public function generateAuthKey() { $this->auth_key = Yii::$app->security->generateRandomString(); } /** * Generates new password reset token */ public function generatePasswordResetToken() { $this->password_reset_token = Yii::$app->security->generateRandomString() . '_' . time(); } /** * Removes password reset token */ public function removePasswordResetToken() { $this->password_reset_token = null; } }
Во-первых, я удалил приватное статическое свойство $user
, которые использовалось вместо ActiveRecord
:
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', ], ];
Теперь User
класс использует ActiveRecord
, а так же IdentityInterface
, который обязывает вас указать следующие методы внутри класса User
, иначе выдаст ошибку:
public static function findIdentity($id); public static function findIdentityByAccessToken($token, $type = null); public function getId(); public function getAuthKey(); public function validateAuthKey($authKey);
В обновленном формате User
класса они (выше) уже добавлены, поэтому вам лично добавлять ничего не нужно.
/** * User model * * @property integer $id * @property string $username * @property string $password_hash * @property string $password_reset_token * @property string $email * @property string $auth_key * @property integer $status * @property integer $created_at * @property integer $updated_at * @property string $password write-only password */
В дополнении, я добавил описание свойств этого класса (id
, username
и др.), чтобы помочь PhpStorm понимать структура класса, иначе он будет писать, что параметр взят откуда-то используя «магию» :D. Это лишь означает, что IDE не имеет малейшего понимаю откуда вы могли взять некоторые атрибуты.
Также, благодаря статическому методу tableName()
мы указываем какая таблица будет использоваться ActiveRecord‘ом:
public static function tableName() { return '{{%user}}'; }
Если бы вы его не указали, тогда класс все равно бы работал, но бралось бы название класса (User
), конвертировалось в нижний регистр и получилось бы название таблицы, НО если ваша таблица в базе называлась users
, тогда точно пришлось бы tableName()
или переименовать класс в Users
.
Так же были добавлены методы помощники, например как этот:
public static function findByUsername($username) { return static::findOne(['username' => $username]); }
Для быстрого поиска пользователя по его логину.
И конечно же, данные теперь все берутся из таблицы, выгружаются в наш User
класс, где выполняется остальная логика (сравнение и т.д.). Например, метод validatePassword()
сравнивает $password
с вариантом в базе:
public function validatePassword($password) { return Yii::$app->security->validatePassword($password, $this->password_hash); }
В остальном, все изменения были сделаны в поддержку ActiveRecord
, чтобы не использовать статическое свойство — $user
.
После того как класс готов, нужно сделать страницу регистрацию, чтобы пользователи могли регистрироваться и входить в систему.
Регистрация
Создайте новый файл signup.php
в views/site
со следующим содержимым:
<?php /* @var $this yii\web\View */ /* @var $form yii\bootstrap\ActiveForm */ /* @var $model app\models\SignupForm */ 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> <p>Please fill out the following fields to signup:</p> <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('Регистрация', ['class' => 'btn btn-primary', 'name' => 'signup-button']) ?> </div> <?php ActiveForm::end(); ?> </div> </div> </div>
Создайте новый файл SignupForm.php
внутри папки models
со следующим содержимым:
<?php namespace app\models; use yii\base\Model; use app\models\User; /** * Signup form */ class SignupForm extends Model { public $username; public $email; public $password; /** * @inheritdoc */ public function rules() { return [ ['username', 'trim'], ['username', 'required'], ['username', 'unique', 'targetClass' => '\app\models\User', 'message' => 'Такое логин уже занят.'], ['username', 'string', 'min' => 2, 'max' => 255], ['email', 'trim'], ['email', 'required'], ['email', 'email'], ['email', 'string', 'max' => 255], ['email', 'unique', 'targetClass' => '\app\models\User', 'message' => 'Такая почта уже занята.'], ['password', 'required'], ['password', 'string', 'min' => 6], ]; } /** * Signs user up. * * @return User|null the saved model or null if saving fails */ public function signup() { if (!$this->validate()) { return null; } $user = new User(); $user->username = $this->username; $user->email = $this->email; $user->setPassword($this->password); $user->generateAuthKey(); return $user->save() ? $user : null; } }
Это будет использоваться для проверки формы.
Теперь нужно зайти в controllers/SiteController.php
и после actionLogin()
вставить добавить actionSignup()
:
... /** * Форма регистрации. * * @return mixed */ public function actionSignup() { $model = new SignupForm(); if ($model->load(Yii::$app->request->post())) { if ($user = $model->signup()) { if (Yii::$app->getUser()->login($user)) { return $this->goHome(); } } } return $this->render('signup', [ 'model' => $model, ]); } ...
И еще, добавим ссылка на регистрацию в верхнем меню и переведем все на русский. Для этого, заходим в views/layouts/main.php
:
И заменяем это:
echo Nav::widget([ 'options' => ['class' => 'navbar-nav navbar-right'], 'items' => [ ['label' => 'Home', 'url' => ['/site/index']], ['label' => 'About', 'url' => ['/site/about']], ['label' => 'Contact', 'url' => ['/site/contact']], Yii::$app->user->isGuest ? ( ['label' => 'Login', 'url' => ['/site/login']] ) : ( '<li>' . Html::beginForm(['/site/logout'], 'post') . Html::submitButton( 'Logout (' . Yii::$app->user->identity->username . ')', ['class' => 'btn btn-link logout'] ) . Html::endForm() . '</li>' ) ], ]);
На это:
echo Nav::widget([ 'options' => ['class' => 'navbar-nav navbar-right'], 'items' => [ ['label' => 'Главная', 'url' => ['/site/index']], ['label' => 'О нас', 'url' => ['/site/about']], ['label' => 'Контакты', 'url' => ['/site/contact']], ['label' => 'Машины', 'url' => ['car/index'], 'visible' => !Yii::$app->user->isGuest], ['label' => 'Регистрация', 'url' => ['site/signup'], 'visible' => Yii::$app->user->isGuest], Yii::$app->user->isGuest ? ( ['label' => 'Войти', 'url' => ['/site/login']] ) : ( '<li>' . Html::beginForm(['/site/logout'], 'post') . Html::submitButton( 'Выйти (' . Yii::$app->user->identity->username . ')', ['class' => 'btn btn-link logout'] ) . Html::endForm() . '</li>' ) ], ]);
Так же я добавил ссылку на все машины, чтобы было удобнее заходить на эту страницу.
Теперь обновите главную страницу и меню должно быть на русском языке. Ссылка «Регистрация» будет отображаться только для не авторизованных пользователей.
По ссылкам: авторизация — site/login, выход — site/logout и регистрация — site/signup.
Меню в Yii2 управляется используя виджет yii\bootstrap\Nav
. Вы конечно же можете написать собственный виджет или использовать Nav
за основу и дописать к нему дополнительные функции, если это потребуется.
Послесловие
На этом изменения касательно User класса заканчиваются. Теперь вы можете регистрироваться на сайте и входить в систему использую базу данных.
Читать далее — Yii2 basic: Gii-помощник