How to login using two different model or switch i

2019-04-09 00:31发布

问题:

I want to allow user login from two different model.

Config.php

'user' => [
        'identityClass' => 'app\models\User', //one more class here
        'enableAutoLogin' => false,
        'authTimeout' => 3600*2,
    ],

LoginForm.php

 public function rules()
{
    return [
        // username and password are both required
        [['username', 'password'], 'required'],
        // rememberMe must be a boolean value
        ['rememberMe', 'boolean'],
        // password is validated by validatePassword()
        ['password', 'validatePassword'],
    ];
}

 public function validatePassword($attribute, $params)
{
    if (!$this->hasErrors()) {
        $user = $this->getUser();

        if (!$user || !$user->validatePassword($this->password)) {
            $this->addError($attribute, Yii::t('user', 'Incorrect username or password.'));
        }
    }
}

public function login()
{
    if ($this->validate()) {
        return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600*24*30 : 0);
    } else {
        return false;
    }
}

public function parentLogin()
{
    // How to validate parent Login?
}

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

    return $this->_user;
}

User.php

class User extends \yii\db\ActiveRecord implements IdentityInterface
{
    public static function tableName()
   {
    return 'users';
   }

   public static function findIdentity($id)
  {
    return static::findOne($id);
  }

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

Controller.php

 public function actionLogin()
{
    // Working
}

public function actionParentLogin()
{
    $model = new LoginForm();

    if ($model->load(Yii::$app->request->post()) && $model->parentLogin()) {

            $parent = ParentLogin::find()->where(['p_username' => $model->p_username])->one();
        if($parent){
            \Yii::$app->session->set('p_id',$parent->p_id);
            return $this->redirect(['parent-dashboard']);
        }
        else
        {
            Yii::$app->getSession()->setFlash('error', Yii::t('site', 'Incorrect username or password.'));
        }
    }
    return $this->render('parent-login', [
            'model' => $model,
        ]);
}

I don't know how to validate parent login. I tried for hours finding workaround but not succeed.

I am stuck on Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600*24*30 : 0); because user table don't have parent login records.

My questions

1) Is it possible to have two identityClass. If yes then how?
2) Is it possible to extend ParentLogin model to User. If yes then how to validate?

References

How To extend user class?
Customising the CWebUser class
Custom userIdentity class in yii2

回答1:

Joe Miller has a good sugestion about having one user class and some boolean fields in users' table to check user's role, as an alternative to rbac. But since in your situation it is not possible, here is what I can suggest to you (this approach is half-way tested and need to be adopted).

Yes you can have two or more identityClasses, but not in the same time. You need to handle switching between identities. So first, I suggest to you edit your LoginForm model a little:

class LoginForm extends Model
{
    public $username;
    public $password;
    public $rememberMe = true;
    // we added this parameter to handle userModel class
    // that is responsible for getting correct user
    public $userModel;

    private $_user = false;

    /* all other methods stay same */

    /**
     * Finds user by [[username]]
     *
     * @return User|null
     */
    public function getUser()
    {
        if ($this->_user === false) {
            // calling findByUsername method dynamically
            $this->_user = call_user_func(
                [$this->userModel, 'findByUsername'], 
                $this->username
            );
        }

        return $this->_user;
    }
}

Now in controller:

public function actionParentLogin()
{
    $model = new LoginForm(['userModel' => ParentLogin::className()]);
    // calling model->login() here as we usually do
    if ($model->load(Yii::$app->request->post()) && $model->login()) {
            // no need to worry about checking if we found parent it's all done polymorphycally for us in LoginForm
            // here is the trick, since we loggin in via parentLogin action we set this session variable.
            Yii::$app->session->set('isParent', true);
            return $this->redirect(['parent-dashboard']);
        } else {
            Yii::$app->getSession()->setFlash('error', Yii::t('site', 'Incorrect username or password.'));
        }
    }
    return $this->render('parent-login', [
            'model' => $model,
        ]);
}

Your parentLogin model should extends User model to make all this work:

class parentLogin extends User
{
    public static function tableName()
    {
        //you parent users table name
        return 'parent_users';
    }

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

Now when you logged in, you need to handle identity switch, because in the configuration you have 'identityClass' => 'app\models\User'. We can use bootstrap property for that:

//in your config file
'bootstrap' => [
    'log',
    //component for switching identities
    'app\components\IdentitySwitcher'
],

IdentitySwitcher class:

class IdentitySwitcher extends Component implements BootstrapInterface
{
    public function bootstrap($app)
    {
        //we set this in parentLogin action
        //so if we loggin in as a parent user it will be true
        if ($app->session->get('isParent')) {
            $app->user->identityClass = 'app\models\ParentLogin';
        }
    }
}


回答2:

** Edit, this doesn't work, you can only have one identity class ** ** Ref https://github.com/yiisoft/yii2/issues/5134 ** I would suggest trying the following - not tested.

In your config, add an extra identity interface, as you suggest;

'user' => [
        'identityClass' => 'app\models\User',
        'enableAutoLogin' => false,
        'authTimeout' => 3600*2,
    ],
'parent' => [
        'identityClass' => 'app\models\Parent',
        'enableAutoLogin' => false,
        'authTimeout' => 3600*2,
    ],

Your Parent model can then either extend the User model, which will give the same validation methods as the original User model, or implement IdentityInterface from scratch. From your column names in your parent table, I'd suggest the second method as the columns are different to the User table.

You would then need two loginForms: loginForm and parentLoginForm, as the validation is different in each case.

Then, in your controller, you can call the appropriate login form as required.