phpyii2yii2-advanced-appyii2-user

Yii2 - Wrong User model class being used


My app is built on the 'advanced' Yii2 application template and my User class is built on the Yii2-user module.

The app seems to use the custom User model class dektrium\user\Module to handle registration as the custom fields I've added are being stored in the database on signup, using the register() method of the vendor User class to do the database persistence operations.

When I access the account settings page, using the action /user/settings/account the model class being used to display the User data is actually common/model/User, as shown by using get_class() and I can't use any of the new methods defined in the custom model class.

Main config file.

return [
   'components' => [
       'db' => [
           'class' => 'yii\db\Connection',
           'dsn' => 'mysql:host=localhost;dbname=DBNAME',
           'username' => 'DBUSER',
           'password' => 'DBPASS',
           'charset' => 'utf8',
       ],
       'mailer' => [
           'class' => 'yii\swiftmailer\Mailer',
           'viewPath' => '@common/mail',
           // send all mails to a file by default. You have to set
           // 'useFileTransport' to false and configure a transport
           // for the mailer to send real emails.
           'useFileTransport' => false,
       ],
   ],
   'modules' => [
       'user' => [
           'class' => 'dektrium\user\Module',
       ],
       'rbac' => [
           'class' => 'dektrium\rbac\Module'
       ],
   ],
];

And the SettingsForm class which has a User as a property.

<?php

/*
 * This file is part of the Dektrium project.
 *
 * (c) Dektrium project <http://github.com/dektrium/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace dektrium\user\models;

use dektrium\user\helpers\Password;
use dektrium\user\Mailer;
use dektrium\user\Module;
use yii\base\Model;
use yii\base\NotSupportedException;

/**
 * SettingsForm gets user's username, email and password and changes them.
 *
 * @property User $user
 *
 * @author Dmitry Erofeev <dmeroff@gmail.com>
 */
class SettingsForm extends Model
{
    /** @var string */
    public $email;

    /** @var string */
   // public $username;

    /** @var string */
    public $new_password;

    /** @var string */
    public $current_password;

    /** @var Module */
    protected $module;

    /** @var Mailer */
    protected $mailer;

    /** @var User */
    private $_user;

    /** @return User */
    public function getUser()
    {
        if ($this->_user == null) {
            $this->_user = \Yii::$app->user->identity;
        }
        return $this->_user;
    }

    /** @inheritdoc */
    public function __construct(Mailer $mailer, $config = [])
    {
        $this->mailer = $mailer;
        $this->module = \Yii::$app->getModule('user');
        $this->setAttributes([
            //'username' => $this->user->username,
            'email'    => $this->user->unconfirmed_email ?: $this->user->email
        ], false);
        parent::__construct($config);
    }

    /** @inheritdoc */
    public function rules()
    {
        return [
          //  'usernameRequired' => ['username', 'required'],
          //  'usernameTrim' => ['username', 'filter', 'filter' => 'trim'],
          //  'usernameLenth' => ['username', 'string', 'min' => 3, 'max' => 20],
          //  'usernamePattern' => ['username', 'match', 'pattern' => '/^[-a-zA-Z0-9_\.@]+$/'],
            'emailRequired' => ['email', 'required'],
            'emailTrim' => ['email', 'filter', 'filter' => 'trim'],
            'emailPattern' => ['email', 'email'],
           // 'emailUsernameUnique' => [['email', 'username'], 'unique', 'when' => function ($model, $attribute) {
           //     return $this->user->$attribute != $model->$attribute;
           // }, 'targetClass' => $this->module->modelMap['User']],
            'newPasswordLength' => ['new_password', 'string', 'min' => 6],
            'currentPasswordRequired' => ['current_password', 'required'],
            'currentPasswordValidate' => ['current_password', function ($attr) {
                if (!Password::validate($this->$attr, $this->user->password_hash)) {
                    $this->addError($attr, \Yii::t('user', 'Current password is not valid'));
                }
            }]
        ];
    }

