Сервис на 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 и вбиваем имена таблицы и модели:

Gii Model

Теперь жмём Preview, ставим галочку что хотим перезаписать старую модель и жмём Generate:

Gii Model Overwrite

Дополним свежесгенерированную модель. Первым делом, введём в неё константы для указания статуса, статический метод 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
<?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;
    }
}

Немного дополним...

В исходном приложении без модульной структуры все представления писем хранятся в папке mail в корне. При переходе к модульной структуре желательно переместить письма тоже в свои модули. Так что при написании письма первым параметром здесь указан новый путь @app/modules/user/mails для файла письма emailConfirm.php.

Создадим файл modules\user\mail\emailConfirm.php. В него пометстим приветствие и ссылку:

<?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
<?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">
            <?php $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>
            <?php ActiveForm::end(); ?>
        </div>
    </div>
</div>

Также нужно добавить свой шаблон письма modules/user/mails/passwordReset.php:

<?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">
            <?php $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>
            <?php 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

Получился свой собственный шаблон приложения. Его можно уверенно использовать и для будущих проектов.

Часть 4: Доработка интерфейса приложения

Комментарии

 

Дмитрий

Спасибо за статью, Дмитрий. Очень подробно, в самый раз для освоения новой версии Yii.
В Yii1.1 есть очень удобные модули для быстрого развертывания приложений User и Right, а для Yii2 есть что-то подобное?

Ответить

 

Дмитрий

Спасибо за наводку! Сам проглядел столь полезный модуль.

Ответить

 

Игорь Васильев

Не знаю как и кому, а мне было бы приятно при регистрации указывать своё реальное имя,
по-русски, и не важно сколько Людей с моим именем зарегистрировалось до меня, придумывать какие-то логины, по мне так глупо и бесполезно, никто кроме вас не знает каким именно электронным адресом вы воспользовались, и какой пароль введёте. По мне так удобнее восстановить только пароль, введя свой номер телефона и получить пароль по SMS или по e-mail чем вспоминать, а какой же логин я вводил, и действительно не удобно как-то и не этично парить мозги пользователю каким-то там логином, имя он своё знает, да и почту наизусть наверняка выучил, пароль можно снегерировать сложный, а если забыл я уже писал как восстановить, так же можно сделать? И получать письма с обращением к вам по имени а не по логину гораздо приятнее, я это реализовывал на самописном движке, как это реализовать на Yii2 ???

Ответить

 

Дмитрий Елисеев

Убрать поле username и для логина использовать email.

Ответить

 

Игорь Васильев

Дмитрий, я то это и так понимаю, не отразится ли это на системе безопасности и на валидацию, ведь по сути мне придётся переделать весь код, заменив username на email, и по логике вещей username содержит правило, запрещающее создавать пользователей с одинаковым логином. Плюс такое правило применяется в БД, указывая строку как уникальное значение. Есть ли способ с наименьшими жертвами, чтобы логин остался, но являлся реальным именем пользователя, который можно ввести по-русски ?

Ответить

 

Дмитрий Елисеев

1. Убираете эти ограничения из rules():

['username', 'match', ...],
['username', 'unique', ...]

2. Удаляете уникальный индекс из базы.

3. Заменяете User::findByUsername на findByEmail в LoginForm.

Ответить

 

bobpps

Спасибо! Отличная статья.

Ответить

 

Вячеслав Шуянов

Версии меняются - миграции все теже) За статью спасибо!)

Ответить

 

Максим

Статья супер! Ждем, с нетерпением, продолжения ))

Ответить

 

Andrew Johns – opendg.ru

Коллега, отлично пишете. Жду продолжения, а пока читаю английский getstarted.

Ответить

 

VGrigoryev

В 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. А во втором случае можно добавить:

if (!$user = User::findOne(['email' => $this->email, 'status' => USER::STATUS_WAIT])) {
    $user = new User();
}
Ответить

 

Spirit Absolute

Дмитрий, а куда писать эту строчку? в signup? "после if ($this->validate())"

Ответить

 

Дмитрий Елисеев

Вместо

$user = new User();
Ответить

 

Spirit Absolute

Если вместо этой строчки написать, то до нее это условие не проходит. У нас же вначале идет валидация, то есть он сразу проверяет есть ли уже этот пользователь. if ($this->validate()) и только потом уже создает $user = new User(); значит надо до валидации еще эту проверку делать, и если пользователь есть то делать без валидации...

Ответить

 

Arman Kaztore

Здравствуйте.А что если юзер по ошибке указал другой email и хочет сделать регистрацию заново, но не сможет потому что при регистрации нужно указать еще один уникальный поля иин

Ответить

 

Дмитрий Елисеев

Можно привязать уникальность к статусу.

Ответить

 

Дмитрий

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

Calling unknown method: app\modules\user\models\ConfirmEmailForm::findOne()

Долго сравнивал свой код с кодом из статьи, но ничего не нашел.
В итоге, я решил а почему static::findOne если мы ищем данные по юзеру и написал User:findOne и все работало - выводится сообщение что емаил подтвержден в БД поле email_confirm_token удаляется.
Дмитрий. это у вас ошибка или я что не так понял?

Ответить

 

Дмитрий Елисеев

А где у Вас этот static::findOne? В каком классе?

Ответить

 

Дмитрий

Там же где и у вас

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]);
    }
Ответить

 

Дмитрий

вообщем все работает и с static::findOne
Наверное я что-то делал не так.
Сброс пароля тоже работает.
А вот залогиниться не могу - пишет Incorrect username or password.

Не могли вы рассказать об авторизации......целый день пытаюсь и ни как не получается

Ответить

 

Дмитрий Елисеев

А в базе данных у пользователя точно активный статус?

Ответить

 

Дмитрий

да, активный

Ответить

 

Дмитрий Елисеев

Тогда посмотрите в методе LoginForm::validatePassword находится ли пользователь, проверяется ли пароль.

Ответить

 

Дмитрий

пользователь находится, в вот Yii::$app->security->validatePassword($password, $this->password_hash); возвращает false

Ответить

 

Дмитрий

Решение нашел на форуме yiiframework.com - вся проблема была длине поля password_hash. Увеличил размер поля и все заработало :) Оказывается не один я такой

Ответить

 

Дмитрий

для сброса пароля
в PasswordResetRequestForm надо заменить не только namespace но и класс в правилах:

'targetClass' => '\common\models\User',

заменить на

'targetClass' => '\app\models\User',
Ответить

 

Дмитрий Елисеев

И вообще для таких случаев лучше использовать конструкцию:

'targetClass' => User::className(),
Ответить

 

Дмитрий

Дмитрий подскажите следующий момент.
В регистрации и авторизации юзера заложена идея статусов (ждет подтверждения/заблокирован), но в

public static function findByUsername($username)
{
    return static::findOne(['username' => $username, 'status' => self::STATUS_ACTIVE]);
}


при авторизации ищется юзер с логином и статусом активен.
Как сделать чтобы искался юзер по логину и потом была проверка статуса - заблокирован/ждет подтверждения и выводились определенные сообщения? Т.е. если статус WAIT то выводим сообщение что на емаил было отправлено письмо и ссылка на повторное отправление письма.
Буду благодарен всем кто подскажет. Yii только начал изучать поэтому столько вопросов.....

Ответить

 

Дмитрий

Сам задал вопрос. сам и отвечу :)
Решил таким способом

public static function findByUsername($username)
{
    $result =  static::findOne(['username' => $username]);
    if($result->status == self::STATUS_BLOCKED)
        return Yii::$app->getSession()->setFlash('error', 'Извините. Ваш аккаунт заблокирован.');
    elseif($result->status == self::STATUS_WAIT)
        return Yii::$app->getSession()->setFlash('error', 'Ваш аккаунт не подтвежден. На '.$result->email.' было выслано письмо со ссылкой подтверждения.');
    elseif($result->status == self::STATUS_ACTIVE)
        return $result;
}

Может есть еще более проще вариант?

Ответить

 

Дмитрий Елисеев

Метод setFlash ничего не возвращает, поэтому подставлять его в return не очень корректно.

Помимо этого в методе появилось побочное действие (запись в сессию). По моральным соображениям это противоречит принципу единой ответственности, а с технической точки зрения вызывает проблемы с использованием метода findByUsername в консольных командах, где компонента сессии нет вообще.

Дополнил статью. А именно убрал указание статуса из findByUsername:

public static function findByUsername($username)
{
    return static::findOne(['username' => $username]);
}

и все проверки перенёс в LoginForm:

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', 'Ваш аккаунт не подтвежден.');
        }
    }
}
Ответить

 

Дмитрий

ну тут да я ошибся с моделью - работаем с моделью LoginForm, а проверку запихнул в User..... тем нарушил принцип MVC.... если я правильно понимаю

Ответить

 

Андрей Лукьянов

В таком случае в форме логина нужно добавить метод повторной отсылки письма, если письмо не пришло или было удалено...
Что-то вроде - "Не получили письмо об активации? Прислать еще раз". тут надо будет вводить e-mail, куда прислать

Или из сообщения об ошибке - $this->addError('username', 'Ваш аккаунт не подтвежден. Прислать письмо еще раз');
тут e-mail вводить не надо. Он уже известен в ходе проверки...

Ответить

 

Дмитрий Елисеев

Ну это уже как захотите.

Ответить

 

Андрей Лукьянов

Дмитрий, подскажите пожалуйста. У нас в контроллере, как я понял, разрешено только два действия

'only' => ['logout', 'signup'],

а выполняются, почему то все action-s...
восстановление пароля, отправка письма и т.п...

Почему так, и для чего тогда нужно это поведение?
Спасибо

Ответить

 

Дмитрий Елисеев

