Сервис на Yii2: Перенос пользователей в БД
В прошлых частях мы создали новое приложение на Yii2, доработали конфигурационный файлы и разбили приложение на модули. Сегодня мы, как было обещано ранее, перенесём хранение пользователей в базу данных и добавим возможность регистрации и восстановления пароля.
Репозиторий проекта на GitHub
Часть 1: Установка и настройка приложения
Часть 2: Настройка IDE и модульная структура
Приступим к переносу пользователей в базу данных.
Структура данных для таблицы
Можно создать таблицы базы вручную, но с этим будет много мороки. Нужно будет постоянно сравнивать рабочую и локальную базы и вносить в них каждый раз одинаковые изменения. Издеваться над собой не в наших планах, поэтому воспользуемся миграциями. Учтём, что в Yii2 немного другой формат запуска.
Запустим команду создания первой миграции и ответим yes
или y
:
mkdir migrations php yii migrate/create create_user_table
Если у нас есть папка migrations
, то увидим уведомление, что всё у нас получилось:
New migration created successfully.
Теперь откроем сгенерированный файл. Пока в нём нет никаких операций:
use yii\db\Schema; use yii\db\Migration; class m140916_150445_create_user_table extends Migration { public function up() { } public function down() { echo "m140916_150445_create_user_table cannot be reverted.\n"; return false; } }
Структуру таблицы мы можем позаимствовать из миграции расширенного шаблона с некоторыми изменениями. А именно, удалим лишние NOT NULL
и добавим индексы для оптимизации поиска:
use yii\db\Schema; use yii\db\Migration; class m140916_150445_create_user_table extends Migration { public function up() { $tableOptions = null; if ($this->db->driverName === 'mysql') { $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_general_ci ENGINE=InnoDB'; } $this->createTable('{{%user}}', [ 'id' => $this->primaryKey(), 'created_at' => $this->integer()->notNull(), 'updated_at' => $this->integer()->notNull(), 'username' => $this->string()->notNull(), 'auth_key' => $this->string(32), 'email_confirm_token' => $this->string(), 'password_hash' => $this->string()->notNull(), 'password_reset_token' => $this->string(), 'email' => $this->string()->notNull(), 'status' => $this->smallInteger()->notNull()->defaultValue(0), ], $tableOptions); $this->createIndex('idx-user-username', '{{%user}}', 'username', true); $this->createIndex('idx-user-email', '{{%user}}', 'email', true); } public function down() { $this->dropTable('{{%user}}'); } }
Здесь не случайно дополнительные параметры таблицы вынесены в переменную $tableOptions
и индексам даны уникальные имена в формате idx_{таблица}_{поле}
. Это делает миграции кроссплатформенными, то есть полностью рабочими на базах MySQL и PostgreSQL.
Теперь в командной строке (или в PhpMyAdmin) создадим новую базу данных:
mysql -uroot --execute='create database seokeys character set utf8;'
Проверим настройки соединения в config/common-local.php
:
'db' => [ 'dsn' => 'mysql:host=localhost;dbname=seokeys', 'username' => 'root', 'password' => '', 'tablePrefix' => 'keys_', ],
и применим эту миграцию:
php yii migrate/up
На вопрос ответим yes
и получим готовую таблицу:
Yii Migration Tool Creating migration history table "keys_migration"...done. Total 1 new migration to be applied: m140916_150445_create_user_table Apply the above migration? (yes|no) [no]:y *** applying m140916_150445_create_user_table > create table {{%user}} ... done (time: 0.006s) *** applied m140916_150445_create_user_table (time: 0.012s) Migrated up successfully.
Теперь по этой таблице нужно сгенерировать новую модель данных User
. Прверяем, что в web/index.php
константа YII_ENV
у нас выставлена в dev
и переходим по адресу http://localhost/gii
. Там переходим по ссылке Model Generator
и вбиваем имена таблицы и модели:
Теперь жмём Preview
, ставим галочку что хотим перезаписать старую модель и жмём Generate
:
Дополним свежесгенерированную модель. Первым делом, введём в неё константы для указания статуса, статический метод getStatusesArray
для получения их списка и метод getStatusName
для получения имени статуса пользователя. Эти методы пригодятся, например, при выводе пользователей в панели управления:
namespace app\modules\user\models; use Yii; use yii\base\NotSupportedException; use yii\behaviors\TimestampBehavior; use yii\db\ActiveRecord; use yii\helpers\ArrayHelper; /** * This is the model class for table "{{%user}}". * * @property integer $id * @property integer $created_at * @property integer $updated_at * @property string $username * @property string $auth_key * @property string $email_confirm_token * @property string $password_hash * @property string $password_reset_token * @property string $email * @property integer $status */ class User extends ActiveRecord { const STATUS_BLOCKED = 0; const STATUS_ACTIVE = 1; const STATUS_WAIT = 2; ... public function getStatusName() { return ArrayHelper::getValue(self::getStatusesArray(), $this->status); } public static function getStatusesArray() { return [ self::STATUS_BLOCKED => 'Заблокирован', self::STATUS_ACTIVE => 'Активен', self::STATUS_WAIT => 'Ожидает подтверждения', ]; } }
Теперь изменим сгенерированные автоматически правила валидации. Просто оставим только те поля, которые могут понадобиться при редактировании пользователя. Также переведём имена полей в методе attributeLabels
и введём проверку на правильность статуса:
class User extends ActiveRecord { public function rules() { return [ ['username', 'required'], ['username', 'match', 'pattern' => '#^[\w_-]+$#is'], ['username', 'unique', 'targetClass' => self::className(), 'message' => 'This username has already been taken.'], ['username', 'string', 'min' => 2, 'max' => 255], ['email', 'required'], ['email', 'email'], ['email', 'unique', 'targetClass' => self::className(), 'message' => 'This email address has already been taken.'], ['email', 'string', 'max' => 255], ['status', 'integer'], ['status', 'default', 'value' => self::STATUS_ACTIVE], ['status', 'in', 'range' => array_keys(self::getStatusesArray())], ]; } public function attributeLabels() { return [ 'id' => 'ID', 'created_at' => 'Создан', 'updated_at' => 'Обновлён', 'username' => 'Имя пользователя', 'email' => 'Email', 'status' => 'Статус', ]; } ... }
Также не забудем, что у нас имеются поля created_at
и updated_at
, в которые нужно вписывать дату при создании и каждом обновлении записи. Для этого нам пригодится уже имеющееся в Yii2 поведение:
use yii\behaviors\TimestampBehavior; class User extends ActiveRecord { ... public function behaviors() { return [ TimestampBehavior::className(), ]; } }
Про них в Yii2 я ещё не рассказывал, но для общего понимания не теряет актуальность статья про поведения в Yii1.
В старой модели наш класс заодно осуществлял хранение авторизованного пользователя в Yii::$app->user->identity
и для этого реализовывал интерфейс IdentityInterface
. Допишем методы, которые требует добавить этот интерфейс. Коды методов позаимствуем из того же расширенного шаблона приложения.
Читатель может спросить, зачем нам брать
yii-app-basic
и копировать в него код изyii-app-advanced
, если можно сразу взятьadvanced
. Здесь мы делаем простой сервис. Разделение на «лицо» и «админку» на разных поддоменах из advanced-шаблона нам не нужно, так как фактически у нас будет только админка. Так что остаётся либо взять advanced-шаблон, удалив из него за ненадобностью frontend-часть, либо сделать промежуточный гибрид: установитьsample
и скопировать в него идею конфигурации и БД-авторизацию из advanced. Как раз вторым путём мы и идём.
Итак, добавим наши методы:
use yii\web\IdentityInterface; class User extends ActiveRecord implements IdentityInterface { ... 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 function getId() { return $this->getPrimaryKey(); } public function getAuthKey() { return $this->auth_key; } public function validateAuthKey($authKey) { return $this->getAuthKey() === $authKey; } }
В старой модели также были findByUsername
и validatePassword
для работы класса LoginForm
. Добавим и их. В методе findByUsername
не будем искать по статусу User::STATUS_ACTIVE
. Так как у нас много статусов, то проверки удобнее будет совершать в контроллере или в форме LoginForm
. Для хэширования паролей будем использовать новый компонент Security
:
class User extends ActiveRecord implements IdentityInterface { ... /** * Finds user by username * * @param string $username * @return static|null */ public static function findByUsername($username) { return static::findOne(['username' => $username]); } /** * Validates password * * @param string $password password to validate * @return boolean if password provided is valid for current user */ public function validatePassword($password) { return Yii::$app->security->validatePassword($password, $this->password_hash); } }
Перед записью в базу для каждого пользователя нужно генерировать хэш пароля и дополнительный ключ автоматической аутентификации. Добавим методы их генерации и сделаем второй метод автозапускаемым при создании записи:
class User extends ActiveRecord implements IdentityInterface { /** * @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(); } public function beforeSave($insert) { if (parent::beforeSave($insert)) { if ($insert) { $this->generateAuthKey(); } return true; } return false; } }
Теперь добавим возможность смены пароля. Для этого у нас предусмотрено поле password_reset_token
. При запросе восстановления мы в это поле будем записывать уникальную случайную строку с временной меткой и посылать по электронной почте ссылку с этим хешэм на контроллер с действием активаци. А в контроллере уже найдём этого пользователя по данному хешу и поменяем ему пароль.
Добавим методы для генерации хеша и поиска по нему:
class User extends ActiveRecord implements IdentityInterface { ... /** * 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, 'status' => self::STATUS_ACTIVE, ]); } /** * Finds out if password reset token is valid * * @param string $token password reset token * @return boolean */ public static function isPasswordResetTokenValid($token) { if (empty($token)) { return false; } $expire = Yii::$app->params['user.passwordResetTokenExpire']; $parts = explode('_', $token); $timestamp = (int) end($parts); return $timestamp + $expire >= time(); } /** * 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; } }
Для регистрирующихся пользователей не помешает сделать подтверждение адреса электронной почты. Для этой цели добавим несколько методов для управления email_confirm_token
. При регистрации мы будем присваивать пользователю статус STATUS_WAIT
, генерировать ключ и отправлять ссылку с ключом на почту. А в контроллере (при переходе по этой ссылке) найдём пользователя по ключу и активируем:
class User extends ActiveRecord implements IdentityInterface { ... /** * @param string $email_confirm_token * @return static|null */ public static function findByEmailConfirmToken($email_confirm_token) { return static::findOne(['email_confirm_token' => $email_confirm_token, 'status' => self::STATUS_WAIT]); } /** * Generates email confirmation token */ public function generateEmailConfirmToken() { $this->email_confirm_token = Yii::$app->security->generateRandomString(); } /** * Removes email confirmation token */ public function removeEmailConfirmToken() { $this->email_confirm_token = null; } }
В файл params.php
добавим параметр, определяющий время «жизни» токена сброса пароля и дополнительное поле supportEmail
, значение которого будет использоваться в поле From
для исходящих писем:
return [ 'adminEmail' => '', 'supportEmail' => '', 'user.passwordResetTokenExpire' => 3600, ];
В личном же файле config/params-local.php
впишем свои значения:
return [ 'adminEmail' => 'admin@site.com', 'supportEmail' => 'info@site.com', ];
Модифицируем класс LoginForm
. А именно в валидатор validatePassword
добавим проверку статуса пользователя. В целях безопасности проверку будем осуществлять только при правильном пароле. Это не позволит взломщику узнать даже имена пользователей. Всё, что он увидит – это фразу «Неверное имя пользователя или пароль»:
<?php namespace app\modules\user\models; use Yii; use yii\base\Model; /** * LoginForm is the model behind the login form. */ class LoginForm extends Model { public $username; public $password; public $rememberMe = true; private $_user = false; /** * @return array the validation rules. */ public function rules() { return [ [['username', 'password'], 'required'], ['rememberMe', 'boolean'], ['password', 'validatePassword'], ]; } /** * Validates the username and password. * This method serves as the inline validation for password. */ public function validatePassword() { if (!$this->hasErrors()) { $user = $this->getUser(); if (!$user || !$user->validatePassword($this->password)) { $this->addError('password', 'Неверное имя пользователя или пароль.'); } elseif ($user && $user->status == User::STATUS_BLOCKED) { $this->addError('username', 'Ваш аккаунт заблокирован.'); } elseif ($user && $user->status == User::STATUS_WAIT) { $this->addError('username', 'Ваш аккаунт не подтвежден.'); } } } /** * Logs in a user using the provided username and password. * @return boolean whether the user is logged in successfully */ public function login() { if ($this->validate()) { return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600*24*30 : 0); } else { return false; } } /** * Finds user by [[username]] * * @return User|null */ public function getUser() { if ($this->_user === false) { $this->_user = User::findByUsername($this->username); } return $this->_user; } }
Теперь позаимствуем класс формы регистрации пользователей SignupForm
из advanced-шаблона, дополнив его выводом капчи verifyCode
, установкой статуса User::STATUS_WAIT
, вызовом генерации токена подтверждения и отправке этого токена по почте:
namespace app\modules\user\models; use yii\base\Model; use Yii; /** * Signup form */ class SignupForm extends Model { public $username; public $email; public $password; public $verifyCode; public function rules() { return [ ['username', 'filter', 'filter' => 'trim'], ['username', 'required'], ['username', 'match', 'pattern' => '#^[\w_-]+$#i'], ['username', 'unique', 'targetClass' => User::className(), 'message' => 'This username has already been taken.'], ['username', 'string', 'min' => 2, 'max' => 255], ['email', 'filter', 'filter' => 'trim'], ['email', 'required'], ['email', 'email'], ['email', 'unique', 'targetClass' => User::className(), 'message' => 'This email address has already been taken.'], ['password', 'required'], ['password', 'string', 'min' => 6], ['verifyCode', 'captcha', 'captchaAction' => '/user/default/captcha'], ]; } /** * Signs user up. * * @return User|null the saved model or null if saving fails */ public function signup() { if ($this->validate()) { $user = new User(); $user->username = $this->username; $user->email = $this->email; $user->setPassword($this->password); $user->status = User::STATUS_WAIT; $user->generateAuthKey(); $user->generateEmailConfirmToken(); if ($user->save()) { Yii::$app->mailer->compose('@app/modules/user/mails/emailConfirm', ['user' => $user]) ->setFrom([Yii::$app->params['supportEmail'] => Yii::$app->name]) ->setTo($this->email) ->setSubject('Email confirmation for ' . Yii::$app->name) ->send(); return $user; } } return null; } }
Немного дополним...
В исходном приложении без модульной структуры все представления писем хранятся в папке
@app/modules/user/mails
для файла письмаemailConfirm.php
.
Создадим файл modules\user\mail\emailConfirm.php
. В него пометстим приветствие и ссылку:
<?php use yii\helpers\Html; /* @var $this yii\web\View */ /* @var $user app\modules\user\models\User */ $confirmLink = Yii::$app->urlManager->createAbsoluteUrl(['user/default/email-confirm', 'token' => $user->email_confirm_token]); Здравствуйте, Html::encode($user->username) ! Для подтверждения адреса пройдите по ссылке: Html::a(Html::encode($confirmLink), $confirmLink) Если Вы не регистрировались у на нашем сайте, то просто удалите это письмо.
Остальные формы PasswordResetRequestForm
и PasswordResetForm
можно взять из папки frontend\models advanced-приложения. Просто заменим в них пространства имён:
namespace frontend\models; use common\models\User; use yii\base\Model; /** * Password reset request form */ class PasswordResetRequestForm extends Model ...
на другие адреса:
namespace app\modules\user\models; use app\modules\user\models\User; use yii\base\Model; /** * Password reset request form */ class PasswordResetRequestForm extends Model ...
И, как мы уже говорили, изменим адрес до представления письма на полный:
Yii::$app->mailer->compose('@app/modules/user/mails/passwordReset', ['user' => $user])
И от себя добавим сервис подтверждения Email-адреса EmailConfirm
по примеру формы сброса пароля. В нём мы будем искать пользователя по токену, вызывая User::findByEmailConfirmToken
, активировать его и очищать поле email_confirm_token
:
namespace app\modules\user\models; use yii\base\InvalidParamException; use Yii; class EmailConfirm { /** * @var User */ private $_user; /** * Creates a form model given a token. * * @param string $token * @param array $config * @throws \yii\base\InvalidParamException if token is empty or not valid */ public function __construct($token, $config = []) { if (empty($token) || !is_string($token)) { throw new InvalidParamException('Отсутствует код подтверждения.'); } $this->_user = User::findByEmailConfirmToken($token); if (!$this->_user) { throw new InvalidParamException('Неверный токен.'); } parent::__construct($config); } /** * Confirm email. * * @return boolean if email was confirmed. */ public function confirmEmail() { $user = $this->_user; $user->status = User::STATUS_ACTIVE; $user->removeEmailConfirmToken(); return $user->save(); } }
Теперь модифицируем DefaultController
модуля user
:
namespace app\modules\user\controllers; use app\modules\user\models\EmailConfirm; use app\modules\user\models\LoginForm; use app\modules\user\models\PasswordResetRequestForm; use app\modules\user\models\PasswordResetForm; use app\modules\user\models\SignupForm; use yii\base\InvalidParamException; use yii\filters\AccessControl; use yii\filters\VerbFilter; use yii\web\BadRequestHttpException; use yii\web\Controller; use Yii; class DefaultController extends Controller { public function behaviors() { return [ 'access' => [ 'class' => AccessControl::className(), 'only' => ['logout', 'signup'], 'rules' => [ [ 'actions' => ['signup'], 'allow' => true, 'roles' => ['?'], ], [ 'actions' => ['logout'], 'allow' => true, 'roles' => ['@'], ], ], ], 'verbs' => [ 'class' => VerbFilter::className(), 'actions' => [ 'logout' => ['post'], ], ], ]; } public function actions() { return [ 'captcha' => [ 'class' => 'yii\captcha\CaptchaAction', 'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null, ], ]; } public function actionLogin() { if (!Yii::$app->user->isGuest) { return $this->goHome(); } $model = new LoginForm(); if ($model->load(Yii::$app->request->post()) && $model->login()) { return $this->goBack(); } else { return $this->render('login', [ 'model' => $model, ]); } } public function actionLogout() { Yii::$app->user->logout(); return $this->goHome(); } public function actionSignup() { $model = new SignupForm(); if ($model->load(Yii::$app->request->post())) { if ($user = $model->signup()) { Yii::$app->getSession()->setFlash('success', 'Подтвердите ваш электронный адрес.'); return $this->goHome(); } } return $this->render('signup', [ 'model' => $model, ]); } public function actionEmailConfirm($token) { try { $model = new EmailConfirm($token); } catch (InvalidParamException $e) { throw new BadRequestHttpException($e->getMessage()); } if ($model->confirmEmail()) { Yii::$app->getSession()->setFlash('success', 'Спасибо! Ваш Email успешно подтверждён.'); } else { Yii::$app->getSession()->setFlash('error', 'Ошибка подтверждения Email.'); } return $this->goHome(); } public function actionPasswordResetRequest() { $model = new PasswordResetRequestForm(); if ($model->load(Yii::$app->request->post()) && $model->validate()) { if ($model->sendEmail()) { Yii::$app->getSession()->setFlash('success', 'Спасибо! На ваш Email было отправлено письмо со ссылкой на восстановление пароля.'); return $this->goHome(); } else { Yii::$app->getSession()->setFlash('error', 'Извините. У нас возникли проблемы с отправкой.'); } } return $this->render('passwordResetRequest', [ 'model' => $model, ]); } public function actionPasswordReset($token) { try { $model = new PasswordResetForm($token); } catch (InvalidParamException $e) { throw new BadRequestHttpException($e->getMessage()); } if ($model->load(Yii::$app->request->post()) && $model->validate() && $model->resetPassword()) { Yii::$app->getSession()->setFlash('success', 'Спасибо! Пароль успешно изменён.'); return $this->goHome(); } return $this->render('passwordReset', [ 'model' => $model, ]); } }
Оповешения об успешности операции отправляются в сессию в виде одноразовых Flash-сообщений. Для их вывода в шаблоне добавим готовый виджет Alert в папку widgets
и пространство имён app\widgets
и подключим его к шаблону views\layouts\main
после хлебных крошек:
<?php ... use yii\widgets\Breadcrumbs; use app\widgets\Alert; ... <div class="container"> Breadcrumbs::widget([ 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], ]) Alert::widget() $content </div> ...
Нам осталось скопировать представления login
, passwordReset
и passwordResetRequest
из похожих в папке frontend/views/site. Например, представление login.php
будет выглядеть так:
use yii\helpers\Html; use yii\bootstrap\ActiveForm; /* @var $this yii\web\View */ /* @var $form yii\bootstrap\ActiveForm */ /* @var $model \app\modules\user\models\LoginForm */ $this->title = 'Login'; $this->params['breadcrumbs'][] = $this->title; <div class="user-default-login"> <h1> Html::encode($this->title) </h1> <p>Please fill out the following fields to login:</p> <div class="row"> <div class="col-lg-5"> $form = ActiveForm::begin(['id' => 'login-form']); $form->field($model, 'username') $form->field($model, 'password')->passwordInput() $form->field($model, 'rememberMe')->checkbox() <div style="color:#999;margin:1em 0"> If you forgot your password you can Html::a('reset it', ['password-reset-request']) . </div> <div class="form-group"> Html::submitButton('Login', ['class' => 'btn btn-primary', 'name' => 'login-button']) </div> ActiveForm::end(); </div> </div> </div>
Также нужно добавить свой шаблон письма modules/user/mails/passwordReset.php
:
<?php use yii\helpers\Html; /* @var $this yii\web\View */ /* @var $user app\modules\user\models\User */ $resetLink = Yii::$app->urlManager->createAbsoluteUrl(['user/default/password-reset', 'token' => $user->password_reset_token]); Здравствуйте, Html::encode($user->username) ! Пройдите по ссылке, чтобы сменить пароль: Html::a(Html::encode($resetLink), $resetLink)
Представление modules/user/views/default/signup.php
тоже позаимствуем, но скопируем в него вывод капчи из представления модуля contact
с новым значением параметра captchaAction
:
use yii\captcha\Captcha; use yii\helpers\Html; use yii\bootstrap\ActiveForm; /* @var $this yii\web\View */ /* @var $form yii\bootstrap\ActiveForm */ /* @var $model app\modules\user\models\SignupForm */ $this->title = 'Signup'; $this->params['breadcrumbs'][] = $this->title; <div class="user-default-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"> $form = ActiveForm::begin(['id' => 'form-signup']); $form->field($model, 'username') $form->field($model, 'email') $form->field($model, 'password')->passwordInput() $form->field($model, 'verifyCode')->widget(Captcha::className(), [ 'captchaAction' => '/user/default/captcha', 'template' => '<div class="row"><div class="col-lg-3">{image}</div><div class="col-lg-6">{input}</div></div>', ]) <div class="form-group"> Html::submitButton('Signup', ['class' => 'btn btn-primary', 'name' => 'signup-button']) </div> ActiveForm::end(); </div> </div> </div>
Дополним список действий для ЧПУ в config/common.php
. А именно изменим строку, которая ранее представляла действие login
:
'urlManager' => [ 'class' => 'yii\web\UrlManager', 'enablePrettyUrl' => true, 'showScriptName' => false, 'rules' => [ '' => 'main/default/index', 'contact' => 'main/contact/index', '<_a:error>' => 'main/default/<_a>', '<_a:(login|logout|signup|email-confirm|request-password-reset|password-reset)>' => 'user/default/<_a>', '<_m:[\w\-]+>/<_c:[\w\-]+>/<_a:[\w\-]+>/<id:\d+>' => '<_m>/<_c>/<_a>', '<_m:[\w\-]+>/<_c:[\w\-]+>/<id:\d+>' => '<_m>/<_c>/view', '<_m:[\w\-]+>' => '<_m>/default/index', '<_m:[\w\-]+>/<_c:[\w\-]+>' => '<_m>/<_c>/index', ], ],
и добавим пункт регистрации в главное меню макета views/layouts/main.php
:
echo Nav::widget([ 'options' => ['class' => 'navbar-nav navbar-right'], 'items' => array_filter([ ['label' => 'Home', 'url' => ['/main/default/index']], ['label' => 'Contact', 'url' => ['/main/contact/index']], Yii::$app->user->isGuest ? ['label' => 'Sign Up', 'url' => ['/user/default/signup']] : false, Yii::$app->user->isGuest ? ['label' => 'Login', 'url' => ['/user/default/login']] : ['label' => 'Logout (' . Yii::$app->user->identity->username . ')', 'url' => ['/user/default/logout'], 'linkOptions' => ['data-method' => 'post']], ]), ]);
Обратите внимание, что мы добавили функцию array_filter
, чтобы можно было выводить либо скрывать пункты по выбору.
Автозаполнение формы обратной связи
Если теперь авторизованный пользователь войдёт на страницу обратной связи, то ему всё равно нужно будет заполнить поля имени и электронного адреса. Добавим автоподстановку значений в поля формы:
namespace app\modules\main\controllers; use app\modules\main\models\ContactForm; use yii\web\Controller; use Yii; class ContactController extends Controller { ... public function actionIndex() { $model = new ContactForm(); if ($user = Yii::$app->user->identity) { /** @var \app\modules\user\models\User $user */ $model->name = $user->username; $model->email = $user->email; } if ($model->load(Yii::$app->request->post()) && $model->contact(Yii::$app->params['adminEmail'])) { Yii::$app->session->setFlash('contactFormSubmitted'); return $this->refresh(); } else { return $this->render('index', [ 'model' => $model, ]); } } }
Теперь авторизованному пользователю будет легче.
Консольное управление
Теперь у нас имеется таблица для хранения пользователей и вся инфраструктура для авторизации. Теперь можно попробовать зарегистрироваться на своём же сайте. Но удобнее добавить свою консольную команду для управления пользователями. Добавим в неё примитивный набор действий:
namespace app\commands; use app\modules\user\models\User; use yii\base\Model; use yii\console\Controller; use yii\console\Exception; use yii\helpers\Console; class UsersController extends Controller { public function actionIndex() { echo 'yii users/create' . PHP_EOL; echo 'yii users/remove' . PHP_EOL; echo 'yii users/activate' . PHP_EOL; echo 'yii users/change-password' . PHP_EOL; } public function actionCreate() { $model = new User(); $this->readValue($model, 'username'); $this->readValue($model, 'email'); $model->setPassword($this->prompt('Password:', [ 'required' => true, 'pattern' => '#^.{6,255}$#i', 'error' => 'More than 6 symbols', ])); $model->generateAuthKey(); $this->log($model->save()); } public function actionRemove() { $username = $this->prompt('Username:', ['required' => true]); $model = $this->findModel($username); $this->log($model->delete()); } public function actionActivate() { $username = $this->prompt('Username:', ['required' => true]); $model = $this->findModel($username); $model->status = User::STATUS_ACTIVE; $model->removeEmailConfirmToken(); $this->log($model->save()); } public function actionChangePassword() { $username = $this->prompt('Username:', ['required' => true]); $model = $this->findModel($username); $model->setPassword($this->prompt('New password:', [ 'required' => true, 'pattern' => '#^.{6,255}$#i', 'error' => 'More than 6 symbols', ])); $this->log($model->save()); } /** * @param string $username * @throws \yii\console\Exception * @return User the loaded model */ private function findModel($username) { if (!$model = User::findOne(['username' => $username])) { throw new Exception('User not found'); } return $model; } /** * @param Model $model * @param string $attribute */ private function readValue($model, $attribute) { $model->$attribute = $this->prompt(mb_convert_case($attribute, MB_CASE_TITLE, 'utf-8') . ':', [ 'validator' => function ($input, &$error) use ($model, $attribute) { $model->$attribute = $input; if ($model->validate([$attribute])) { return true; } else { $error = implode(',', $model->getErrors($attribute)); return false; } }, ]); } /** * @param bool $success */ private function log($success) { if ($success) { $this->stdout('Success!', Console::FG_GREEN, Console::BOLD); } else { $this->stderr('Error!', Console::FG_RED, Console::BOLD); } echo PHP_EOL; } }
Теперь можно выполнить:
php yii users/create
и ввести данные пользователя.
Что получилось
В итоге, на этот раз мы изменили и добавили файлы:
commands/ UsersController.php components/ widgets/ Alert.php config/ common.php params.php params-local.php mail/ layouts/ migrations/ m140916_150445_create_user_table.php modules / user/ controllers/ DefaultController models/ EmailConfirm.php LoginForm.php PasswordResetRequestForm.php PasswordResetForm.php SignupForm.php User.php mails/ emailConfirm.php passwordReset.php views/ default/ login.php passwordResetRequest.php passwordReset.php signup.php views/ layouts/ main.php
Получился свой собственный шаблон приложения. Его можно уверенно использовать и для будущих проектов.
Спасибо за статью, Дмитрий. Очень подробно, в самый раз для освоения новой версии Yii.
В Yii1.1 есть очень удобные модули для быстрого развертывания приложений User и Right, а для Yii2 есть что-то подобное?
Посмотрите, например, http://yii2-user.readthedocs.org/en/latest/
Спасибо за наводку! Сам проглядел столь полезный модуль.
Не знаю как и кому, а мне было бы приятно при регистрации указывать своё реальное имя,
по-русски, и не важно сколько Людей с моим именем зарегистрировалось до меня, придумывать какие-то логины, по мне так глупо и бесполезно, никто кроме вас не знает каким именно электронным адресом вы воспользовались, и какой пароль введёте. По мне так удобнее восстановить только пароль, введя свой номер телефона и получить пароль по SMS или по e-mail чем вспоминать, а какой же логин я вводил, и действительно не удобно как-то и не этично парить мозги пользователю каким-то там логином, имя он своё знает, да и почту наизусть наверняка выучил, пароль можно снегерировать сложный, а если забыл я уже писал как восстановить, так же можно сделать? И получать письма с обращением к вам по имени а не по логину гораздо приятнее, я это реализовывал на самописном движке, как это реализовать на Yii2 ???
Убрать поле username и для логина использовать email.
Дмитрий, я то это и так понимаю, не отразится ли это на системе безопасности и на валидацию, ведь по сути мне придётся переделать весь код, заменив username на email, и по логике вещей username содержит правило, запрещающее создавать пользователей с одинаковым логином. Плюс такое правило применяется в БД, указывая строку как уникальное значение. Есть ли способ с наименьшими жертвами, чтобы логин остался, но являлся реальным именем пользователя, который можно ввести по-русски ?
1. Убираете эти ограничения из rules():
2. Удаляете уникальный индекс из базы.
3. Заменяете User::findByUsername на findByEmail в LoginForm.
Спасибо! Отличная статья.
Версии меняются - миграции все теже) За статью спасибо!)
Статья супер! Ждем, с нетерпением, продолжения ))
Коллега, отлично пишете. Жду продолжения, а пока читаю английский getstarted.
В User::actionSignup не хватает отправки письма с токеном для подтверждения e-mail.
Это производится в SignupForm.
Вопрос по ЧПУ
- в конфиге - confirm-email - > на user/default/_a (т.е. на confirm-email)
- в письме - у нас прямая ссылка (не совсем красивая) - createAbsoluteUrl(['user/default/confirm-email',
- если убрать default из ссылки - то сработает роутинг и получим ошибку. Т.к. метода confirm-email нет в контроллере.
вопрос - зачем мы тогда в ЧПУ добавляли confirm-email|request-password-reset|reset-password, раз не используем в роуте?
UPD.
в письме надо указать ..... createAbsoluteUrl(['confirm-email',.... - тогда сработает роутинг.
Здесь user/default/confirm-email – это не прямая ссылка, а обращение к actionConfirmEmail. В Yii2 надо обращаться так.
а письмо приходит после регистрации юзера?
У меня после регистрации юзера писем нет, но зато он автоматом проходит аутентификацию и меню появляется logout
Изменил SignupForm и actionSignup, чтобы автоматом не логинился.
Если в config/common-local.php стоит 'useFileTransport' => true, то письма складываются в runtime/mail.
а спасибо большое, оказывается все письма приходят, просто я по привычке искал их во временной папке опен сервера :(
Будем разбираться дальше......
Дмитрий, ждем продолжения........очень четко и доступно объясняете.
да, приходит - посмотрите в runtime
а чтобы не авторизировался - при сохраниении поставьте статус STATUS_WAIT
Всем привет.
Спасибо автору за статьи, очень помогает в изучении Yii2, особенно тем кто с ним только начинает работать.
Делал все как написано, при регистрации юзера перебрасывает на главную, в БД сохраняется только created_at и updated_at и status.
Больше никаких данных не сохраняется, письмо не высылается, сообщений о том что выслано письмо и юзер зареген на выводится.
что не так и куда копать?
Вопрос решился....сам накосячил
Вопрос решился с записью данных в БД, а вот письма и сообщений нет :(
Здравствуйте, спасибо за статью. На всякий случай имхо в signup() надо добавить$user->status = User::STATUS_WAIT;
Еще вот такая ситуация - допустим, юзер запросил регистрацию, а письмо удалил, и теперь регистрируется по новой, а такой email уже есть. Как-то может учесть эту ситуацию? Наверное, лучше просить на 1 шаге только почту, и учитывать её статус - если STATUS_WAIT, отправлять письмо повторно.
Да, изменил SignupForm и actionSignup. А во втором случае можно добавить:
Дмитрий, а куда писать эту строчку? в signup? "после if ($this->validate())"
Вместо
Если вместо этой строчки написать, то до нее это условие не проходит. У нас же вначале идет валидация, то есть он сразу проверяет есть ли уже этот пользователь. if ($this->validate()) и только потом уже создает $user = new User(); значит надо до валидации еще эту проверку делать, и если пользователь есть то делать без валидации...
Здравствуйте.А что если юзер по ошибке указал другой email и хочет сделать регистрацию заново, но не сможет потому что при регистрации нужно указать еще один уникальный поля иин
Можно привязать уникальность к статусу.
По поводу подтверждения емайла.
Переходим по ссылке присланной в письме и получаем вот такое сообщение:
Calling unknown method: app\modules\user\models\ConfirmEmailForm::findOne()
Долго сравнивал свой код с кодом из статьи, но ничего не нашел.
В итоге, я решил а почему static::findOne если мы ищем данные по юзеру и написал User:findOne и все работало - выводится сообщение что емаил подтвержден в БД поле email_confirm_token удаляется.
Дмитрий. это у вас ошибка или я что не так понял?
А где у Вас этот static::findOne? В каком классе?
Там же где и у вас
вообщем все работает и с static::findOne
Наверное я что-то делал не так.
Сброс пароля тоже работает.
А вот залогиниться не могу - пишет Incorrect username or password.
Не могли вы рассказать об авторизации......целый день пытаюсь и ни как не получается
А в базе данных у пользователя точно активный статус?
да, активный
Тогда посмотрите в методе LoginForm::validatePassword находится ли пользователь, проверяется ли пароль.
пользователь находится, в вот Yii::$app->security->validatePassword($password, $this->password_hash); возвращает false
Решение нашел на форуме yiiframework.com - вся проблема была длине поля password_hash. Увеличил размер поля и все заработало :) Оказывается не один я такой
для сброса пароля
в PasswordResetRequestForm надо заменить не только namespace но и класс в правилах:
заменить на
И вообще для таких случаев лучше использовать конструкцию:
Дмитрий подскажите следующий момент.
В регистрации и авторизации юзера заложена идея статусов (ждет подтверждения/заблокирован), но в
при авторизации ищется юзер с логином и статусом активен.
Как сделать чтобы искался юзер по логину и потом была проверка статуса - заблокирован/ждет подтверждения и выводились определенные сообщения? Т.е. если статус WAIT то выводим сообщение что на емаил было отправлено письмо и ссылка на повторное отправление письма.
Буду благодарен всем кто подскажет. Yii только начал изучать поэтому столько вопросов.....
Сам задал вопрос. сам и отвечу :)
Решил таким способом
Может есть еще более проще вариант?
Метод setFlash ничего не возвращает, поэтому подставлять его в return не очень корректно.
Помимо этого в методе появилось побочное действие (запись в сессию). По моральным соображениям это противоречит принципу единой ответственности, а с технической точки зрения вызывает проблемы с использованием метода findByUsername в консольных командах, где компонента сессии нет вообще.
Дополнил статью. А именно убрал указание статуса из findByUsername:
и все проверки перенёс в LoginForm:
ну тут да я ошибся с моделью - работаем с моделью LoginForm, а проверку запихнул в User..... тем нарушил принцип MVC.... если я правильно понимаю
В таком случае в форме логина нужно добавить метод повторной отсылки письма, если письмо не пришло или было удалено...
Что-то вроде - "Не получили письмо об активации? Прислать еще раз". тут надо будет вводить e-mail, куда прислать
Или из сообщения об ошибке - $this->addError('username', 'Ваш аккаунт не подтвежден. Прислать письмо еще раз');
тут e-mail вводить не надо. Он уже известен в ходе проверки...
Ну это уже как захотите.
Дмитрий, подскажите пожалуйста. У нас в контроллере, как я понял, разрешено только два действия
а выполняются, почему то все action-s...
восстановление пароля, отправка письма и т.п...
Почему так, и для чего тогда нужно это поведение?
Спасибо
Only обозначает, к каким действиям применяются перечисленные правила контроля доступа. Выйти может только залогиненный пользователь (условня роль @), а зарегистрироваться – только гость (?). Остальные действия правил не имеют и работают как обычно.
По консоли вопрос.
Контроллер у нас называется Users.
В индексе -
echo 'yii user/create' . PHP_EOL;
echo 'yii user/remove' . PHP_EOL;
Переименовал контроллер и файл в UserController
пишу в консоли - php yii user/create - выдается ошибка, что неизвестная команда...
Да, имя user оказалось занято.
Дмитрий огромное спасибо, Будет ли часть по использованию RBAC'a ?
Да, будет.
Оень классный материал по Yii2. Спасибо.
Жаль только, что
Тут выходит по сути фронтенд часть за админку.
Можно ли на этой модульной структуре реализовать админ часть для модулей, например, чтобы доспуп к ней был /backend/contact или /backend/user и т.п.?
Я так полагаю, нужно в каталогах controllers, models и views создать каталоги backend, соответственно и тут вопросы уже как роуты прописать или что нужно настаивать.
А advanced шаблон сложноватый выходит, там и common присутствует.
Не подскажете как реализовать?
В отличие от Yii1 с пространствами имён можно размещать всё как угодно. Например, можно создать папку backend и в неё поместить эти модули.
В представлении login.php как правильно указать url?
стандартный урл ествественно приводит к 404.
user/default/request-password-reset
нет, пробовал, все равно выдает 404 Unable to resolve the request "user/user/default/request-password-reset".
<?= Html::a('reset it', ['user/default/request-password-reset']) ?> выдает ошибку 404 Unable to resolve the request "user/user/default/request-password-reset".
Слеш "/" вначале постаьте.
Спасибо! Извиняюсь!
Достаточно ввести:
<?= Html::a('сбросить', ['default/request-password-reset']) ?>
этого хватит :-)
Да, так лучше. Спасибо!
Дмитрий, когда продолжение?
Хотелось бы по подробнее узнать про виджет AvtiveForm, его настроку, т.е. использование configField
К формам скоро подберёмся.
Пару моментов:
В PasswordResetRequestForm.php есть проверка if (!User::isPasswordResetTokenValid($user->password_reset_token)), но в модели User никакого isPasswordResetTokenValid нет. Пришлось подтянуть из advanced app Yii.
Ещё в статье не хватает информации о некоторых местах, где надо заменить пути к классам. Пришлось фиксить руками. Часть этих моментов есть в комментарии, но было бы неплохо обновить статью.
Добавил isPasswordResetTokenValid. А по изменению путей – меняются у всех классов, которые перекладываются.
Добавил капчу в форму регистрации (метод DefaultController::actions(), класс SignupForm и представление signup.php).
Маршрут /logout выдал ошибку 400 'Unable to verify your data submission.'
Оказалось что в app/layouts/main.php в секцию нужно добавить <?= Html::csrfMetaTags() ?> потому что поздние версии больше не генерируют CSRF мета тег
В конфиг нужно добавить строчку 'loginUrl' => ['user/default/login'], для корректного переброса на страницу авторизации
Спасибо! Добавил.
Когда пытаюсь применить миграцию, вылезает такие ошибки http://c2n.me/jkKSHo
Не подскажешь в чем тут проблема?
Не найдены настройки компонента db.
как насчет сделать авторизацию через соц. сети?
Может и сделаю. Раньше имел дело только с uLogin. Там у них есть готовый код для Yii1.
Может можно переделать его с yii1 на yii2, реально нужная вещь.
Есть проблема с кодировкой текста, отправленным виджетом Alert. Все файлы перекинул в UTF8, не помогло. Да и вообще, чтобы русский текст нормально отображался из вьюх, приходится их пересохранять в UTF8. Может есть более простой способ?
Я всегда использую для файлов и прочего UTF-8. Тогда все серверы и программы работают нормально.
Пользуюсь NetBeans IDE , там изначально настраивается кодировка, какую пожелаете, и рефакторинг делать проще, и прописывать узы, и прочие рабочие моменты проще. После того как Notepad++ переломал мне весь проект, я отказался от его использования, да и менять кодировку с BOM или без BOM устал, скачайте NetBeans IDE , тем более что он бесплатный, и радуйтесь жизни ))
Подробно ражевано, спасибо!
Очень жду RBAC!
Дмитрий, спасибо за шикарный цикл статей!
У меня для MS SQL (работаю через PDO dblib) скрипт миграции при создании таблицы user не воспринял инструкцию NULL для трех полей и все равно создали их NOT NULL.
Сам код миграции правильный:
А если попробовать
А так всё верно. Спасибо!
И Вам спасибо! Исправил в статье.
А как сделать, что бы письма сохранялись в читаемом виде?
У меня в папке @runtime/mail сохраняются письма в закодированном виде, типо такого =D0=BD=D0=B0=D1=88=D0=B5=D0=BC =D1=81=D0=B0=D0=
И еще в дебаг панеле, в разделе Mail нету ни одного письма. Это нормально?
Письма можно открывать с помощью какой-нибудь программы-почтовика. А про дебаг-панель особо не знаю. Возможно, что она отображает только реально отправленные.
Спасибо большое за прекрасные статьи.
У меня такой вопрос. Шаблон выбрал basic, скачал класс Alert и бросил в папку components и возникает проблема с
<?= Alert::widget() ?> какие namespace прописать для класса Alert?
Спасибо.
> бросил в папку components
Корневая папка это "app". Значит "namespace app\components" и "use app\components\Alert".
В общем вот как сделал, вроде завелось :-)
В файле Alert.php заменил вот это:
вот таким кодом
и в файле main.php который в layouts/ прописал
Дмитрий, если помимо модуля user нам еще потребуется создать модуль admin для управления юзерами (и прочих админских дел) - где будет располагаться модель User? Ведь не создавать же ее в каждом модуле.
Оставим в user.
Т.е. для модулей не существует такого понятия, как общие модели?
Можно как угодно делать. Можно вынести в корневую папку models, можно брать модели из других модулей, можно в модуле admin насоздавать заглушек-наследников и работать с ними:
В целом ясно, что все кастомизируется под каждый отдельный проект, но хотелось бы узнать про уже зарекомендовавшие себя практики.
В частности, интересно узнать как "в народе" принято реализовывать админку при модульной структуре. Чтобы было удобно и при разработке и при дальнейшем разделении прав и доступов.
В функции signup() перестраховались:
у нас вроде beforSave() генерится этот ключик?
Дмитрий, немного не по теме, но все же спрошу, подскажите пожалуйста, как мне лучше всего переопределить bootstrap css && js таким образом, что бы на всех страницах были подключены именно мои бутстрап файлы (хочу подключить немного другую версию, нежели предлагают разрабы уии)? В AppAsset подключаю свои скрипты, в бандл оставляю только 'yii\web\YiiAsset', но другие виджеты всеравно, в случае использования, кидают "родные" бутстрапы ниже моих.
Создайте у себя в components классы yii\bootstrap\BootstrapAsset и yii\bootstrap\BootstrapPluginAsset (в том же пространстве имён, но со своими путями) вроде:
Потом в index.php перед созданием приложения сделайте подмену файлов классов:
Спасибо! Это работает!
Здравствуйте, Дмитрий!
Огромное спасибо за ваш труд!
Я недавно приступил к изучению фреймворков в принципе и сразу начал с Yii.
Ваши статьи очень просты, логичны, и доходчивы.
Сейчас я столкнулся с проблемой.
При попытке выполнения "php yii migrate/up"
получаю ошибку:
"PHP Fatal error: Class 'm150108_110342_create_user_table' not found in .../vendor/yiisoft/yii2/console/controllers/MigrateController.php on line 118"
Пока у меня не получилось разобраться самостоятельно.
Буду признателен за любую подсказку.
А имя класса и имя файла миграции совпадают?
Верное замечание.
Я даже не подумал проверить здесь.
Был не внимателен при копипасте.
Спасибо за помощь!
Ещё раз здравствуйте!
Дополните, пожалуйста, вот это
"Дополним список действий для ЧПУ в config/common.php:"
уточнением, что новые правила нужно добавлять вверх списка значений массива. Это может надолго поставить в тупик новичка (вроде меня :) )...
Если вы опишите подробнее работу с UrlManager -> rules - это будет восхитительно :)
Быстро найти подробное объяснение по работе с регулярными выражениями в нём мне не удалось.
Спасибо! Дополнил. А про rules в Yii1 рассказывал в статье о маршрутизации.
Спасибо! :)
Здравствуйте. В глазах сосуды лопаются, не знаю в чем дело. Капча одинаковая что на странице регистрации, что на странице контактной формы.
В файле контроллере SiteController.php
В файле SignupForm.php
В файле signup.php
Спасибо за статью.
Буду премного благодарен за помощь.
Одинаковая, так как берётся из одного действия site/captcha. Сделайте ещё одно действие:
и укажите новое в captchaAction.
Вот тут возможно опечатка или что-то обновилось:
у Вас: PasswordResetForm
на github: ResetPasswordForm.php
Спасибо! Исправил.
Так же если кому-то интересно на какой VPS сервер поставить сайт, то без задумки выбираем
rootwelt
Так же на этой странице в низу Вас будет ждать мой презент в виде промо кода на 50% скидку.
А почему для полей created_at, updated_at выбран тип INTEGER. Я понимаю чо так было в шаблоне. Но все же в чем преимущества. Ведь тогда недоступны некоторые выборки из базы и операции по преобразованию времени самой БД?
Со встроенными типами TIMESTAMP и DATETIME и операциями работать можно, но в разных базах эти операции разные. А TIMESTAMP ещё и сам часовую зону учитывает, что может приводить к погрешностям. Да и всё равно при выводе на страницу их в нужный формат преобразовывать приходится.
А INT работает всегда и во всех базах. А все нужные выборки по дате можно успешно эмулировать переводя введённую пользователем дату c помощью strtotime(...).
А если необходимо будет сделать вывод с группировкой по дате (CAST(timestamp_value AS DATE))? MySQL и остальные смогут использовать индексы?
С вычисляемыми полями можно делать индексы в PostgreSQL. Если надо часто группировать по дате без времени в MySQL, то лучше рядом с timestamp для этой цели добавить ещё одну колонку формата DATE.
Спасибо за статью.
Но я не совсем понял , письма должны приходить только в папку
C:\work.ru\runtime\mail ?
Т.е. регистрируюсь , приходит письмо в папку, а как подтвердить и чтобы статус стал активный?
В базе пользователь сохранился , поле email_confirm_token не пустое.
Когда логинюсь пишет , что нужно подтвердить.
Вопрос: как сделать, чтобы отправлял на реальную почту и после подтверждения я мог зайти на свой сайт?
В config/common-local.php у компонента mailer уберите строку:
Не помогло, наверно где-то накосячил, у меня свой класс не User, а Users.
Вроде все исправил, но на реальную почту не приходит, более того не работает отправка стандартной формы Contact из basic , пишет что отправлено все ок, на почте опять нету.
Есть ссылка на репозиторий этой статьи ,буду копаться что не так?
Можно в личку.
Заранее спасибо.
Я обычно формирую письма так:
При этом для надёжности отправляю письма по SMTP. Например, для того же Яндекса:
чтобы supportEmail совпадал с username. Иначе почтовиками письмо с несовпадающим исходящим адресом определяется как спамное, а на MailRu вообще не доходит.
Спасибо,заработало.
Еще 2 вопроса:
1) получается что пароль мне нужно хранить в открытом виде, может есть какие-нибудь хитрости чтобы в качестве той же хешь суммы хранить как в базе?
Как это сделать, какой нибудь примерный код в какую сторону копать.
2) После того как письмо пришло , при нажатии на ссылку в письме кидает на сайт, но на сайте пишет что такой страницы нет.
ссылка вида /index.php?r=users%2Fconfirm-email&token=Ux10w64jTtgHCXOH_1XUu
confirm-email есть , все файлы есть где посмотреть в чем ошибка ?
Извините, если спрашиваю примитивные вопросы, учусь заново можно сказать.
1) Зачем в открытом?
2) А модель по этому токену там находится?
При /logout возникает ошибка:
Кто-то, Дмитрий и у вас тут в блоге и тут http://www.yiiframework.com/forum/index.php/topic/55483-method-not-allowed-405-solved/ написали, что в раздел HEAD необходимо добавить строки:
Добавил, не помогло, проблема сохраняется.
Спасибо. Вопрос решён. Вот этой строчки
Достаточно
Добавил код автоподстановки имени и электронного адреса в поля формы обратной связи.
Возникла проблема.
Честно говоря на php особо не пишу, yii вообще решил только посмотреть.
Все делаю по урокам.
При запросе server.ru/web/logout - ошибка 404
При server.ru/web/user/default/logout - ошибка 405
В чем я мог накосячить, что посмотреть?
Настройте сервер на папку web, чтобы адрес был server.ru/logout.
Возможности нет, к сожалению.
Но я пока просто изучаю... Не подумал о правилах ЧПУ, поправил, теперь ок. Спасибо за наводку
Здравствуйте! Никак не могу подтвердить свою регистрацию, письмо через smtp прекрасно отсылается, линк генерируется и он точно такой же как в базе, но при переходе высвечивается алерт " Ошибка подтверждения Email. ". Что может послужить причиной? А ещё почему-то не добавляется в базу username...
Смотрел свойство user->username через дебаг, в нем торчит введенное имя
getErrors - пустой массив...
Проверьте, срабатывает ли поиск User::findByEmailConfirmToken($token) в форме ConfirmEmailForm.
Всё исправилось само собой, сделав я pull на основном пк...
Здравствуйте!
Помогите разобраться с этим куском:
в дефолтном контроллере модуля User есть такой код:
Переменная $user нигде не используется. Более того, как смысл проверять успешность присвоения?
Я так полагаю, здесь должен быть просто $model->signup() ?
Столкнулся я с этим от того, что у меня операция save не прошла и модель вернула false. В процессе дебага вот наткнулся вот на эту строчку.
Это осталось из yii2-app-advanced, где при регистрации пользователь сразу логинился:
А у нас да, лишний $user можно и убрать.
Доброй ночи.
Дошёл до этой статьи, вроде всё сделал как Вы описываете.
Но возникла проблема с восстановлением пароля.
При попытке запросить новый пароль выдавало ошибку
Переименовал файл passwordResetToken.php в passwordResetToken-html.php, вдобавок создал ещё один файл passwordResetToken-text.php и добавил в mail/layouts файл text.php и всё заработало, письмо начало уходить, появлялся файл в runtime/mail
Но можно решить проблему изменив строку
на строку
в файле modules/user/models/PasswordResetRequestForm.php
Исходя из этого два вопроса:
1) Каким должно быть содержание добавленных мной файлов?
3) Или не создавать дополнительные файлы, а просто изменить строку в классе PasswordResetRequestForm.php
Это разделение изменилось в advanced шаблоне уже после написания статьи. Так что можете выбрать любой вариант.
Значит содержимое файлов html и txt одинаково должно быть?
Один в HTML-разметке, второй просто текстом.
тут вроде 'range' лишнее.
Почему?
Извиняюсь, все верно, это название 'range' в валидации меня периодически сбивает с толку...
При регистрации мы будем присваивать пользователю статус STATUS_WAIT,
ок, тогда почему здесь:
При регистрации мы присваиваем статуc STATUS_WAIT:
а по умолчанию STATUS_ACTIVE.
Скажите, а почему тогда при создании таблицы БД мы там указали в качестве значения по умолчанию 0, что означает BLOCKED?
Просто так. Можете указать DEFAULT 1.
Добрый день.
Я новичок в Yii2. Поставлена задача реализации личного кабинета пользователя через этот фреймворк. Тут же возник вопрос: А как быть с проверкой логина/пароля пользователя(аутентификацией), которые заводятся админом в стороннем приложении, а через веб-часть к ним можно достучаться лишь через 6 join-ов в запросе? Везде примеры с таблицей users, которой у меня нет в БД и не будет. Каким образом формировать/переделать модель User? Ткните носом в нужном направлении. Спасибо.
Можно, например, взять исходную безтабличную модель User из yii2-app-basic приложения и переписать все её методы.
огромное спасибо за статью
Resets password.
confirmEmail()
Wtf?
И почему в том примере на гитхабе такой мордоворот с именами файлов/классов, например:
и т.п. Почему не:
?
Спасибо! У себя исправил.
Не знаю, почему так в yii2-app-advanced.
Да.... ваши вкусы весьма специфичны... даунгрейдить advanced до basic. не проще было advanced использовать уж. этож скока гемору вот так вот переносить.
То есть вместо basic взять advanced и удалить frontend? Cмысла особого не вижу. А здесь всего-то модели и вьюшки скопировать и пути поменять.
Доброго.
небольшая опечатка: "Добавим автоподстановку знчений". Так же при добавлении в виджет новых пунктов снова появляется About, который в прошлом уроке был вычищен.
Спасибо! Исправил.
И еще виджет Alert по предоставленной ссылке уже не существует. Может, вместо той ссылки поставить ссылку на ваш гитхаб?
Его из frontend/widgets перенесли в common/widgets.
Спасибо за отличный урок! Если возможно хотел оставить пожелание: было бы хорошо увидеть как реализуется RBAC система на Yii2 basic
Про RBAC здесь затрону немного. Подробно про роли расскажу на вебинаре после выпуска о тестировании. Так что записывайтесь в форму чтобы не пропустить.
К сожалению выполняла всё как написано, но выводит ошибку 404 на страницы регистрации и авторизации.
Так же в комментариях наисано что исправлены различия в названиях на гитхабе и в тексте:
- PasswordResetRequestForm.php
- ResetPasswordForm.php
Есть ли шаблон для загрузки чтоб распаковать и он заработал?
Можно посмотреть репозиторий. Или установить проект заново:
Но там в web/ отсутствует файл index.php
Подскажите пожалуйста, что необходимо добавить туда?
Запустите и выберите dev окружение:
Впишите настройки базы в config/common-local.php и запустите миграции:
После этого создайте пользователя
здравствуйте . у меня такая проблема , установил через composer ,все по инструкции . на главной странице footer-a net ошибка ©
The requested URL /adsnew/web/debug/default/toolbar was not found on this server.
на все ссылки навбара ошибка
The requested URL /adsnew/web/contact was not found on this server.
можете написать что упустил . Спасибо
Адреса должны быть без /adsnew/web/. Исправьте хост на сервере.
Исправил host : DocumentRoot /var/www/html/adsnew/web, но все та же ошибка.
Сайт hit-movie.ru. footer стал работать но вот ссылки на navbar нет.
Добавьте файл .htaccess
Почему мы не удаляем пользователя через какоето время, если он не подтвердил регистрацию?
Кстати да, можно сделать консольную команду для cron.
Из метода signup() модели SignupForm можно удалить $user->generateAuthKey(), т.к. это делает метод beforeSave() в модели User.
Переместил представления писем в папку модуля (в modules/user/mail).
Как то нелогично называть модель EmailConfirmForm, ведь она не описывает форму! Может логичней перенести этот функционал в User!?
Димка не отвечает на комментарии дибилов, а я отвечу, что вы далбаебы. Ущербные и деградирующие индивидумы. Схуяли ЧоТоТамForm нелогично, затупок ты ебаный?! Ты чо, считаешь что Form, это только HTML-форма? Да ты сука олень просто неебический. Эта часть сделана верно, блеать, дубина ты. Иди сука от сюда вообще. Заебали дегенераты в сообществе Yii. Одни нубы и ебланы. Толпы ебланов и нубов. И сука, не логичней переносить функционал в модель User, не логично нахуй, потому что там вообще ничего не должно быть, по сути, кроме методов для работы с ActiveRecord! Все остальное должно быть в Сервисах, Провайдерах, и, ахуеть да, внимание блядь, ФОРМАХ! Иди на хуй отсюда, чмо тупое. Убейся сука ап стену. Я все.
Тебе что, мама дрочить помешала?
дрянь а не руководство. устарело. вообще другие пути. и лучше (читай - правильно) сделать без привязки к предыдущим урокам, а с нуля для дефолтного приложения. потратил час - на выходе ничего. минус вам в карму
А ты еще больше затупок, чем дибил выше тебя комментарием.
Да-да, помню. Вы во ВКонтакте жаловались, что файл миграции не находит, так как Вы класс миграции скопипастили, а переименовать под своё имя файла забыли.
Дмитрий, подскажите пожалуйста.
Делаю свое приложение на базе ваших примеров. За что вам большое спасибо.
В целом получается. Но столкнулся с такой проблемой.
После авторизации пользователя, в поле логина пользователя, автоматически прописывается его электронная почта. Как сделать, чтобы туда прописывалось его имя. Перерыл весь код и не нашел. Где искать?
Где именно? В SignupForm?
Да, именно там.
Ну в SignupForm присваивается, вроде, правильно:
Пытаюсь сделать отправку письма на почту, а не в runtime выводит ошибку:
С чем это может быть связанно? Всё делал по инструкции, useFileTransport удалил.
У Гугла smtp.gmail.com:
Вставил, теперь yii2 пишет ошибку:
Ошибка (#535)
Возникла внутренняя ошибка сервера.
С чем она может быть связана?
Включите дебаг (или откройте логи) и текст ошибки посмотрите.
Гугл написал, что: Заблокирована попытка входа в аккаунт, видать надо искать в настройках почты?
Разобрался в чём дело надо разрешить доступ к ненадёжным аккаунтам в гугле.
Извините, что отвлекаю, летом много занимался по вашим урокам, мне они очень понравились.
По аналогии делал свой сайт, сейчас решил восстановить для другой работы и при тестировании обнаружил ошибку во время восстановления пароля, когда уже приходит письмо, то при ответе с письма отправляет на ошибку:
С чем она может быть связана?
Видимо в PasswordResetForm Вы namespace забыли поменять.
namespace в PasswordResetForm менял , на вот такой app\modules\user\models
Извиняюсь, что сразу полный код ошибки не привёл
Может у меня в другом месте проблема?
Переименуйте класс с ResetPasswordForm на PasswordResetForm.
Спасибо проблема была действительно а этом.
А как сделать чтобы можно было оставлять комментарии без регистрации? Создавать отдельную таблицу в базе или сохранять пользователей в таблице user?
Любым вариантом. Можно в саму таблицу комментария добавить поля для имени и почты. И заполнять их если в модели поле user_id пустое.
Здравствуйте Дмитрий.
Вот сколько вижу примеров на эту тему и возникает вопрос, почему форма для регистрации не наследует модель "User". Ведь можно наследовать "User" переопределить нужные нам методы, допустим валидацию и все готово. А так мы создаем модель которая по сути просто проверяет корректность данных и "кривым" способом передаем эти данные в модель "User" в случае их валидности.
Почему кривым - потому что каждое свойство модели мы присваиваем "передаем" руками другой моделе, а в случае наследования вызвали бы просто "load" и все готово.
Проще всего свалить все методы прямо в User и разрулить атрибуты сценариями. В итоге вместо нескольких примитивных классов получаем неповоротливого монстра из десятка сценариев, сотни методов и свойств :)
Наследовать полностью всю ActiveRecord-модель User только ради списка атрибутов - слишком жирно. По сути User - это ActiveRecord (строка из базы), а SignupForm - уже не ActiveRecord. А если отнаследуемся, то обе станут AR. Получается ерунда по смыслу.
Так уж получилось, что Yii позволяет прямо саму User в форму выводить, так как ActiveRecord наследуется от Model. Если бы было явное встроенное разделение ответственностей, то отнаследоваться было бы невозможно, и всегда бы приходилось делать отдельную модель для каждой формы, и таких соблазнов лишний раз отнаследоваться вообще бы не возникало. Но имеем что имеем: даже генерируемая через Gii форма поиска для CRUD наследуется от ActiveRecord-модели вопреки здравому смыслу, но ради мнимого удобства.
А если лень.вручную перечислять все атрибуты, то работает то же массовое присваивание:
Это удобнее, когда полей много.
Подскажите с миграциями, мне понадобилось в таблицу юзеров добавить пару новых полей. Уже после создания таблицы через миграции.
Я дописал их в файл m160118_100701_create_user_table.php выполняю
Как правильно их добавить ?
удалить базу и заново делать migrate/up? но мне надо сохранить данные в таблице
Создать новую миграцию с addColumn().
Добрый день, Дмитрий!
Обратил внимание, что виджет yii\bootstrap\Nav ругается на отсутствие ключа 'label' в передаваемом ему массиве, когда ему передается false из условия (этот код из меню layout шаблона):
Если заменить
на
вроде все работает, но не знаю, это из-за каких-то изменений в виджете с момента написании статьи или по другой причине.
Там передаётся не просто массив:
а уже отфильтрованный:
Здравствуйте.
Извините, я разбираюсь с вашей статей, все сделал как у вас и при регистрации после нажатия на отправить перехожу на страницу home со строкой подтвердить электронную почту, а вот на почту ссылка не приходит???
А в папке runtime/mail появляется?
Извините, я не силен в yii2 только изучаю, и Yii2 basic , а в какой папке искать runtime/mail. Строку 'useFileTransport' => true',я убрал?
Спасибо.
Там так папка и называется runtime. Настройте отправку с реальной почты. Для Яндекса это, например, так:
И в параметрах конфига укажите такой же адрес в fromEmail.
Выдает ошибку:
Я настраивал для Гугла:
В параметрах
'supportEmail' => 'тут моя почта на гугле',
Ответ прямо по ссылке из текста ошибки. Разрешите непроверенным приложениям доступ к аккаунту.
Извините мою неграмотность, а как разрешить.
Перейти по адресу, щёлкнуть "разрешить непроверенным приложениям доступ" и сделать как в "Вариант 2".
Огромное спасибо, заработало.
при переходе на логин и сайнап выдаёт ошибку:
actions
Здравствуйте!
Спасибо, за ваш труд.
У меня получается так: вылетают ошибки, исправляю.
Некоторые моменты я уже не помню, но вод из последних:
сперва написано user/mail... потом уже user/mails..., имена файлов в шаблоне advanced другие, там ResetPasswordForm, а не PasswordResetForm.
Это где?
Куда? просто user/views/default ligin.php уже есть, а про замену не слова.
И так далее... Опять же, статья супер, спасибо, но непонимание некоторых моментов осложняет восприятие.
Надо было сразу у вас аккаунт завести...
К своему комментарию выше добавлю, что этот проект выложен на Github и можно посмотреть, что и как. По этому все негодования снимаются.
Доброго времени суток Дмитрий!
В первую очередь примите мою благодарность за ваш труд и за то, что не оставляете без ответа ни одного вопроса в комментариях.
Мой вопрос возможно не по теме статьи, но, так как возник он именно в процессе её прочтения, задам его тут. Если ответ на него уже есть, пожалуйста дайте ссылку.
Меня интересует вопрос того, как правильно в Yii2 advanced сделать разделение конфигов для development и production (например данные для подключения в базе данных, данные для подключения к smtp и т.д.).
В файле PasswordResetRequestForm написано:
Пишет ошибку
Я так понял нужно добавить файл с таким именем, куда и какое его содержимое.
Зарнее спасибо...
home/boris/sites/basic/mail/ в этой папке создал файл и скопировал из папки user/mails/passwordReset.php ...
Теперь письмо со сменой пароля пришло.. перехожу по ссылке , опять ошибка Unable to find 'app\modules\user\models\PasswordResetForm' in file: /home/boris/sites/basic/modules/user/models/PasswordResetForm.php. Namespace missing?
Поправьте namespace у класса.
захожу в почту и нажимаю на ссылку для смены пароля:
Пройдите по ссылке, чтобы сменить пароль: /user/default/password-reset-form?token=yuXy9mWXxzcMWTqpjRm5elJMzyH12liN_1462700117
Page not found.
Т.е. должно быть представление в modules\user\default\passwordResetForm
как должно выглядеть представление, я его не нашел в описании и в структуре...
Откуда у Вас взялось password-reset-form ?
спасибо, да там без -form , но все равно была ошибка, оказывается я забыл при переименовании файла , переименовать и класс, нашел
//class ResetPasswordForm extends Model
class PasswordResetForm extends Model
вот так работает спс.....
Привет! в yii2 появилась автоматизация создания миграции. Для создания внешнего ключа и сопутствующих индексов можно передать в параметре --fields: вместе с названием поля и задание на создание foreignKey, например php yii migrate/create create_user -f: ... status:integer:foreignKey.
как создать по такому же сценарию простой индекс? или это только вручную?
Похоже нет такой команды. Чтобы добавить команду это надо менять parseFields
в /yii2/console/controllers/MigrateController.php Вероятно проще вручную добавлять createIndex() в миграцию.
И всё таки... я переопределил контроллер и сопутствующие шаблоны, сделал в точности по образцу создания внешних ключей. И о чудо! всё получилось))
А вот для того чтобы пользователь мог поменять роль из "обычного пользователя" в "модератор" я хочу чтобы он подтвердил так же и свой номер телефона. Как вы порекомендуете хранить телефон (и кодовое смс) в тойже таблице user или в профиле пользователя?
В полях phone и phone_confirm_token.
То есть в самой таблице User. Я правильно понял?
Да. Рядом с email и email_confirm_token.
Спасибо!
Здравствуйте, Дмитрий.
Большое спасибо за обучающие статьи. Всё доступно и качественно.
У меня возникла проблема на данном этапе. Не открывается страница контактов.
По адресу /contact/ выводится ошибка:
С чем это может быть связано?
С тем, что написали Acton вместо Action.
Пойду заказывать себе очки :) Впредь буду каждую букву по два раза перечитывать.
Спасибо за помощь.
В маршрутах есть правила
я понимаю что _m, _c, _a являются соответственно модулем контроллером и действием, но не могу понять на каком основании такие сокращения? Где об этом можно почитать? Откуда такие сокращения?
Никаких оснований нет. Можно как угодно называть:
Просто _m короче.
Ничего не понимаю. Я думал что это зарезервированное слово в шаблонах маршрутизации?
Зарезервированных слов в маршрутизации нет. Называйте как угодно:
Все я кажется понял. Он просто потом заглядывает в эти правила и смотрит если a/b/c - значит ищет модуль/контроллер/действие, если a/b - значит контроллер/действие.
Я правильно все понял?
То есть ключевыми разделителями здесь являются слеши.
И когда я пишу
(правая часть) будет искать контроллер site с действием param3, то есть сработает автоподстановка при разборе правила.
Посыпались ошибки после
У меня более новая стояла. Откатился обратно.
Прежде чем обновлять composer, думаю стоит версию проверить.
Выполните
и в показанной папке удалите vendor/fxp. После этого запустите снова
Добрый день, спасибо за статьи, помогают очень.
Почему в Model Generator(Gii) в поле table name мы пишем "keys_user"? таблица же просто user называется. Это потому что мы используем префикс в common-local.php? Т.е. если я без префикса работаю то писать user? к чему он вообще этот префикс? ведь он получиться у всех таблиц одинаковый.
Где потом в созданной модели указывается с какой таблицей работать? Ну никак найти не могу. Спасибо, за терпение.
У меня таблица с префиксом. Пишите так, как реально называется в базе. Разные префиксы используются при хранении нескольких сайтов сразу в одной базе на хостинге. В модели указывается в tableName().
Доброй ночи.
Подскажите, пожалуйста, как лучше организовать пошаговую регистрацию?
Последовательность такая:
1 шаг. Пользователь вводит какой-либо текст в textarea, нажимает "проверить".
2 шаг. Вводит email, пароль, подтвердить пароль.
3 шаг. Попадает в кабинет, на почту получает письмо с ссылкой-подтверждением.
Сейчас реализовано так, что если пользователь ввёл текст и email, то нажав на кнопку "проверить" попадает на страницу входа, а на почту получает письмо с ссылкой, логином и паролем.
1. Проверить и сохранить в сессии.
2. Извлечь из сессии и зарегистрировать.
Постоянно обращаясь к одному и тому же action и render один и тот же вид? В зависимости от шага показывать соответствующую часть вида?
Можно один экшен. А можно два, где в зависимости от того, есть текст в сессии или нет редиректить с первого на второй или со второго на первый.
Тест может быть, а может и не быть, остальное, естественное, обязательно будет.
Через get параметр передавать номер шага, или что-то ещё, что бы определить на каком шаге находимся?
Вот только чуть не понял.
Если два action, то на каком этапе выяснять, есть текст или нет?
Или я туплю?
В первом проверить, есть или нет, и от результата продолжать...
Если нет текста, то редирект на второй action, а если есть, то писать текст в сессию и тоже редирект на второй?
Благодарю, но есть одно "но".
Я написал, что надо проверить, если есть текст, то записать в сессию и редирект на второй экшен.
Мне не надо писать в базу, а просто для начала сохранить куда-то текст.
Получается, что мне надо в первом экшене проверить, если нет текста, то сразу редирект на второй экшен, если есть текст, то сначала занести в сессию.
Во втором экшене мне нужно просто проверить и если есть текст, то провести регистрацию, а уже после этого текст писать в базу.
Из Ваших ответов я понял, как это делается, но Вы как всегда добросовестно отнеслись к вопросу и снабдили рабочим примером.
Благодарю ещё раз.
p.s. ну вот так напутано в проекте, сначала текст, а потом уже регистрация)))
Во втором экшене мне нужно просто проверить и если есть текст, то провести регистрацию, а уже после этого текст писать в базу.
Вернее так сделать.
Во втором экшене проверить, есть или нет текст, и если есть, то сначала регистрация, а потом писать текст в базу. Если нет текста, то сразу регистрация.
Вот такая путаница
Настраиваю basic по вашим статьям, но вот при регистрации не сохраняется имя пользователя хотя остальные параметры сохраняются не понимаю в чем проблема подскажите пожалуйста
вот код из Signup form
Код из User модели
и код из контроллера
Уберите лишние поля:
из модели User.
Спасибо Дмитрий теперь работает
> И, как мы уже говорили, изменим адрес до представления письма на полный:
Я долго искал куда и вместо чего вот ето всавлять, плюнул, наривался на ошибки, долго доганял где и что глючит, я ведь новичек в пхп. Допишите для других, в каком модуле, функции ето вписивать и вместо чего. Ведь там массив менять на строку.
В предыдущих строках посмотрите.
Здравствуйте! А можно где-нибудь взять исходники урока - у меня ничего не получилось из basic приложения по вашему уроку - оно попросту отказалось работать и выдавало ошибку за ошибкой?
Самая первая ссылка в статье.
Спасибо видел и не раз уже устанавливал и перебирал ваш модуль. Тяжело с ним работать. Мне лишь хочется понять, как себе сделать такой гибкий rbac, чтоб потом управлять ролями, которые хранятся в БД.
Для управления ролями и разрешениями есть готовый модуль yii2-rights.
Спасибо большое, не знал о таком.
При восстановлении пароля и переходе по ссылке с вида логин пишет(если ссылка в логине прописана как reset-password):
а если ссылка прописана в логине как request-password-reset-token:
В чем проблема не знаю (делал без модулей просто перенося файлы с advanced остальное работает вроде)
И еще у вас resetPassword в папке mails зависит от Module. Не знаю правильно ли я сделал: переделал в модель поменял namespace, и строчку:
поменял на
Присоединяюсь к поблагодарившим :)
Очень полезная статья!
почему id в таблице user не сделан AUTO_INCREMENT? стал AUTO_INCREMENT, но я не понял почему??
Тип $this->primaryKey() как раз делает первичный ключ с инкрементом.
primaryKey и AUTO_INCREMENT это же вроде разные вещи, а если primaryKey без А_I необходим?
Тогда создавайте простое поле:
и вручную навешивайте ключ:
немного понял - спасибо
В общем, куча несоответствий в именах файлов в статье и на git...
Сорри, но понять о каком именно файле идет речь - НЕВОЗМОЖНО.
Пример.
В статье
Нам осталось скопировать представления login, passwordReset и passwordResetRequest из похожих в папке frontend/views/site.
по ссылке
requestPasswordResetToken.php
resetPassword.php
и что чему соответствует?
resetPassword - passwordReset
requestPasswordResetToken - passwordResetRequest
Спасибо большое за то что вы делаете.
Спасибо за статью,
Все очень интересно описываете.
Только почему-то ничего не выходит...
При миграции ошибка
Для консоли компонент 'errorHandler' в конфигурации не нужен.
Можно немного подробнее?
Не могу найти... и grep почему-то перестал писать в каком файле находит...
Спасибо за уроки.
Помогите, пожалуйста, разобраться.
В конце при попытке создать пользователя у меня возникает ошибка
Добавьте метод validateAuthKey.
Большое спасибо.
Так и думал, что где-то что-то пропустил... Удалил проект и начал с первого...
Как показывает действительность - теряюсь в больших объемах данных...но надо выучиться.
Извините, что засыпаю Вас такими глупостями, но мне оооочень нужно разобраться в этом вопросе.
Т.ч. запаситесь валерьянкой - я еще глупых вопросов Вам кучу задам. (:
И еще раз спасибо за статьи и ответы
Здравствуйте, .Дмитрий!
Прежде всего - спасибо вам за этот проект, который многим людям оказал существенную помощь в процессе программирования сайтов. Особо отмечу то, что ваш подход к публикациям и вебинарам позволяет не просто использовать ваш код, но приносит большое удовольствие от последующей работы, потому что наконец-то начинаешь понимать многие вещи "от" и "до".
У меня возник такой вопрос.
Как сделать, чтобы в процессе регистрации пользователь мог вводить кириллицу, латиницу, цифры, дефис и подчеркивание вперемежку? Как я понял, для этого нужно изменить регулярное выражение в
на такое:
Но после замены этого выражения и использования смеси цифр и букв при регистрации в поле name, несмотря на то, что оно валидируется, после отправки формы происходит редирект на эту же страницу и все, никакого результата.
И еще, разве \w не равно A-Za-zА-Яа-я ? Почему же тогда не примается кириллица в этом поле? И какую роль выполняет # в начале и конце выражения? Это вместо слешей?
Помогите разобраться.
Для работы с нелатинскими символами используется модификатор юникода u. В кириллице символ Ё не входит в диапазон А-Я, поэтому нужно его указывать отдельно:
Символ # или любой другой можно вписывать вместо слешей.
Спасибо за подсказку по регулярным выражениям.
Теперь сделал так:
Но и в этом случае при использовании кириллицы в поле name во время регистрации происходит редирект на эту же страницу. Данные никуда не уходят. Латиница, подчеркивание и пробел проблем не вызывают. Не понимаю, почему так происходит. Прошу помощи.
Все остальные функции с именем, введенным не кириллицей, работают - регистрация, аутентификация, восстановление пароля, отправка писем из формы контактов.
Сложилось ощущение, что где-то в другом месте есть какой-то фильтр или условие, потому что при комментировании или удалении из правил строки с регулярным выражением происходит все та же перезагрузка страницы. Опять же в случае использовании кириллицы. В чем может быть причина?
PS
Для корректной работы с кириллицей в форме регистрации, помимо изменений в class SignupForm extends Model нужно таким же образом изменить правила в
Об этом я вообще не подумал...
Теперь все работает. Ура!
Предлагаю сделать на этом сайте функцию, которая растягивает поле с кодом в статьях до конца экрана вправо, а то постоянно скролить приходится.
А как сделать чтобы при подтверждении почты автоматически авторизовываться?
Дописать:
Советую использовать не utf8_general_ci, а utf8mb4_unicode_ci, если вы юзаете мускул, иначе с будут проблемы с вот такими единорогами
В общем, проблемы у вас с ютф символами некоторыми, например опять единорог: U+1F984
При переходе по ссылке из письма (при восстановлении пароля), Страница не найдена.
Линк такого вида (localhost/index.php?r=user%2Fdefault%2Freset-password&token=N3hgwIHtl0Fvqces0K6WOe1bTLbQkw8w_1503147399)
Здравствуйте. Для функции изменения email можно использовать старый токен или нужно добавить новое поле в базу для нового токена?
Использую поле email_confirm_token.
Дмитрий, спасибо за чудесные статьи. Они мне помогают освоить yii2, хоть я и знаю yii1? но почему то сложно дается yii2.
Вопрос такой, в yii1 я спокойно могу сделать отдельную авторизацию для каждого модуля.
Как это сделать во yii2? без всяких RBAK? Просто нужно разграничить доступ из модулей.
Тоесть, есть 3 модуля, admin , partner, author - как сделать, чтобы у каждого была своя авторизация?
Чтобы админу была такая ссылка /admin/login
Для партнера такая /partner/login
Для автора такая /author/login
В yii1 делалось все просто, в модуле создаем папку /components/ в него кидаем класс identity и все... он как бы подхватывал сам этот файл. В базе для каждого своя таблица, для авторов одна, для партнеров другая и для админов третья.
Помогите! Плиииззз! У меня уже мозги кровью заливаются....
class User extends \yii\base\Object implements \yii\web\IdentityInterface
Или создать AdminForm.php в каталоге модуля, сделать новый наследник интерфейса identity , например Adminer
и уже в самой форме AdminForm.php в геттере подключать его?
а как потом сделать разграничение?
допустим по этой же аналогии сделаю и для партнеров и авторов, как запретить доступ авторам в админку и в партнерский кабинет? и наоборот, партнерам в авторский и админский?
Помогите!
авторизацию раздельную вроде сделал, но все равно как бы есть доступ между модулями... не получается сделать разграничение.
в модуль прописал так. все работает, конечно костыль...
может кто подскажет, как решить сей проблему?
Почему для скрытия пунктов меню не используется параметр visible?
Как по мне такой вид меню выглядит более аккуратно, да и array_filter не нужен, Yii сам все отфильтрует.
Да, можно и так.
А есть статьи с более человечный объяснением? Без всяких бесполезных миграций и gii. Новичкам это не нужно
Документацию не нужно советовать, она еще более бесполезна
Тогда больше нет.
Жаль( Трудно разобраться, очень, доку прочитал, только запутался еще больше( Примеров бы по больше из жизни.. Но почему то никто не хочет делать подобный справочник, не понимаю почему(
Есть другие примеры вроде каталога. Но они тоже с миграциями.
еще и смешали advanced... нахер новичкам такие уроки? С басисом трудно разобраться, он еще всякое говно приклеивает .... гавнокодеры они такие
Короче нихера не работает, опять придется удалять проект.. ну что за дауны рукожопые пишут подобные статьи... Поймите, гавнокодеры, вы обязаны писать для новичков, чтобы им было понятно, а не для того, чтобы повыебываться! Заебали блять! Когда вы исчезните уроды! Поколение доков , компостеров, и всякой лажи бесполезной
А зачем тогда вам фреймворки? Ставьте Wordpress и не парьтесь.
Лучше спросите, зачем такие статьи, где ничего путного не объяснено. Они обязаны быть для новичков, так как для профи статьи не нужны, они и сами все знают. В ней вообще ничего не описано, тупо, скачайте то, вставьте код, куда вставлять, зачем, почему.. Зачем объяснять....
Еще и с гавносоветами типа поставить WP.... Доказал свое гавнокодерство
> Поймите, гавнокодеры, вы обязаны писать для новичков
> Они обязаны быть для новичков
Ну так и ищите "туториалы для новичков с объяснениями", а не готовые "мастер-классы с процессом без объяснения". Проблема-то в чём?
Какие мастер классы? Серьезно? Это просто буквы без смысла! Ну так найдите таких, если подобные вам засоряют инет хламом, те кто знает хоть что то в этом, никогда не объясняют, я в этом виноват? Это ваши проблемы!
И опять, вместо того, чтобы поменять уровень подачи материала, идут одни отмазки.
В этих статьях ничего нет, ни для кого. Статья о том, как тупо копипастить и забивать на учебу. А после этого ходите и жалуетесь, а что вам постоянно приходится гавнокод разгребать. А как люди будут учиться, если такие "учителя" не могут и не умеют учить. Это ваша вина, а не моя.
Берём готовый код регистрации из app-advanced и копируем в app-basic. Всё. Что Вам здесь ещё объяснить?
Опять бред от "профи"... И опять все сжато... Еще один факт того, что я прав. Вы не умеете подавать материал. Я могу так же сказать про то, что вы не знаете, и вряд ли понравится это вам.
Откуда берем, что берем, куда кладем? Еще раз повторю, это отдельный язык, он не имеет ничего общего с php и ооп. Он написан на них, но это не эти языки. Чтобы этот язык понять, нужен учитель, который нормально обьясняет. Но таких нету....
Лаврик хорошо подает материал, но он не занимается фреемворками. Поучились бы у него... Или убрали из топа ваш сайт, чтобы другим не мешать развиваться. После таких статей, падает вся мораль...
Что рукожопы создатели, что подобные "учителя" нихера не могут объяснить как сделать эту гребаную регистрацию, которая делается на нормальном php ооп за 10 минут. Тут еб*шься уже 3 месяца... На вопрос, зачем мне это? Нужно. Я не виноват, что дибилы работошлактели берут готовое убожество с мусором кода
> Чтобы этот язык понять, нужен учитель, который нормально обьясняет.
Возьмите себе любого учителя и задайте ему именно свои вопросы. Он всё объяснит на пальцах именно под ваш уровень и ваш запрос.
> не могут объяснить как сделать эту гребаную регистрацию
Она уже четыре года как сделана в yii2-app-advanced из коробки. Не знаю, с кем Вы там совокупляетесь уже три месяца.
Она нигде не сделана. Смиритесь уже. То что вы в этом говне поняли, разобрались, не значит, что это должны делать все. Твоя обязаность объяснять и учить, смирись, или свали от сюда
> Она нигде не сделана.
Сочувствую вашему невидению этого репозитория.
Как все запущено... еще и репозитории смотреть... Сколько дибилушек на этой планете, которые привыкли говно жрать.....
О, класс. Вместо того, чтобы разок посмотреть на официальный репозиторий от фреймворка с регистрацией и авторизацией вы три месяца совокупляетесь сами с собой...
Отлично! Продожайте изучение в том же духе :)
Вместо того, чтобы тявкать, научитесь учить. Клоун! Еще раз повторяю, пользоваться хламом не собираюсь. Тем более тем, где нихера не объясняют. Но таких ущербным это не понять. Нужно истреблять подобных, вы не нужны в этом мире убогие
Вот и не пользуйтесь. Как и сказал, ставьте Wordpress и не парьтесь.
советы от бога... Только что сказал, что не пользуюсь дерьмом всяким, а он советует дерьмо.............. Еще и дырявое как твой мозг...
Доказываешь мою правоту во всем
Да и как учитель вообще может советовать что либо копировать? Чтобы что то копировать, нужно это понять. Чтобы понять, нужно внятное объяснение. Это кстати еще один факт за меня
Это статья как раз о том, как скопировать регистрацию из app-advanced в app-basic. Если какие-то вещи непонятны, то ищите отдельное их описание в других местах.
Даа, тяжко наверное подобным жить.... Мрази, которые нихера не могут, кроме как унижать людей и гнаться за бесполезными и не нужными бумажками, возвышая при этом дерьмогосударство не нужное.... Быстрее бы подобные исчезли....
Копировать.... дерьмоучителей слишком много стало....
Вам виднее.
это факт
И, как мы уже говорили, изменим адрес до представления письма на полный:
Yii::$app->mailer->compose('@app/modules/user/mails/passwordReset', ['user' => $user])
И где это менять? Сложно написать что ли?
Везде, где есть Yii::$app->mailer->compose(...)
Крутое объяснение(....
Мало того что опечаток и ошибок фигова куча... Зачем то соединил басик и адвансед, не изменив пути везде, что то напридумав, не может опять по человечески что и куда вставлять.... Так еще и забивает на вопросы... Всегда было жаль подобных..видимо правильно некоторые пишут, не ваше это, программирование
> Мало того что опечаток и ошибок фигова куча...
Про какие ошибки речь?
> Зачем-то соединил басик и адвансед
Чтобы в basic иметь гибкую конфигурацию и добавить готовую авторизацию из advanced
> не изменив пути везде
Изменив.
> не может опять по человечески что и куда вставлять...
Из папок controllers, models и views в advanced в такие же папки в basic.
Так что при написании письмо первым параметром укажем новый путь @app/modules/user/mails для файла письма emailConfirm.php.
Вот где и что??? Ну как так можно писать.....Хоть бы скрин сделал, или файл написал.....
Прямо в коде перед этим предложением указано.
Хм... Даже зарегистрировался, чтобы написать))
Работает все прекрасно. Вот только что тестировал регистрацию, подтверждение и восстановление пароля. Были некоторые проблемы с путями, то есть после копирования файлов из advanced шаблона, были проблемы с настройками, но если подумать, воспользоваться гуглом, то всё поправимо. Я хоть и нубяра полный, структуру немного поменял по-своему, но у меня почему-то получается.))) Если что-то непонятно, то можно же спросить, а не кидаться на автора, который готовый код выложил на блюдечке. Хочешь тупо копируй, а хочешь пытайся вникнуть. -_- Вот как-то так)
Спасибо за полезные статьи)
Разбираюсь поэтапно, весь код из статьи целиком не переносил. Добавил таблицу с юзерами в БД, изменил метод findByUsername($username).
Получилось, что
из метода поиска юзера по имени возвращает объект User с незаполненными свойствами, значения из БД хранятся в _attributes[]. Соответственно, валидация пароля не проходит.
Дошел до конструктора без аргументов
Туда ли я копаю? :-)
Добрый вечер.
Недавно вернулся к тестовому сайту, который создавал по Вашим статьям.
Заметил такую странность, которую не могу понять.
В модели Users для username и email делаем проверку на уникальность.
Вот в таком варианте при редактировании ошибка валидации "Имя занято...", "Email зарегистрирован..."
Если вместо self::class написать self::className(), то валидация работает нормально, при редактировании не выдаёт ошибки.
В чём тогда разница? className() phpstorm подчёркивает как deprecated, в документации рекомендуют запись ::class, но при этом проблемы с валидацией.
Вместо self::class пишите static::class. Это будет учитывать наследование.
Благодарю.
Уже разобрался с помощью форума http://yiiframework.ru/forum/
День добрый.
Подскажите пожалуйста, может кто-то сталкивался с такой проблемой. Она связана с консолью. Когда prompt требует от меня ввода username в методе create/user, и я его ввожу на кириллице, валидация $input-a не проходит. $input якобы пустой и валидатор выдает ошибку то что поле не должно быть пустым. Хотя если взять и распечатать var_dump($input), то ответ будет пустое значение длинной равной количеству введенных символов string(n). Но такое ощущение что все эти символы заменяются пробелами. Но проверил через функцию ord() она показывает что каждый символ в строке null. Поэтому валидатор ругается правильно.
Здравствуйте Дмитрий!
Классный у вас блог, очень много много раз выручал по разработке сайтов на Yii фреймворке.
У меня вопрос к вам, как к знающему человеку.
Как правильно организовать таблицу хранения пользователей?
Допустим, я хочу сделать сервис, где пользователи могут создавать проекты, например блоги, и у них могут быть свои пользователи блогов.
Тоесть, зарегистрировался пользователь user_A у него могут быть свои пользователи блога, тоесть отдельная регистрация и авторизация на его блог(проект)
Второй пользователь user_B тоже создал проект и у него свои пользователи.
Вопрос в том, что пользователь user_A может быть так же пользователем и user_B , так же как и user_И может быть пользователем user_A.
Если в одну таблицу их сохранять то получается много дублей емайл адресов. Если по разным таблицам, тоесть, сущность оставить одну, например таблица tbl_users и в ней только поля (id,email , token) и сводные tbl_creator (user_id (tbl_users.id) , auth_token , password) и таблицу пользователей этих проектов tbl_people (user_id (tbl_users.id) , auth_token , password) то возникает проблема при получении identity id , там же один параметр всего подается на вход....
Я понимаю, что совсем не правильно думаю, вот и прошу помощи..Как правильно сделать?
Может пример с блогами не понятен, то вот например, человек хочет создать онлайн школу, он создал проект, и у него могут быть свои ученики. Думаю этот пример поможет понять что мне нужно.
надеюсь на помощь
Либо одну таблицу users (id, email, password, token) для пользователей и уже вспомогательные и связующие вроде students (user_id, school_id, status) для остальных мест.
Либо tenants для владельцев и students для регистраций студентов.
Спасибо. Ну примерно так и реализовано.
Как я понял, всё равно дубликатов емайл не избежать.
У меня ещё одна проблема, кабинеты сделаны по модулям.
И нужно сделать, чтоб админ мог заходить без авторизации в любой кабинет пользователя.
Сейчас у меня какой то глюк. Если заходит в кабинет пользователя, то выкидывает из админки.
Я сделал разные модели, но всё равно выкидывает. Сделал разные идентификаторы сессии, то тогда вообще не пускает в кабинет пользователя.
Как сделать правильно, чтобы админ мог заходить в кабинет пользователя без авторизации. Модульная система. Авторизации в модуле каждая своя.
Отличается лишь одним параметром, создатель и пользователь
Разрешить через кастомные Auth\Rule.
А как можно добавить дополнительные атрибуту сеанса? тоесть, чтобы через getIdentity($id) можно было что то вроде getIdentity($id , $param1, $param2)
В identity можно делать и составной id вроде "id-type".
Спасибо. Я уже решил проблему. Просто расширил класс по своему.
У меня ещё один вопрос, который задавал ранее, чем про identity.
Как правильно организовать хранение пользователей в таблице?
Пользователь создаёт блог. И на его блог могут регистрироваться тоже пользователи.
У меня сделано в одной таблице. Я лишь добавил поле создатель и просто пользователь. Но мне не даёт покоя, что дублируются емайл адреса. Ведь пользователь может быть зарегистрирован на других блогах. И получается приходится его дублировать, только ещё метить к какому блогу он относится.
Хотел сделать разные таблицы под создателя и пользователей, но тогда проблема с выбором партнёров.
Если подскажите как правильно создать структуру таблиц для пользователей и создателей, буду очень признателен.
Либо всех пользователей хранить в одной таблице и везде делать сязующие таблицы авторства и участия. Либо делать отдельные таблицы tenant и user.