    /** @inheritdoc */
    public function attributeLabels()
    {
        return [
            'email'            => \Yii::t('user', 'Email'),
            'username'         => \Yii::t('user', 'Username'),
            'new_password'     => \Yii::t('user', 'New password'),
            'current_password' => \Yii::t('user', 'Current password')
        ];
    }

    /** @inheritdoc */
    public function formName()
    {
        return 'settings-form';
    }

    /**
     * Saves new account settings.
     *
     * @return bool
     */
    public function save()
    {
        if ($this->validate()) {
          //  $this->user->scenario = 'settings';
         //   $this->user->username = $this->username;
            $this->user->password = $this->new_password;
            if ($this->email == $this->user->email && $this->user->unconfirmed_email != null) {
                $this->user->unconfirmed_email = null;
            } else if ($this->email != $this->user->email) {
                switch ($this->module->emailChangeStrategy) {
                    case Module::STRATEGY_INSECURE:
                        $this->insecureEmailChange(); break;
                    case Module::STRATEGY_DEFAULT:
                        $this->defaultEmailChange(); break;
                    case Module::STRATEGY_SECURE:
                        $this->secureEmailChange(); break;
                    default:
                        throw new \OutOfBoundsException('Invalid email changing strategy');
                }
            }
            return $this->user->save();
        }

        return false;
    }

    /**
     * Changes user's email address to given without any confirmation.
     */
    protected function insecureEmailChange()
    {
        $this->user->email = $this->email;
        \Yii::$app->session->setFlash('success', \Yii::t('user', 'Your email address has been changed'));
    }

    /**
     * Sends a confirmation message to user's email address with link to confirm changing of email.
     */
    protected function defaultEmailChange()
    {
        $this->user->unconfirmed_email = $this->email;
        /** @var Token $token */
        $token = \Yii::createObject([
            'class'   => Token::className(),
            'user_id' => $this->user->id,
            'type'    => Token::TYPE_CONFIRM_NEW_EMAIL
        ]);
        $token->save(false);
        $this->mailer->sendReconfirmationMessage($this->user, $token);
        \Yii::$app->session->setFlash('info', \Yii::t('user', 'A confirmation message has been sent to your new email address'));
    }

    /**
     * Sends a confirmation message to both old and new email addresses with link to confirm changing of email.
     * @throws \yii\base\InvalidConfigException
     */
    protected function secureEmailChange()
    {
        $this->defaultEmailChange();
        /** @var Token $token */
        $token = \Yii::createObject([
            'class'   => Token::className(),
            'user_id' => $this->user->id,
            'type'    => Token::TYPE_CONFIRM_OLD_EMAIL
        ]);
        $token->save(false);
        $this->mailer->sendReconfirmationMessage($this->user, $token);

        // unset flags if they exist
        $this->user->flags &= ~User::NEW_EMAIL_CONFIRMED;
        $this->user->flags &= ~User::OLD_EMAIL_CONFIRMED;
        $this->user->save(false);

        \Yii::$app->session->setFlash('info', \Yii::t('user', 'We have sent confirmation links to both old and new email addresses. You must click both links to complete your request'));
    }
}

The module property is showing the type to be the custom User but the _user is from the /common/models/User.

Have I missed something to say that any User objects should be of the custom class, so I can access the new properties/methods?


Solution

  • At the moment I have changed the common/models/User class to simply extend the User in dektrium/user/models/User with no actual code in the common User class.

    class User extends \dektrium\user\models\User {}
    

    It has solved my problem for now, but it feels like a really bad solution. Any further input would be gratefully received...

    Edit:

    I'd forgotten about this Q&A...

    in my common/config/main.php where the user module is declared, in the modelMap I'm able to specify to use my own custom User model, something like the following.

    'modules' => [
        'user' => [
            'class' => 'dektrium\user\Module',
    
            'modelMap' => [
                'User'                => 'common\models\User',
            ],
    

    Where there is an entry for each model used by the dektrium user module.