Only обозначает, к каким действиям применяются перечисленные правила контроля доступа. Выйти может только залогиненный пользователь (условня роль @), а зарегистрироваться – только гость (?). Остальные действия правил не имеют и работают как обычно.

Ответить

 

Андрей Лукьянов

По консоли вопрос.
Контроллер у нас называется Users.
В индексе -

echo 'yii user/create' . PHP_EOL;
echo 'yii user/remove' . PHP_EOL;

Переименовал контроллер и файл в UserController
пишу в консоли - php yii user/create - выдается ошибка, что неизвестная команда...

Ответить

 

Дмитрий Елисеев

Да, имя user оказалось занято.

Ответить

 

Alex Ssdd

Дмитрий огромное спасибо, Будет ли часть по использованию RBAC'a ?

Ответить

 

Дмитрий Елисеев

Да, будет.

Ответить

 

Alexey

Оень классный материал по Yii2. Спасибо.

Жаль только, что

...так как фактически у нас будет только админка

Тут выходит по сути фронтенд часть за админку.

Можно ли на этой модульной структуре реализовать админ часть для модулей, например, чтобы доспуп к ней был /backend/contact или /backend/user и т.п.?

Я так полагаю, нужно в каталогах controllers, models и views создать каталоги backend, соответственно и тут вопросы уже как роуты прописать или что нужно настаивать.

А advanced шаблон сложноватый выходит, там и common присутствует.

Не подскажете как реализовать?

Ответить

 

Дмитрий Елисеев

В отличие от Yii1 с пространствами имён можно размещать всё как угодно. Например, можно создать папку backend и в неё поместить эти модули.

Ответить

 

Spirit Absolute

В представлении login.php как правильно указать url?
стандартный урл ествественно приводит к 404.

<?= Html::a('reset it', ['site/request-password-reset']) ?>.
Ответить

 

Дмитрий Елисеев

user/default/request-password-reset

Ответить

 

Spirit Absolute

нет, пробовал, все равно выдает 404 Unable to resolve the request "user/user/default/request-password-reset".

Ответить

 

Дмитрий Елисеев
<?= Html::a('reset it', ['/user/default/request-password-reset']) ?>.
Ответить

 

Spirit Absolute

<?= Html::a('reset it', ['user/default/request-password-reset']) ?> выдает ошибку 404 Unable to resolve the request "user/user/default/request-password-reset".

Ответить

 

Дмитрий Елисеев

Слеш "/" вначале постаьте.

Ответить

 

Spirit Absolute

Спасибо! Извиняюсь!

Ответить

 

Дмитрий

Достаточно ввести:
<?= Html::a('сбросить', ['default/request-password-reset']) ?>
этого хватит :-)

Ответить

 

Дмитрий Елисеев

Да, так лучше. Спасибо!

Ответить

 

Дмитрий

Дмитрий, когда продолжение?
Хотелось бы по подробнее узнать про виджет AvtiveForm, его настроку, т.е. использование configField

Ответить

 

Дмитрий Елисеев

К формам скоро подберёмся.

Ответить

 

ref – deadblog.ru

Пару моментов:

В 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'], для корректного переброса на страницу авторизации

'components' => [
    'user' => [
        'identityClass' => 'app\models\User',
        'enableAutoLogin' => true,
        'loginUrl' => ['user/default/login'],
    ],
    ...
],
Ответить

 

Дмитрий Елисеев

Спасибо! Добавил.

Ответить

 

Вячеслав

Когда пытаюсь применить миграцию, вылезает такие ошибки http://c2n.me/jkKSHo
Не подскажешь в чем тут проблема?

Ответить

 

Дмитрий Елисеев

Не найдены настройки компонента db.

Ответить

 

Akulenok

как насчет сделать авторизацию через соц. сети?

Ответить

 

Дмитрий Елисеев

Может и сделаю. Раньше имел дело только с uLogin. Там у них есть готовый код для Yii1.

Ответить

 

Akulenok

Может можно переделать его с yii1 на yii2, реально нужная вещь.

Ответить

 

Cold

Есть проблема с кодировкой текста, отправленным виджетом Alert. Все файлы перекинул в UTF8, не помогло. Да и вообще, чтобы русский текст нормально отображался из вьюх, приходится их пересохранять в UTF8. Может есть более простой способ?

Ответить

 

Дмитрий Елисеев

Я всегда использую для файлов и прочего UTF-8. Тогда все серверы и программы работают нормально.

Ответить

 

Игорь – www.vaigmania.ru

Пользуюсь NetBeans IDE , там изначально настраивается кодировка, какую пожелаете, и рефакторинг делать проще, и прописывать узы, и прочие рабочие моменты проще. После того как Notepad++ переломал мне весь проект, я отказался от его использования, да и менять кодировку с BOM или без BOM устал, скачайте NetBeans IDE , тем более что он бесплатный, и радуйтесь жизни ))

Ответить

 

Александр

Подробно ражевано, спасибо!
Очень жду RBAC!

Ответить

 

Andrey Rabchevsky

Дмитрий, спасибо за шикарный цикл статей!

У меня для MS SQL (работаю через PDO dblib) скрипт миграции при создании таблицы user не воспринял инструкцию NULL для трех полей и все равно создали их NOT NULL.

Сам код миграции правильный:

$this->createTable('{{%user}}', [
            'id' => Schema::TYPE_PK,
            'created_at' => Schema::TYPE_INTEGER . ' NOT NULL',
            'updated_at' => Schema::TYPE_INTEGER . ' NOT NULL',
            'username' => Schema::TYPE_STRING . ' NOT NULL',
            'auth_key' => Schema::TYPE_STRING . '(32) DEFAULT NULL',
            'email_confirm_token' => Schema::TYPE_STRING . ' DEFAULT NULL',
            'password_hash' => Schema::TYPE_STRING . ' NOT NULL',
            'password_reset_token' => Schema::TYPE_STRING . ' DEFAULT NULL',
            'email' => Schema::TYPE_STRING . ' NOT NULL',
            'status' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0',
        ], $tableOptions);
Ответить

 

Дмитрий Елисеев

А если попробовать

Schema::TYPE_STRING . '(32) NULL DEFAULT NULL'
Ответить

 

Andrey Rabchevsky

А так всё верно. Спасибо!

Ответить

 

Дмитрий Елисеев

И Вам спасибо! Исправил в статье.

Ответить

 

Сергей Доровский

А как сделать, что бы письма сохранялись в читаемом виде?
У меня в папке @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 заменил вот это:

namespace frontend\widgets;
и class Alert extends \yii\bootstrap\Widget{

вот таким кодом

namespace app\components\widgets;
use yii\bootstrap\Widget;

class Alert extends Widget
{

и в файле main.php который в layouts/ прописал

use yii\widgets\Breadcrumbs;
use app\components\widgets\Alert;
Ответить

 

Andrey Rabchevsky

Дмитрий, если помимо модуля user нам еще потребуется создать модуль admin для управления юзерами (и прочих админских дел) - где будет располагаться модель User? Ведь не создавать же ее в каждом модуле.

Ответить

 

Дмитрий Елисеев

Оставим в user.

Ответить

 

Andrey Rabchevsky

Т.е. для модулей не существует такого понятия, как общие модели?

Ответить

 

Дмитрий Елисеев

Можно как угодно делать. Можно вынести в корневую папку models, можно брать модели из других модулей, можно в модуле admin насоздавать заглушек-наследников и работать с ними:

namespace app\modules\admin\models;
class User extends app\models\User {}
Ответить

 

Andrey Rabchevsky

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

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

Ответить

 

Vic

В функции signup() перестраховались:

$user->generateAuthKey();

у нас вроде beforSave() генерится этот ключик?

Ответить

 

Александр

Дмитрий, немного не по теме, но все же спрошу, подскажите пожалуйста, как мне лучше всего переопределить bootstrap css && js таким образом, что бы на всех страницах были подключены именно мои бутстрап файлы (хочу подключить немного другую версию, нежели предлагают разрабы уии)? В AppAsset подключаю свои скрипты, в бандл оставляю только 'yii\web\YiiAsset', но другие виджеты всеравно, в случае использования, кидают "родные" бутстрапы ниже моих.

class AppAsset extends AssetBundle
{
    public $basePath = '@webroot';
    public $baseUrl = '@web';
    public $css = [
        'themes/theone/assets/stylesheets/bootstrap.min.css',
        'themes/theone/assets/stylesheets/themes.min.css',
        'themes/theone/assets/stylesheets/pages.min.css',
        'themes/theone/css/main.css',
    ];
    public $js = [
        'themes/theone/assets/javascripts/bootstrap.min.js',
        'themes/theone/assets/javascripts/themes.min.js',
    ];
    public $depends = [
        'yii\web\YiiAsset',
  //      'yii\bootstrap\BootstrapAsset',
    ];
}
Ответить

 

Дмитрий Елисеев

Создайте у себя в components классы yii\bootstrap\BootstrapAsset и yii\bootstrap\BootstrapPluginAsset (в том же пространстве имён, но со своими путями) вроде:

namespace yii\bootstrap;
use yii\web\AssetBundle;

class BootstrapAsset extends AssetBundle
{
    public $basePath = '@web/themes/theone/assets';
    public $css = [
        'stylesheets/bootstrap.min.css',
    ];
}

Потом в index.php перед созданием приложения сделайте подмену файлов классов:

Yii::$classMap['yii\bootstrap\BootstrapAsset'] = '@app/components/BootstrapAsset.php';
Yii::$classMap['yii\bootstrap\BootstrapPluginAsset'] = '@app/components/BootstrapPluginAsset.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 - это будет восхитительно :)
Быстро найти подробное объяснение по работе с регулярными выражениями в нём мне не удалось.

Ответить

 

Стас

Спасибо! :)

Ответить

 

Matthew P.

Здравствуйте. В глазах сосуды лопаются, не знаю в чем дело. Капча одинаковая что на странице регистрации, что на странице контактной формы.

В файле контроллере SiteController.php

public function actions()
{
    return [
        'captcha' => ['class' => 'yii\captcha\CaptchaAction'],
    ];
}

В файле SignupForm.php

public function rules()
{
    return [
        ['verifyCode', 'captcha', 'captchaAction' => 'site/captcha'],
    ];
}

В файле signup.php

...
<?= $form->field($model, 'verifyCode')->widget(Captcha::className(), [
    'captchaAction' => 'site/captcha',
    'template' => '<div class="row"><div class="col-lg-3">{image}</div><div class="col-lg-6">{input}</div></div>',
]) ?>
...

Спасибо за статью.
Буду премного благодарен за помощь.

Ответить

 

Дмитрий Елисеев

Одинаковая, так как берётся из одного действия site/captcha. Сделайте ещё одно действие:

public function actions()
{
    return [
        'captcha' => ['class' => 'yii\captcha\CaptchaAction'],
        'captcha2' => ['class' => 'yii\captcha\CaptchaAction'],
    ];
}

и укажите новое в captchaAction.

Ответить

 

Vic

Вот тут возможно опечатка или что-то обновилось:

у Вас: PasswordResetForm
на github: ResetPasswordForm.php

Ответить

 

Дмитрий Елисеев

Спасибо! Исправил.

Ответить

 

Влад – infoblog1.ru

Так же если кому-то интересно на какой VPS сервер поставить сайт, то без задумки выбираем
rootwelt
Так же на этой странице в низу Вас будет ждать мой презент в виде промо кода на 50% скидку.

Ответить

 

Александр

А почему для полей created_at, updated_at выбран тип INTEGER. Я понимаю чо так было в шаблоне. Но все же в чем преимущества. Ведь тогда недоступны некоторые выборки из базы и операции по преобразованию времени самой БД?

Ответить

 

Дмитрий Елисеев

Со встроенными типами TIMESTAMP и DATETIME и операциями работать можно, но в разных базах эти операции разные. А TIMESTAMP ещё и сам часовую зону учитывает, что может приводить к погрешностям. Да и всё равно при выводе на страницу их в нужный формат преобразовывать приходится.

А INT работает всегда и во всех базах. А все нужные выборки по дате можно успешно эмулировать переводя введённую пользователем дату c помощью strtotime(...).

Ответить

 

Александр

А если необходимо будет сделать вывод с группировкой по дате (CAST(timestamp_value AS DATE))? MySQL и остальные смогут использовать индексы?

Ответить

 

Дмитрий Елисеев

С вычисляемыми полями можно делать индексы в PostgreSQL. Если надо часто группировать по дате без времени в MySQL, то лучше рядом с timestamp для этой цели добавить ещё одну колонку формата DATE.

Ответить

 

shvarz

Спасибо за статью.
Но я не совсем понял , письма должны приходить только в папку
C:\work.ru\runtime\mail ?
Т.е. регистрируюсь , приходит письмо в папку, а как подтвердить и чтобы статус стал активный?
В базе пользователь сохранился , поле email_confirm_token не пустое.
Когда логинюсь пишет , что нужно подтвердить.
Вопрос: как сделать, чтобы отправлял на реальную почту и после подтверждения я мог зайти на свой сайт?

Ответить

 

Дмитрий Елисеев

В config/common-local.php у компонента mailer уберите строку:

'useFileTransport' => true,
Ответить

 

shvarz

Не помогло, наверно где-то накосячил, у меня свой класс не User, а Users.
Вроде все исправил, но на реальную почту не приходит, более того не работает отправка стандартной формы Contact из basic , пишет что отправлено все ок, на почте опять нету.
Есть ссылка на репозиторий этой статьи ,буду копаться что не так?
Можно в личку.
Заранее спасибо.

Ответить

 

Дмитрий Елисеев

Я обычно формирую письма так:

Yii::$app->mailer->compose()
    ->setTo($email)
    ->setFrom([Yii::$app->params['supportEmail'] => Yii::$app->name])
    ->setReplyTo([$this->email => $this->name])
    ->setSubject('New message')
...

При этом для надёжности отправляю письма по SMTP. Например, для того же Яндекса:

'mailer' => [
    'transport' => [
        'class' => 'Swift_SmtpTransport',
        'host' => 'smtp.yandex.ru',
        'username' => 'sample@yandex.ru',
        'password' => '*****',
        'port' => '587',
        'encryption' => 'tls',
    ],
],

чтобы supportEmail совпадал с username. Иначе почтовиками письмо с несовпадающим исходящим адресом определяется как спамное, а на MailRu вообще не доходит.

Ответить

 

shvarz

Спасибо,заработало.

Еще 2 вопроса:

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

ссылка вида /index.php?r=users%2Fconfirm-email&token=Ux10w64jTtgHCXOH_1XUu

confirm-email есть , все файлы есть где посмотреть в чем ошибка ?

Извините, если спрашиваю примитивные вопросы, учусь заново можно сказать.

Ответить

 

Дмитрий Елисеев

1) Зачем в открытом?
2) А модель по этому токену там находится?

Ответить

 

NEIRON

При /logout возникает ошибка:

