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
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';
}
}
}
** 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.