Method Not Allowed (#405)
Method Not Allowed. This url can only handle the following request methods: POST.

Кто-то, Дмитрий и у вас тут в блоге и тут http://www.yiiframework.com/forum/index.php/topic/55483-method-not-allowed-405-solved/ написали, что в раздел HEAD необходимо добавить строки:

<?= Html::csrfMetaTags() ?>

Добавил, не помогло, проблема сохраняется.

Ответить

 

NEIRON

Спасибо. Вопрос решён. Вот этой строчки

<?= Html::csrfMetaTags() ?>

Достаточно

Ответить

 

Дмитрий Елисеев

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

Ответить

 

Egor Oldenburger

Возникла проблема.
Честно говоря на php особо не пишу, yii вообще решил только посмотреть.
Все делаю по урокам.

При запросе server.ru/web/logout - ошибка 404
При server.ru/web/user/default/logout - ошибка 405

В чем я мог накосячить, что посмотреть?

Ответить

 

Дмитрий Елисеев

Настройте сервер на папку web, чтобы адрес был server.ru/logout.

Ответить

 

Egor Oldenburger

Возможности нет, к сожалению.
Но я пока просто изучаю... Не подумал о правилах ЧПУ, поправил, теперь ок. Спасибо за наводку

Ответить

 

Роман

Здравствуйте! Никак не могу подтвердить свою регистрацию, письмо через smtp прекрасно отсылается, линк генерируется и он точно такой же как в базе, но при переходе высвечивается алерт " Ошибка подтверждения Email. ". Что может послужить причиной? А ещё почему-то не добавляется в базу username...

Ответить

 

Роман

Смотрел свойство user->username через дебаг, в нем торчит введенное имя

Ответить

 

Роман

getErrors - пустой массив...

Ответить

 

Дмитрий Елисеев

Проверьте, срабатывает ли поиск User::findByEmailConfirmToken($token) в форме ConfirmEmailForm.

Ответить

 

Роман

Всё исправилось само собой, сделав я pull на основном пк...

Ответить

 

Игорь

Здравствуйте!

Помогите разобраться с этим куском:
в дефолтном контроллере модуля User есть такой код:

  public function actionSignup()
    {
...
            if ($user = $model->signup()) {

...


Переменная $user нигде не используется. Более того, как смысл проверять успешность присвоения?
Я так полагаю, здесь должен быть просто $model->signup() ?
Столкнулся я с этим от того, что у меня операция save не прошла и модель вернула false. В процессе дебага вот наткнулся вот на эту строчку.

Ответить

 

Дмитрий Елисеев

Это осталось из yii2-app-advanced, где при регистрации пользователь сразу логинился:

if ($user = $model->signup()) {
    if (Yii::$app->getUser()->login($user)) {
        return $this->goHome();
    }
}

А у нас да, лишний $user можно и убрать.

Ответить

 

slo_nik

Доброй ночи.
Дошёл до этой статьи, вроде всё сделал как Вы описываете.
Но возникла проблема с восстановлением пароля.
При попытке запросить новый пароль выдавало ошибку

Invalid Parameter – yii\base\InvalidParamException
The view file does not exist: /home/slonik/localhost/www/cabinet/mail/passwordResetToken-html.php

Переименовал файл passwordResetToken.php в passwordResetToken-html.php, вдобавок создал ещё один файл passwordResetToken-text.php и добавил в mail/layouts файл text.php и всё заработало, письмо начало уходить, появлялся файл в runtime/mail
Но можно решить проблему изменив строку

return Yii::$app->mailer->compose(['html' => 'passwordResetToken-html', 'text' => 'passwordResetToken-text'], ['user' => $user])


на строку

return Yii::$app->mailer->compose(['html' => 'passwordResetToken'], ['user' => $user])


в файле modules/user/models/PasswordResetRequestForm.php

Исходя из этого два вопроса:
1) Каким должно быть содержание добавленных мной файлов?
3) Или не создавать дополнительные файлы, а просто изменить строку в классе PasswordResetRequestForm.php

Ответить

 

Дмитрий Елисеев

Это разделение изменилось в advanced шаблоне уже после написания статьи. Так что можете выбрать любой вариант.

Ответить

 

slo_nik

Значит содержимое файлов html и txt одинаково должно быть?

Ответить

 

Дмитрий Елисеев

Один в HTML-разметке, второй просто текстом.

Ответить

 

Сашка
['status', 'in', 'range' => array_keys(self::getStatusesArray())],

тут вроде 'range' лишнее.

Ответить

 

Дмитрий Елисеев

Почему?

Ответить

 

Сашка

Извиняюсь, все верно, это название 'range' в валидации меня периодически сбивает с толку...

Ответить

 

Сашка

При регистрации мы будем присваивать пользователю статус STATUS_WAIT,

ок, тогда почему здесь:

['status', 'default', 'value' => self::STATUS_ACTIVE],
Ответить

 

Дмитрий Елисеев

При регистрации мы присваиваем статуc STATUS_WAIT:

$user->status = User::STATUS_WAIT;

а по умолчанию STATUS_ACTIVE.

Ответить

 

Andrewkha

Скажите, а почему тогда при создании таблицы БД мы там указали в качестве значения по умолчанию 0, что означает BLOCKED?

Ответить

 

Дмитрий Елисеев

Просто так. Можете указать DEFAULT 1.

Ответить

 

Андрей

Добрый день.
Я новичок в Yii2. Поставлена задача реализации личного кабинета пользователя через этот фреймворк. Тут же возник вопрос: А как быть с проверкой логина/пароля пользователя(аутентификацией), которые заводятся админом в стороннем приложении, а через веб-часть к ним можно достучаться лишь через 6 join-ов в запросе? Везде примеры с таблицей users, которой у меня нет в БД и не будет. Каким образом формировать/переделать модель User? Ткните носом в нужном направлении. Спасибо.

Ответить

 

Дмитрий Елисеев

Можно, например, взять исходную безтабличную модель User из yii2-app-basic приложения и переписать все её методы.

Ответить

 

Сергей

огромное спасибо за статью

Ответить

 

Сергей Пуговкин
/**
 * Resets password.
 *
 * @return boolean if password was reset.
 */
public function confirmEmail()
{
    $user = $this->_user;
    $user->status = User::STATUS_ACTIVE;
    $user->removeEmailConfirmToken();

    return $user->save();
}

Resets password.
confirmEmail()

Wtf?

И почему в том примере на гитхабе такой мордоворот с именами файлов/классов, например:

PasswordResetRequestForm.php
ResetPasswordForm.php

и т.п. Почему не:

PasswordResetRequestForm.php
PasswordResetForm.php

?

Ответить

 

Дмитрий Елисеев

Спасибо! У себя исправил.

Не знаю, почему так в yii2-app-advanced.

Ответить

 

des

Да.... ваши вкусы весьма специфичны... даунгрейдить advanced до basic. не проще было advanced использовать уж. этож скока гемору вот так вот переносить.

Ответить

 

Дмитрий Елисеев

То есть вместо basic взять advanced и удалить frontend? Cмысла особого не вижу. А здесь всего-то модели и вьюшки скопировать и пути поменять.

Ответить

 

Art

Доброго.

небольшая опечатка: "Добавим автоподстановку знчений". Так же при добавлении в виджет новых пунктов снова появляется About, который в прошлом уроке был вычищен.

Ответить

 

Дмитрий Елисеев

Спасибо! Исправил.

Ответить

 

Art

И еще виджет Alert по предоставленной ссылке уже не существует. Может, вместо той ссылки поставить ссылку на ваш гитхаб?

Ответить

 

Дмитрий Елисеев

Его из frontend/widgets перенесли в common/widgets.

Ответить

 

temir – prosoft.kg

Спасибо за отличный урок! Если возможно хотел оставить пожелание: было бы хорошо увидеть как реализуется RBAC система на Yii2 basic

Ответить

 

Дмитрий Елисеев

Про RBAC здесь затрону немного. Подробно про роли расскажу на вебинаре после выпуска о тестировании. Так что записывайтесь в форму чтобы не пропустить.

Ответить

 

Наталия

К сожалению выполняла всё как написано, но выводит ошибку 404 на страницы регистрации и авторизации.
Так же в комментариях наисано что исправлены различия в названиях на гитхабе и в тексте:
- PasswordResetRequestForm.php
- ResetPasswordForm.php

Есть ли шаблон для загрузки чтоб распаковать и он заработал?

Ответить

 

Дмитрий Елисеев

Можно посмотреть репозиторий. Или установить проект заново:

composer global require "fxp/composer-asset-plugin:~1.0.0"
composer create-project --prefer-dist --stability=dev elisdn/seokeys project
Ответить

 

Наталия

Но там в web/ отсутствует файл index.php
Подскажите пожалуйста, что необходимо добавить туда?

Ответить

 

Дмитрий Елисеев

Запустите и выберите dev окружение:

php init

Впишите настройки базы в config/common-local.php и запустите миграции:

php yii migrate

После этого создайте пользователя

php yii users/create
Ответить

 

Gevy

здравствуйте . у меня такая проблема , установил через 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/. Исправьте хост на сервере.

Ответить

 

Gevy

Исправил host : DocumentRoot /var/www/html/adsnew/web, но все та же ошибка.

Сайт hit-movie.ru. footer стал работать но вот ссылки на navbar нет.

Ответить

 

Дмитрий Елисеев

Добавьте файл .htaccess

Ответить

 

Алексей

Почему мы не удаляем пользователя через какоето время, если он не подтвердил регистрацию?

Ответить

 

Дмитрий Елисеев

Кстати да, можно сделать консольную команду для cron.

Ответить

 

Uterm

Из метода signup() модели SignupForm можно удалить $user->generateAuthKey(), т.к. это делает метод beforeSave() в модели User.

Ответить

 

Дмитрий Елисеев

Переместил представления писем в папку модуля (в modules/user/mail).

Ответить

 

Александр

Как то нелогично называть модель EmailConfirmForm, ведь она не описывает форму! Может логичней перенести этот функционал в User!?

Ответить

 

Анонимус

Димка не отвечает на комментарии дибилов, а я отвечу, что вы далбаебы. Ущербные и деградирующие индивидумы. Схуяли ЧоТоТамForm нелогично, затупок ты ебаный?! Ты чо, считаешь что Form, это только HTML-форма? Да ты сука олень просто неебический. Эта часть сделана верно, блеать, дубина ты. Иди сука от сюда вообще. Заебали дегенераты в сообществе Yii. Одни нубы и ебланы. Толпы ебланов и нубов. И сука, не логичней переносить функционал в модель User, не логично нахуй, потому что там вообще ничего не должно быть, по сути, кроме методов для работы с ActiveRecord! Все остальное должно быть в Сервисах, Провайдерах, и, ахуеть да, внимание блядь, ФОРМАХ! Иди на хуй отсюда, чмо тупое. Убейся сука ап стену. Я все.

Ответить

 

Александр

Тебе что, мама дрочить помешала?

Ответить

 

des

дрянь а не руководство. устарело. вообще другие пути. и лучше (читай - правильно) сделать без привязки к предыдущим урокам, а с нуля для дефолтного приложения. потратил час - на выходе ничего. минус вам в карму

Ответить

 

Анонимус

А ты еще больше затупок, чем дибил выше тебя комментарием.

Ответить

 

Дмитрий Елисеев

Да-да, помню. Вы во ВКонтакте жаловались, что файл миграции не находит, так как Вы класс миграции скопипастили, а переименовать под своё имя файла забыли.

Ответить

 

Дмитрий

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

Ответить

 

Дмитрий Елисеев

Где именно? В SignupForm?

Ответить

 

Дмитрий

Да, именно там.

Ответить

 

Дмитрий Елисеев

Ну в SignupForm присваивается, вроде, правильно:

$user->username = $this->username;
$user->email = $this->email;
Ответить

 

Grey

Пытаюсь сделать отправку письма на почту, а не в runtime выводит ошибку:

Exception 'Swift_TransportException' with message
'Connection could not be established with host smtp.google.com
[php_network_getaddresses: getaddrinfo failed: Name or service not known #0]' 

С чем это может быть связанно? Всё делал по инструкции, useFileTransport удалил.

Ответить

 

Дмитрий Елисеев

У Гугла smtp.gmail.com:

'mailer' => [
    'class' => 'yii\swiftmailer\Mailer',
    'transport' => [
        'class' => 'Swift_SmtpTransport',
        'host' => 'smtp.gmail.com',
        'username' => 'username@gmail.com',
        'password' => 'password',
        'port' => '587',
        'encryption' => 'tls',
    ],
],
Ответить

 

Grey

Вставил, теперь yii2 пишет ошибку:
Ошибка (#535)
Возникла внутренняя ошибка сервера.
С чем она может быть связана?

Ответить

 

Дмитрий Елисеев

Включите дебаг (или откройте логи) и текст ошибки посмотрите.

Ответить

 

Grey

Гугл написал, что: Заблокирована попытка входа в аккаунт, видать надо искать в настройках почты?

Ответить

 

Grey

Разобрался в чём дело надо разрешить доступ к ненадёжным аккаунтам в гугле.

Ответить

 

Сергей

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

Exception (Unknown Class) 'yii\base\UnknownClassException' with message 'Unable to find 'app\modules\user\models\PasswordResetForm' in file: /var/www/localhost/html/MySite/modules/user/models/PasswordResetForm.php. Namespace missing?' 


С чем она может быть связана?

Ответить

 

Дмитрий Елисеев

Видимо в PasswordResetForm Вы namespace забыли поменять.

Ответить

 

Сергей

namespace в PasswordResetForm менял , на вот такой app\modules\user\models
Извиняюсь, что сразу полный код ошибки не привёл

Exception (Unknown Class) 'yii\base\UnknownClassException' with message 'Unable to find 'app\modules\user\models\PasswordResetForm' in file: /var/www/localhost/html/MySite/modules/user/models/PasswordResetForm.php. Namespace missing?' 
in /var/www/localhost/MySite/vendor/yiisoft/yii2/BaseYii.php:291

Может у меня в другом месте проблема?

namespace app\modules\user\models;

class ResetPasswordForm extends Model
{
    ...
}
Ответить

 

Дмитрий Елисеев

Переименуйте класс с ResetPasswordForm на PasswordResetForm.

Ответить

 

Сергей

Спасибо проблема была действительно а этом.

Ответить

 

Макс

А как сделать чтобы можно было оставлять комментарии без регистрации? Создавать отдельную таблицу в базе или сохранять пользователей в таблице user?

Ответить

 

Дмитрий Елисеев

Любым вариантом. Можно в саму таблицу комментария добавить поля для имени и почты. И заполнять их если в модели поле user_id пустое.

Ответить

 

Дмитрий

Здравствуйте Дмитрий.
Вот сколько вижу примеров на эту тему и возникает вопрос, почему форма для регистрации не наследует модель "User". Ведь можно наследовать "User" переопределить нужные нам методы, допустим валидацию и все готово. А так мы создаем модель которая по сути просто проверяет корректность данных и "кривым" способом передаем эти данные в модель "User" в случае их валидности.

Почему кривым - потому что каждое свойство модели мы присваиваем "передаем" руками другой моделе, а в случае наследования вызвали бы просто "load" и все готово.

Ответить

 

Дмитрий Елисеев

Проще всего свалить все методы прямо в User и разрулить атрибуты сценариями. В итоге вместо нескольких примитивных классов получаем неповоротливого монстра из десятка сценариев, сотни методов и свойств :)

Наследовать полностью всю ActiveRecord-модель User только ради списка атрибутов - слишком жирно. По сути User - это ActiveRecord (строка из базы), а SignupForm - уже не ActiveRecord. А если отнаследуемся, то обе станут AR. Получается ерунда по смыслу.

Так уж получилось, что Yii позволяет прямо саму User в форму выводить, так как ActiveRecord наследуется от Model. Если бы было явное встроенное разделение ответственностей, то отнаследоваться было бы невозможно, и всегда бы приходилось делать отдельную модель для каждой формы, и таких соблазнов лишний раз отнаследоваться вообще бы не возникало. Но имеем что имеем: даже генерируемая через Gii форма поиска для CRUD наследуется от ActiveRecord-модели вопреки здравому смыслу, но ради мнимого удобства.

А если лень.вручную перечислять все атрибуты, то работает то же массовое присваивание:

$user->setAttributes($this->getAttributes());

Это удобнее, когда полей много.

Ответить

 

Akulenok

Подскажите с миграциями, мне понадобилось в таблицу юзеров добавить пару новых полей. Уже после создания таблицы через миграции.
Я дописал их в файл m160118_100701_create_user_table.php выполняю

php yii migrate/up
Yii Migration Tool (based on Yii v2.0.7-dev)

No new migrations found. Your system is up-to-date.

Как правильно их добавить ?

удалить базу и заново делать migrate/up? но мне надо сохранить данные в таблице

Ответить

 

Дмитрий Елисеев

Создать новую миграцию с addColumn().

Ответить

 

Александр

Добрый день, Дмитрий!
Обратил внимание, что виджет yii\bootstrap\Nav ругается на отсутствие ключа 'label' в передаваемом ему массиве, когда ему передается false из условия (этот код из меню layout шаблона):

Yii::$app->user->isGuest ?
    ['label' => 'Signup', 'url' => ['/user/default/signup']] :
    false,


Если заменить

false

на

['label' => false]

вроде все работает, но не знаю, это из-за каких-то изменений в виджете с момента написании статьи или по другой причине.

Ответить

 

Дмитрий Елисеев

Там передаётся не просто массив:

'items' => [ ... ],

а уже отфильтрованный:

'items' => array_filter([ ... ]),
Ответить

 

Анатолий Гедзюк

Здравствуйте.
Извините, я разбираюсь с вашей статей, все сделал как у вас и при регистрации после нажатия на отправить перехожу на страницу home со строкой подтвердить электронную почту, а вот на почту ссылка не приходит???

Ответить

 

Дмитрий Елисеев

А в папке runtime/mail появляется?

Ответить

 

Анатолий Гедзюк

Извините, я не силен в yii2 только изучаю, и Yii2 basic , а в какой папке искать runtime/mail. Строку 'useFileTransport' => true',я убрал?
Спасибо.

Ответить

 

Дмитрий Елисеев

Там так папка и называется runtime. Настройте отправку с реальной почты. Для Яндекса это, например, так:

'mailer' => [
    'class' => 'yii\swiftmailer\Mailer',
    'transport' => [
        'class' => 'Swift_SmtpTransport',
        'host' => 'smtp.yandex.ru',
        'username' => 'qwerty@yandex.ru',
        'password' => 'qwerty',
        'port' => '587',
        'encryption' => 'tls',
    ],
],

И в параметрах конфига укажите такой же адрес в fromEmail.

Ответить

 

Анатолий Гедзюк

Выдает ошибку:

Expected response code 250 but got code "535", with message "535-5.7.8 Username and Password not accepted. Learn more at
535 5.7.8 https://support.google.com/mail/answer/14257 zs6sm3528190lbb.38 - gsmtp

Я настраивал для Гугла:

'mailer' => [
    'class' => 'yii\swiftmailer\Mailer',
    'transport' => [
        'class' => 'Swift_SmtpTransport',
        'host' => 'smtp.gmail.com',
        'username' => 'username@gmail.com',  //тут моя почта на гугле
        'password' => 'password',                     //пароль входа на почту
        'port' => '587',
        'encryption' => 'tls',
    ],
],

В параметрах
'supportEmail' => 'тут моя почта на гугле',

Ответить

 

Дмитрий Елисеев

Ответ прямо по ссылке из текста ошибки. Разрешите непроверенным приложениям доступ к аккаунту.

Ответить

 

Анатолий Гедзюк

Извините мою неграмотность, а как разрешить.

Ответить

 

Дмитрий Елисеев

Перейти по адресу, щёлкнуть "разрешить непроверенным приложениям доступ" и сделать как в "Вариант 2".

Ответить

 

Анатолий Гедзюк

Огромное спасибо, заработало.

Ответить

 

Егор

при переходе на логин и сайнап выдаёт ошибку:

yii\base\UnknownPropertyException	exception 'yii\base\UnknownPropertyException' with message 'Setting unknown property: yii\filters\AccessRule::action' in C:\xampp\htdocs\yii2study\project\vendor\yiisoft\yii2\base\Component.php:197
Ответить

 

Дмитрий Елисеев

actions

Ответить

 

Анатолий – kali.net.ru

Здравствуйте!
Спасибо, за ваш труд.
У меня получается так: вылетают ошибки, исправляю.
Некоторые моменты я уже не помню, но вод из последних:
сперва написано user/mail... потом уже user/mails..., имена файлов в шаблоне advanced другие, там ResetPasswordForm, а не PasswordResetForm.

И, как мы уже говорили, изменим адрес до представления письма на полный:
Yii::$app->mailer->compose('@app/modules/user/mails/passwordReset', ['user' => $user]


Это где?

Нам осталось скопировать представления login, passwordReset и passwordResetRequest


Куда? просто user/views/default ligin.php уже есть, а про замену не слова.
И так далее... Опять же, статья супер, спасибо, но непонимание некоторых моментов осложняет восприятие.

Ответить

 

Анатолий Белов

Надо было сразу у вас аккаунт завести...
К своему комментарию выше добавлю, что этот проект выложен на Github и можно посмотреть, что и как. По этому все негодования снимаются.

Ответить

 

Андрей

Доброго времени суток Дмитрий!
В первую очередь примите мою благодарность за ваш труд и за то, что не оставляете без ответа ни одного вопроса в комментариях.
Мой вопрос возможно не по теме статьи, но, так как возник он именно в процессе её прочтения, задам его тут. Если ответ на него уже есть, пожалуйста дайте ссылку.
Меня интересует вопрос того, как правильно в Yii2 advanced сделать разделение конфигов для development и production (например данные для подключения в базе данных, данные для подключения к smtp и т.д.).

Ответить

 

Shvarz

В файле PasswordResetRequestForm написано:

->compose(['html' => 'passwordReset'], ['user' => $user])

Пишет ошибку

Invalid Parameter – yii\base\InvalidParamException
The view file does not exist: /home/boris/sites/basic/mail/passwordReset.php

Я так понял нужно добавить файл с таким именем, куда и какое его содержимое.
Зарнее спасибо...

Ответить

 

Shvarz

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 у класса.

Ответить

 

Shvarz

захожу в почту и нажимаю на ссылку для смены пароля:
Пройдите по ссылке, чтобы сменить пароль: /user/default/password-reset-form?token=yuXy9mWXxzcMWTqpjRm5elJMzyH12liN_1462700117

$resetLink = Yii::$app->urlManager->createAbsoluteUrl(['user/default/password-reset-form', 'token' => $user->password_reset_token]);

Page not found.

Т.е. должно быть представление в modules\user\default\passwordResetForm
как должно выглядеть представление, я его не нашел в описании и в структуре...

Ответить

 

Дмитрий Елисеев

Откуда у Вас взялось password-reset-form ?

Ответить

 

Shvarz

спасибо, да там без -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/ выводится ошибка:

Exception (Unknown Property) 'yii\base\UnknownPropertyException'
with message 'Setting unknown property:
yii\captcha\CaptchaValidator::captchaActon'

С чем это может быть связано?

Ответить

 

Дмитрий Елисеев

С тем, что написали Acton вместо Action.

Ответить

 

Иззет Фатуллаев

Пойду заказывать себе очки :) Впредь буду каждую букву по два раза перечитывать.
Спасибо за помощь.

Ответить

 

Игорь

В маршрутах есть правила

'<_m:[\w\-]+>/<_c:[\w\-]+>/<_a:[\w\-]+>/<id:\d+>' => '<_m>/<_c>/<_a>'

я понимаю что _m, _c, _a являются соответственно модулем контроллером и действием, но не могу понять на каком основании такие сокращения? Где об этом можно почитать? Откуда такие сокращения?

Ответить

 

Дмитрий Елисеев

Никаких оснований нет. Можно как угодно называть:

<module:[\w\-]+>/<controller:[\w\-]+>/<action:[\w\-]+>

Просто _m короче.

Ответить

 

Игорь

Ничего не понимаю. Я думал что это зарезервированное слово в шаблонах маршрутизации?

Ответить

 

Дмитрий Елисеев

Зарезервированных слов в маршрутизации нет. Называйте как угодно:

'<param1:[\w\-]+>/<param2:[\w\-]+>/<param3:[\w\-]+>/<id:\d+>' => '<param1>/<param2>/<param3>'
Ответить

 

Игорь

Все я кажется понял. Он просто потом заглядывает в эти правила и смотрит если a/b/c - значит ищет модуль/контроллер/действие, если a/b - значит контроллер/действие.

Я правильно все понял?

Ответить

 

Игорь

То есть ключевыми разделителями здесь являются слеши.

Ответить

 

Игорь

И когда я пишу

=> site/<param3>

(правая часть) будет искать контроллер site с действием param3, то есть сработает автоподстановка при разборе правила.

Ответить

 

Ро

Посыпались ошибки после

php composer.phar global require "fxp/composer-asset-plugin:1.0.*@dev"
Class Fxp\Composer\AssetPlugin\Repository\NpmRepository does not exist

У меня более новая стояла. Откатился обратно.
Прежде чем обновлять composer, думаю стоит версию проверить.

Ответить

 

Дмитрий Елисеев

Выполните

composer global status

и в показанной папке удалите vendor/fxp. После этого запустите снова

composer self-update
composer global require "fxp/composer-asset-plugin:~1.1.1"
Ответить

 

zitttz

Добрый день, спасибо за статьи, помогают очень.
Почему в Model Generator(Gii) в поле table name мы пишем "keys_user"? таблица же просто user называется. Это потому что мы используем префикс в common-local.php? Т.е. если я без префикса работаю то писать user? к чему он вообще этот префикс? ведь он получиться у всех таблиц одинаковый.
Где потом в созданной модели указывается с какой таблицей работать? Ну никак найти не могу. Спасибо, за терпение.

Ответить

 

Дмитрий Елисеев

У меня таблица с префиксом. Пишите так, как реально называется в базе. Разные префиксы используются при хранении нескольких сайтов сразу в одной базе на хостинге. В модели указывается в tableName().

Ответить

 

slo_nik

Доброй ночи.
Подскажите, пожалуйста, как лучше организовать пошаговую регистрацию?
Последовательность такая:
1 шаг. Пользователь вводит какой-либо текст в textarea, нажимает "проверить".
2 шаг. Вводит email, пароль, подтвердить пароль.
3 шаг. Попадает в кабинет, на почту получает письмо с ссылкой-подтверждением.

Сейчас реализовано так, что если пользователь ввёл текст и email, то нажав на кнопку "проверить" попадает на страницу входа, а на почту получает письмо с ссылкой, логином и паролем.

Ответить

 

Дмитрий Елисеев

1. Проверить и сохранить в сессии.
2. Извлечь из сессии и зарегистрировать.

Ответить

 

slo_nik

Постоянно обращаясь к одному и тому же action и render один и тот же вид? В зависимости от шага показывать соответствующую часть вида?

Ответить

 

Дмитрий Елисеев

Можно один экшен. А можно два, где в зависимости от того, есть текст в сессии или нет редиректить с первого на второй или со второго на первый.

Ответить

 

slo_nik

Тест может быть, а может и не быть, остальное, естественное, обязательно будет.
Через get параметр передавать номер шага, или что-то ещё, что бы определить на каком шаге находимся?
Вот только чуть не понял.
Если два action, то на каком этапе выяснять, есть текст или нет?
Или я туплю?
В первом проверить, есть или нет, и от результата продолжать...
Если нет текста, то редирект на второй action, а если есть, то писать текст в сессию и тоже редирект на второй?

Ответить

 

Дмитрий Елисеев
public function actionText
{
    $session = Yii::app->session;
    if ($session['text'])) {
        return $this->redirect(['form']);
    }    
    $model = new TextForm();
    if ($model->load(...) && $model->validate()) {
        $session['text'] = $model->text;
        return $this->redirect(['form']);
    }
    return $this->render('text', [
        'model' => $model,
    ]);
}
public function actionForm
{
    $session = Yii::app->session;
    if (!$text = $session['text'])) {
        return $this->redirect(['text']);
    }
    $model = new SignupForm($text);
    if ($model->load(...) && $model->signup()) {
        $session->remove('text');
        $session->setFlash('success', 'Спасибо!');
        return $this->goHome();
    }
    return $this->render('form', [
        'model' => $model,
    ]);
}
Ответить

 

slo_nik

Благодарю, но есть одно "но".
Я написал, что надо проверить, если есть текст, то записать в сессию и редирект на второй экшен.
Мне не надо писать в базу, а просто для начала сохранить куда-то текст.

Получается, что мне надо в первом экшене проверить, если нет текста, то сразу редирект на второй экшен, если есть текст, то сначала занести в сессию.

Во втором экшене мне нужно просто проверить и если есть текст, то провести регистрацию, а уже после этого текст писать в базу.

Из Ваших ответов я понял, как это делается, но Вы как всегда добросовестно отнеслись к вопросу и снабдили рабочим примером.
Благодарю ещё раз.

p.s. ну вот так напутано в проекте, сначала текст, а потом уже регистрация)))

Ответить

 

slo_nik

Во втором экшене мне нужно просто проверить и если есть текст, то провести регистрацию, а уже после этого текст писать в базу.

Вернее так сделать.
Во втором экшене проверить, есть или нет текст, и если есть, то сначала регистрация, а потом писать текст в базу. Если нет текста, то сразу регистрация.

Вот такая путаница

Ответить

 

Новичек

Настраиваю basic по вашим статьям, но вот при регистрации не сохраняется имя пользователя хотя остальные параметры сохраняются не понимаю в чем проблема подскажите пожалуйста

вот код из Signup form

class SignupForm extends Model
{
    public $username;
    public $email;
    public $password;
    public $verifyCode;

    public function rules()
    {
        ...
    }

    public function attributeLabels()
    {
        return [
            'id' => 'ID',
            'username' => Yii::t('app', 'USER_USERNAME'),
            'email' => Yii::t('app', 'USER_EMAIL'),
            'password' => Yii::t('app', 'USER_PASSWORD'),
            'verifyCode' => Yii::t('app', 'USER_VERIFYCODE'),
        ];
    }
    
    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;
    }
}

Код из User модели

class User extends ActiveRecord implements IdentityInterface
{
    const STATUS_BLOCKED = 0;
    const STATUS_ACTIVE = 1;
    const STATUS_WAIT = 2;

    public $id;
    public $username;
    public $password;
    public $authKey;
    public $accessToken;

    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',
        ],
    ];


    public function rules()
    {
        ...
    }

    public function attributeLabels()
    {
        ...
    }
    

    public function behaviors()
    {
        return [
            TimestampBehavior::className(),
        ];
    }

    public static function findIdentity($id)
    {
        return static::findOne(['id' => $id, 'status' => self::STATUS_ACTIVE]);
    }

    public static function findIdentityByAccessToken($token, $type = null)
    {
        throw new NotSupportedException('findIdentityByAccessToken is not implemented.');
    }


    public static function findByUsername($username)
    {
        return static::findOne(['username' => $username]);
    }
...

и код из контроллера

...
    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 $id;
public $username;
public $password;
public $authKey;
public $accessToken;

private static $users = [...];

из модели User.

Ответить

 

Новичек

Спасибо Дмитрий теперь работает

Ответить

 

Igor

> И, как мы уже говорили, изменим адрес до представления письма на полный:

Yii::$app->mailer->compose('@app/modules/user/mails/passwordReset', ['user' => $user])

Я долго искал куда и вместо чего вот ето всавлять, плюнул, наривался на ошибки, долго доганял где и что глючит, я ведь новичек в пхп. Допишите для других, в каком модуле, функции ето вписивать и вместо чего. Ведь там массив менять на строку.

Ответить

 

Дмитрий Елисеев

В предыдущих строках посмотрите.

Ответить

 

Евгений

Здравствуйте! А можно где-нибудь взять исходники урока - у меня ничего не получилось из basic приложения по вашему уроку - оно попросту отказалось работать и выдавало ошибку за ошибкой?

Ответить

 

Дмитрий Елисеев

Самая первая ссылка в статье.

Ответить

 

Евгений

Спасибо видел и не раз уже устанавливал и перебирал ваш модуль. Тяжело с ним работать. Мне лишь хочется понять, как себе сделать такой гибкий rbac, чтоб потом управлять ролями, которые хранятся в БД.

Ответить

 

Дмитрий Елисеев

Для управления ролями и разрешениями есть готовый модуль yii2-rights.

Ответить

 

Евгений

Спасибо большое, не знал о таком.

Ответить

 

Stas

При восстановлении пароля и переходе по ссылке с вида логин пишет(если ссылка в логине прописана как reset-password):

Bad Request (#400)
Missing required parameters: token
The above error occurred while the Web server was processing your request.
Please contact us if you think this is a server error. Thank you.

а если ссылка прописана в логине как request-password-reset-token:

Not Found (#404)
Page not found.
The above error occurred while the Web server was processing your request.
Please contact us if you think this is a server error. Thank you.

В чем проблема не знаю (делал без модулей просто перенося файлы с advanced остальное работает вроде)

Ответить

 

Stas

И еще у вас resetPassword в папке mails зависит от Module. Не знаю правильно ли я сделал: переделал в модель поменял namespace, и строчку:

return Yii::t('modules/main/'

поменял на

return Yii::t('app/'
Ответить

 

Alexander

Присоединяюсь к поблагодарившим :)
Очень полезная статья!

Ответить

 

Ратибор Харьков

почему id в таблице user не сделан AUTO_INCREMENT? стал AUTO_INCREMENT, но я не понял почему??

Ответить

 

Дмитрий Елисеев

Тип $this->primaryKey() как раз делает первичный ключ с инкрементом.

Ответить

 

Ратибор Харьков

primaryKey и AUTO_INCREMENT это же вроде разные вещи, а если primaryKey без А_I необходим?

Ответить

 

Дмитрий Елисеев

Тогда создавайте простое поле:

'id' => $this->integer()->notNull(),

и вручную навешивайте ключ:

$this->addPrimaryKey('pk-user', '{{%user}}', 'id');
Ответить

 

Ратибор Харьков

немного понял - спасибо

Ответить

 

Igor

В общем, куча несоответствий в именах файлов в статье и на git...
Сорри, но понять о каком именно файле идет речь - НЕВОЗМОЖНО.
Пример.
В статье
Нам осталось скопировать представления login, passwordReset и passwordResetRequest из похожих в папке frontend/views/site.

по ссылке
requestPasswordResetToken.php
resetPassword.php

и что чему соответствует?

Ответить

 

Дмитрий Елисеев

resetPassword - passwordReset
requestPasswordResetToken - passwordResetRequest

Ответить

 

Ержан Шаймерденов

Спасибо большое за то что вы делаете.

Ответить

 

Александр

Спасибо за статью,
Все очень интересно описываете.
Только почему-то ничего не выходит...
При миграции ошибка

PHP Fatal error:  Uncaught yii\base\UnknownPropertyException: Setting unknown property: yii\console\ErrorHandler::errorAction in /home/user/sites/blog/html/vendor/yiisoft/yii2/base/Component.php:201
Stack trace:
#0 /home/user/sites/blog/html/vendor/yiisoft/yii2/BaseYii.php(525): yii\base\Component->__set('errorAction', 'main/default/er...')
#1 /home/user/sites/blog/html/vendor/yiisoft/yii2/base/Object.php(105): yii\BaseYii::configure(Object(yii\console\ErrorHandler), Array)
#2 [internal function]: yii\base\Object->__construct(Array)
#3 /home/user/sites/blog/html/vendor/yiisoft/yii2/di/Container.php(381): ReflectionClass->newInstanceArgs(Array)
#4 /home/user/sites/blog/html/vendor/yiisoft/yii2/di/Container.php(156): yii\di\Container->build('yii\\console\\Err...', Array, Array)
#5 /home/user/sites/blog/html/vendor/yiisoft/yii2/BaseYii.php(344): yii\di\Container->get('yii\\console\\Err...', Array, Array)
#6 /home/user/sites/blog/html/vendor/yiisoft/yii2/di/ServiceLocator.php(135): yii\BaseYii::createObject(Array)
#7 in /home/user/sites/blog/html/vendor/yiisoft/yii2/base/Component.php on line 201
Ответить

 

Дмитрий Елисеев

Для консоли компонент 'errorHandler' в конфигурации не нужен.

Ответить

 

Александр

Можно немного подробнее?
Не могу найти... и grep почему-то перестал писать в каком файле находит...

Ответить

 

Александр

Спасибо за уроки.
Помогите, пожалуйста, разобраться.
В конце при попытке создать пользователя у меня возникает ошибка

Class app\modules\user\models\User contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (yii\web\IdentityInterface::validateAuthKey)
Ответить

 

Дмитрий Елисеев

Добавьте метод validateAuthKey.

Ответить

 

Александр Cтепанов

Большое спасибо.
Так и думал, что где-то что-то пропустил... Удалил проект и начал с первого...
Как показывает действительность - теряюсь в больших объемах данных...но надо выучиться.
Извините, что засыпаю Вас такими глупостями, но мне оооочень нужно разобраться в этом вопросе.
Т.ч. запаситесь валерьянкой - я еще глупых вопросов Вам кучу задам. (:
И еще раз спасибо за статьи и ответы

Ответить

 

Иван Сухарев

Здравствуйте, .Дмитрий!
Прежде всего - спасибо вам за этот проект, который многим людям оказал существенную помощь в процессе программирования сайтов. Особо отмечу то, что ваш подход к публикациям и вебинарам позволяет не просто использовать ваш код, но приносит большое удовольствие от последующей работы, потому что наконец-то начинаешь понимать многие вещи "от" и "до".
У меня возник такой вопрос.
Как сделать, чтобы в процессе регистрации пользователь мог вводить кириллицу, латиницу, цифры, дефис и подчеркивание вперемежку? Как я понял, для этого нужно изменить регулярное выражение в

class SignupForm extends Model
{
  ...
    public function rules()
    {
        return [
            ['username', 'filter', 'filter' => 'trim'],
            ['username', 'required'],
            ['username', 'match', 'pattern' => '#^[\w_-]+$#is'],
  ...


на такое:

/[A-Za-zА-Яа-я0-9_\-]/

Но после замены этого выражения и использования смеси цифр и букв при регистрации в поле name, несмотря на то, что оно валидируется, после отправки формы происходит редирект на эту же страницу и все, никакого результата.
И еще, разве \w не равно A-Za-zА-Яа-я ? Почему же тогда не примается кириллица в этом поле? И какую роль выполняет # в начале и конце выражения? Это вместо слешей?
Помогите разобраться.

Ответить

 

Дмитрий Елисеев

Для работы с нелатинскими символами используется модификатор юникода u. В кириллице символ Ё не входит в диапазон А-Я, поэтому нужно его указывать отдельно:

'/^[a-zа-яё0-9_\-]+$/uis'

Символ # или любой другой можно вписывать вместо слешей.

Ответить

 

Иван Сухарев

Спасибо за подсказку по регулярным выражениям.
Теперь сделал так:

class SignupForm extends Model
{
...
    public function rules()
    {
        return [
            ['username', 'filter', 'filter' => 'trim'],
            ['username', 'required'],
            ['username', 'match', 'pattern' => '#^[a-zа-яё0-9_\-]+$#uis'],
...


Но и в этом случае при использовании кириллицы в поле name во время регистрации происходит редирект на эту же страницу. Данные никуда не уходят. Латиница, подчеркивание и пробел проблем не вызывают. Не понимаю, почему так происходит. Прошу помощи.
Все остальные функции с именем, введенным не кириллицей, работают - регистрация, аутентификация, восстановление пароля, отправка писем из формы контактов.
Сложилось ощущение, что где-то в другом месте есть какой-то фильтр или условие, потому что при комментировании или удалении из правил строки с регулярным выражением происходит все та же перезагрузка страницы. Опять же в случае использовании кириллицы. В чем может быть причина?

PS
Для корректной работы с кириллицей в форме регистрации, помимо изменений в class SignupForm extends Model нужно таким же образом изменить правила в

class User extends ActiveRecord implements IdentityInterface
...
    public function rules()
    {
        return [
            ['username', 'required'],
            ['username', 'match', 'pattern' => '/^[a-zа-яё0-9_\-]+$/uis'],


Об этом я вообще не подумал...
Теперь все работает. Ура!

Ответить

 

Алексей

Предлагаю сделать на этом сайте функцию, которая растягивает поле с кодом в статьях до конца экрана вправо, а то постоянно скролить приходится.

Ответить

 

Алексей

А как сделать чтобы при подтверждении почты автоматически авторизовываться?

Ответить

 

Дмитрий Елисеев

Дописать:

Yii::$app->user->login($user);
Ответить

 

Александр

Советую использовать не 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

class Adminer extends \yii\base\Object implements \yii\web\IdentityInterface

и уже в самой форме AdminForm.php в геттере подключать его?

public function getUser()
{
    if ($this->_user === false) {
        $this->_user = Adminer::findByUsername($this->username);
    }

    return $this->_user;
}

а как потом сделать разграничение?

допустим по этой же аналогии сделаю и для партнеров и авторов, как запретить доступ авторам в админку и в партнерский кабинет? и наоборот, партнерам в авторский и админский?

Помогите!

Ответить

 

Виктор Шурыгин

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

в модуль прописал так. все работает, конечно костыль...

\Yii::$app->set('user', [
    'class' => 'yii\Web\User',
    'identityCookie' => ['name' => '_adm', 'httpOnly' => true],
    'idParam' => '__adm_id',
    'identityClass'  => 'app\modules\admin\components\AdminIdentity',
    'loginUrl' => ['admin/auth/login'],
    'enableAutoLogin' => true,
    'authTimeout' => '300',
    'authTimeoutParam' => '___authTimeoutParam',			
]);

может кто подскажет, как решить сей проблему?

Ответить

 

Kirill Nagovitsyn

Почему для скрытия пунктов меню не используется параметр visible?
Как по мне такой вид меню выглядит более аккуратно, да и array_filter не нужен, Yii сам все отфильтрует.

echo Nav::widget([
    'options' => ['class' => 'navbar-nav navbar-right'],
    'items' => [
        [
            'label' => Yii::t('app', 'NAV_HOME'),
            'url' => ['/main/default/index'],
        ],
        [
            'label' => Yii::t('app', 'NAV_CONTACT'),
            'url' => ['/main/contact/index'],
        ],
        [
            'label' => Yii::t('app', 'NAV_SIGNUP'),
            'url' => ['/user/default/signup'],
            'visible' => Yii::$app->user->isGuest,
        ],
        [
            'label' => Yii::t('app', 'NAV_LOGIN'),
            'url' => ['/user/default/login'],
            'visible' => Yii::$app->user->isGuest,
        ],
        [
            'label' => Yii::t('app', 'NAV_PROFILE'),
            'url' => ['/user/profile/index'],
            'visible' => !Yii::$app->user->isGuest,
        ],
        [
            'label' => Yii::t('app', 'NAV_LOGOUT'),
            'url' => ['/user/default/logout'],
            'linkOptions' => ['data-method' => 'post'],
            'visible' => !Yii::$app->user->isGuest,
        ],
    ]),
]);
Ответить

 

Дмитрий Елисеев

Да, можно и так.

Ответить

 

андрей

А есть статьи с более человечный объяснением? Без всяких бесполезных миграций и gii. Новичкам это не нужно

Ответить

 

андрей

Документацию не нужно советовать, она еще более бесполезна

Ответить

 

Дмитрий Елисеев

Тогда больше нет.

Ответить

 

андрей

Жаль( Трудно разобраться, очень, доку прочитал, только запутался еще больше( Примеров бы по больше из жизни.. Но почему то никто не хочет делать подобный справочник, не понимаю почему(

Ответить

 

Дмитрий Елисеев

Есть другие примеры вроде каталога. Но они тоже с миграциями.

Ответить

 

андрей

еще и смешали advanced... нахер новичкам такие уроки? С басисом трудно разобраться, он еще всякое говно приклеивает .... гавнокодеры они такие

Ответить

 

андрей

Короче нихера не работает, опять придется удалять проект.. ну что за дауны рукожопые пишут подобные статьи... Поймите, гавнокодеры, вы обязаны писать для новичков, чтобы им было понятно, а не для того, чтобы повыебываться! Заебали блять! Когда вы исчезните уроды! Поколение доков , компостеров, и всякой лажи бесполезной

Ответить

 

Дмитрий Елисеев

А зачем тогда вам фреймворки? Ставьте Wordpress и не парьтесь.

Ответить

 

HiStO rIaN

Лучше спросите, зачем такие статьи, где ничего путного не объяснено. Они обязаны быть для новичков, так как для профи статьи не нужны, они и сами все знают. В ней вообще ничего не описано, тупо, скачайте то, вставьте код, куда вставлять, зачем, почему.. Зачем объяснять....

Еще и с гавносоветами типа поставить WP.... Доказал свое гавнокодерство

Ответить

 

Дмитрий Елисеев

> Поймите, гавнокодеры, вы обязаны писать для новичков
> Они обязаны быть для новичков

Ну так и ищите "туториалы для новичков с объяснениями", а не готовые "мастер-классы с процессом без объяснения". Проблема-то в чём?

Ответить

 

HiStO rIaN

Какие мастер классы? Серьезно? Это просто буквы без смысла! Ну так найдите таких, если подобные вам засоряют инет хламом, те кто знает хоть что то в этом, никогда не объясняют, я в этом виноват? Это ваши проблемы!

И опять, вместо того, чтобы поменять уровень подачи материала, идут одни отмазки.

В этих статьях ничего нет, ни для кого. Статья о том, как тупо копипастить и забивать на учебу. А после этого ходите и жалуетесь, а что вам постоянно приходится гавнокод разгребать. А как люди будут учиться, если такие "учителя" не могут и не умеют учить. Это ваша вина, а не моя.

Ответить

 

Дмитрий Елисеев

Берём готовый код регистрации из app-advanced и копируем в app-basic. Всё. Что Вам здесь ещё объяснить?

Ответить

 

HiStO rIaN

Опять бред от "профи"... И опять все сжато... Еще один факт того, что я прав. Вы не умеете подавать материал. Я могу так же сказать про то, что вы не знаете, и вряд ли понравится это вам.

Откуда берем, что берем, куда кладем? Еще раз повторю, это отдельный язык, он не имеет ничего общего с php и ооп. Он написан на них, но это не эти языки. Чтобы этот язык понять, нужен учитель, который нормально обьясняет. Но таких нету....

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

Что рукожопы создатели, что подобные "учителя" нихера не могут объяснить как сделать эту гребаную регистрацию, которая делается на нормальном php ооп за 10 минут. Тут еб*шься уже 3 месяца... На вопрос, зачем мне это? Нужно. Я не виноват, что дибилы работошлактели берут готовое убожество с мусором кода

Ответить

 

Дмитрий Елисеев

> Чтобы этот язык понять, нужен учитель, который нормально обьясняет.

Возьмите себе любого учителя и задайте ему именно свои вопросы. Он всё объяснит на пальцах именно под ваш уровень и ваш запрос.

> не могут объяснить как сделать эту гребаную регистрацию

Она уже четыре года как сделана в yii2-app-advanced из коробки. Не знаю, с кем Вы там совокупляетесь уже три месяца.

Ответить

 

HiStO rIaN

Она нигде не сделана. Смиритесь уже. То что вы в этом говне поняли, разобрались, не значит, что это должны делать все. Твоя обязаность объяснять и учить, смирись, или свали от сюда

Ответить

 

Дмитрий Елисеев

> Она нигде не сделана.

Сочувствую вашему невидению этого репозитория.

Ответить

 

HiStO rIaN

Как все запущено... еще и репозитории смотреть... Сколько дибилушек на этой планете, которые привыкли говно жрать.....

Ответить

 

Дмитрий Елисеев

О, класс. Вместо того, чтобы разок посмотреть на официальный репозиторий от фреймворка с регистрацией и авторизацией вы три месяца совокупляетесь сами с собой...

Отлично! Продожайте изучение в том же духе :)

Ответить

 

HiStO rIaN

Вместо того, чтобы тявкать, научитесь учить. Клоун! Еще раз повторяю, пользоваться хламом не собираюсь. Тем более тем, где нихера не объясняют. Но таких ущербным это не понять. Нужно истреблять подобных, вы не нужны в этом мире убогие

Ответить

 

Дмитрий Елисеев

Вот и не пользуйтесь. Как и сказал, ставьте Wordpress и не парьтесь.

Ответить

 

HiStO rIaN

советы от бога... Только что сказал, что не пользуюсь дерьмом всяким, а он советует дерьмо.............. Еще и дырявое как твой мозг...

Доказываешь мою правоту во всем

Ответить

 

HiStO rIaN

Да и как учитель вообще может советовать что либо копировать? Чтобы что то копировать, нужно это понять. Чтобы понять, нужно внятное объяснение. Это кстати еще один факт за меня

Ответить

 

Дмитрий Елисеев

Это статья как раз о том, как скопировать регистрацию из app-advanced в app-basic. Если какие-то вещи непонятны, то ищите отдельное их описание в других местах.

Ответить

 

HiStO rIaN

Даа, тяжко наверное подобным жить.... Мрази, которые нихера не могут, кроме как унижать людей и гнаться за бесполезными и не нужными бумажками, возвышая при этом дерьмогосударство не нужное.... Быстрее бы подобные исчезли....

Копировать.... дерьмоучителей слишком много стало....

Ответить

 

Дмитрий Елисеев

Вам виднее.

Ответить

 

HiStO rIaN

это факт

Ответить

 

Васька

И, как мы уже говорили, изменим адрес до представления письма на полный:

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.

Вот где и что??? Ну как так можно писать.....Хоть бы скрин сделал, или файл написал.....

Ответить

 

Дмитрий Елисеев

Прямо в коде перед этим предложением указано.

Ответить

 

Dark Strannik

Хм... Даже зарегистрировался, чтобы написать))
Работает все прекрасно. Вот только что тестировал регистрацию, подтверждение и восстановление пароля. Были некоторые проблемы с путями, то есть после копирования файлов из advanced шаблона, были проблемы с настройками, но если подумать, воспользоваться гуглом, то всё поправимо. Я хоть и нубяра полный, структуру немного поменял по-своему, но у меня почему-то получается.))) Если что-то непонятно, то можно же спросить, а не кидаться на автора, который готовый код выложил на блюдечке. Хочешь тупо копируй, а хочешь пытайся вникнуть. -_- Вот как-то так)

Ответить

 

Dark Strannik

Спасибо за полезные статьи)

Ответить

 

Павел – vk.com

Разбираюсь поэтапно, весь код из статьи целиком не переносил. Добавил таблицу с юзерами в БД, изменил метод findByUsername($username).

Получилось, что

static::findOne(['username' => $username])

из метода поиска юзера по имени возвращает объект User с незаполненными свойствами, значения из БД хранятся в _attributes[]. Соответственно, валидация пароля не проходит.

Дошел до конструктора без аргументов

public static function instantiate($row)
{
    return new static();
}

Туда ли я копаю? :-)

Ответить

 

slo_nik

Добрый вечер.
Недавно вернулся к тестовому сайту, который создавал по Вашим статьям.
Заметил такую странность, которую не могу понять.
В модели Users для username и email делаем проверку на уникальность.

['username', 'unique', 'targetClass' => self::class],
['email', 'unique', 'targetClass' => self::class]


Вот в таком варианте при редактировании ошибка валидации "Имя занято...", "Email зарегистрирован..."
Если вместо self::class написать self::className(), то валидация работает нормально, при редактировании не выдаёт ошибки.
В чём тогда разница? className() phpstorm подчёркивает как deprecated, в документации рекомендуют запись ::class, но при этом проблемы с валидацией.

Ответить

 

Дмитрий Елисеев

Вместо self::class пишите static::class. Это будет учитывать наследование.

Ответить

 

User

День добрый.
Подскажите пожалуйста, может кто-то сталкивался с такой проблемой. Она связана с консолью. Когда 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.

Ответить

Оставить комментарий

Войти | Завести аккаунт | Войти через


(никто не увидит)





Можно использовать теги <p> <ul> <li> <b> <i> <a> <pre>