UserModel::create()   A
last analyzed

Complexity

Conditions 3
Paths 10

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 28
rs 9.472
c 0
b 0
f 0
cc 3
nc 10
nop 0
1
<?php
2
3
namespace yii2mod\user\models;
4
5
use Yii;
6
use yii\base\NotSupportedException;
7
use yii\behaviors\TimestampBehavior;
8
use yii\db\ActiveRecord;
9
use yii\helpers\ArrayHelper;
10
use yii\web\IdentityInterface;
11
use yii2mod\user\models\enums\UserStatus;
12
use yii2mod\user\traits\EventTrait;
13
14
/**
15
 * Class UserModel
16
 *
17
 * @property int $id
18
 * @property string $username
19
 * @property string $password_hash
20
 * @property string $password_reset_token
21
 * @property string $email
22
 * @property string $auth_key
23
 * @property int $status
24
 * @property int $created_at
25
 * @property int $updated_at
26
 * @property int $last_login
27
 * @property string $password write-only password
28
 */
29
class UserModel extends ActiveRecord implements IdentityInterface
30
{
31
    use EventTrait;
32
33
    /**
34
     * Event is triggered before creating a user.
35
     * Triggered with \yii2mod\user\events\CreateUserEvent.
36
     */
37
    const BEFORE_CREATE = 'beforeCreate';
38
39
    /**
40
     * Event is triggered after creating a user.
41
     * Triggered with \yii2mod\user\events\CreateUserEvent.
42
     */
43
    const AFTER_CREATE = 'afterCreate';
44
45
    /**
46
     * @var string plain password
47
     */
48
    public $plainPassword;
49
50
    /**
51
     * @inheritdoc
52
     */
53
    public static function tableName()
54
    {
55
        return '{{%user}}';
56
    }
57
58
    /**
59
     * @inheritdoc
60
     */
61 View Code Duplication
    public function rules()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
62
    {
63
        return [
64
            [['username', 'email'], 'required'],
65
            ['email', 'unique', 'message' => Yii::t('yii2mod.user', 'This email address has already been taken.')],
66
            ['username', 'unique', 'message' => Yii::t('yii2mod.user', 'This username has already been taken.')],
67
            ['username', 'string', 'min' => 2, 'max' => 255],
68
            ['email', 'email'],
69
            ['email', 'string', 'max' => 255],
70
            ['plainPassword', 'string', 'min' => 6],
71
            ['plainPassword', 'required', 'on' => 'create'],
72
            ['status', 'default', 'value' => UserStatus::ACTIVE],
73
            ['status', 'in', 'range' => UserStatus::getConstantsByName()],
74
        ];
75
    }
76
77
    /**
78
     * @inheritdoc
79
     */
80
    public function attributeLabels()
81
    {
82
        return [
83
            'username' => Yii::t('yii2mod.user', 'Username'),
84
            'email' => Yii::t('yii2mod.user', 'Email'),
85
            'status' => Yii::t('yii2mod.user', 'Status'),
86
            'created_at' => Yii::t('yii2mod.user', 'Registration time'),
87
            'last_login' => Yii::t('yii2mod.user', 'Last login'),
88
            'plainPassword' => Yii::t('yii2mod.user', 'Password'),
89
        ];
90
    }
91
92
    /**
93
     * @inheritdoc
94
     */
95
    public function behaviors()
96
    {
97
        return [
98
            TimestampBehavior::class,
99
        ];
100
    }
101
102
    /**
103
     * Create user
104
     *
105
     * @return null|UserModel the saved model or null if saving fails
106
     *
107
     * @throws \Exception
108
     */
109
    public function create()
110
    {
111
        $transaction = $this->getDb()->beginTransaction();
112
113
        try {
114
            $event = $this->getCreateUserEvent($this);
115
            $this->trigger(self::BEFORE_CREATE, $event);
116
117
            $this->setPassword($this->plainPassword);
118
            $this->generateAuthKey();
119
120
            if (!$this->save()) {
121
                $transaction->rollBack();
122
123
                return null;
124
            }
125
126
            $this->trigger(self::AFTER_CREATE, $event);
127
128
            $transaction->commit();
129
130
            return $this;
131
        } catch (\Exception $e) {
132
            $transaction->rollBack();
133
            Yii::warning($e->getMessage());
134
            throw $e;
135
        }
136
    }
137
138
    /**
139
     * @inheritdoc
140
     */
141
    public static function findIdentity($id)
142
    {
143
        return static::findOne($id);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return static::findOne($id); (yii\db\ActiveRecordInterface|array|null) is incompatible with the return type declared by the interface yii\web\IdentityInterface::findIdentity of type yii\web\IdentityInterface|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
144
    }
145
146
    /**
147
     * @inheritdoc
148
     */
149
    public static function findIdentityByAccessToken($token, $type = null)
150
    {
151
        throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.');
152
    }
153
154
    /**
155
     * Finds user (with active status) by username
156
     *
157
     * @param  string $username
158
     *
159
     * @return static|null
160
     */
161
    public static function findByUsername($username)
162
    {
163
        return static::findOne(['username' => $username, 'status' => UserStatus::ACTIVE]);
164
    }
165
166
    /**
167
     * Finds user by email
168
     *
169
     * @param $email
170
     *
171
     * @return null|static
172
     */
173
    public static function findByEmail($email)
174
    {
175
        return static::findOne(['email' => $email]);
176
    }
177
178
    /**
179
     * Finds user by password reset token
180
     *
181
     * @param string $token password reset token
182
     *
183
     * @return static|null
184
     */
185
    public static function findByPasswordResetToken($token)
186
    {
187
        if (!static::isPasswordResetTokenValid($token)) {
188
            return null;
189
        }
190
191
        return static::findOne([
192
            'password_reset_token' => $token,
193
            'status' => UserStatus::ACTIVE,
194
        ]);
195
    }
196
197
    /**
198
     * Finds out if password reset token is valid
199
     *
200
     * @param string $token password reset token
201
     *
202
     * @return bool
203
     */
204
    public static function isPasswordResetTokenValid($token)
205
    {
206
        if (empty($token)) {
207
            return false;
208
        }
209
210
        $timestamp = (int) substr($token, strrpos($token, '_') + 1);
211
        $expire = ArrayHelper::getValue(Yii::$app->params, 'user.passwordResetTokenExpire', 3600);
212
213
        return $timestamp + $expire >= time();
214
    }
215
216
    /**
217
     * @inheritdoc
218
     */
219
    public function getId()
220
    {
221
        return $this->getPrimaryKey();
222
    }
223
224
    /**
225
     * @inheritdoc
226
     */
227
    public function getAuthKey()
228
    {
229
        return $this->auth_key;
230
    }
231
232
    /**
233
     * @inheritdoc
234
     */
235
    public function validateAuthKey($authKey)
236
    {
237
        return $this->getAuthKey() === $authKey;
238
    }
239
240
    /**
241
     * Validates password
242
     *
243
     * @param  string $password password to validate
244
     *
245
     * @return bool if password provided is valid for current user
246
     */
247
    public function validatePassword($password)
248
    {
249
        return Yii::$app->getSecurity()->validatePassword($password, $this->password_hash);
250
    }
251
252
    /**
253
     * Generates password hash from password and sets it to the model
254
     *
255
     * @param string $password
256
     */
257
    public function setPassword($password)
258
    {
259
        $this->password_hash = Yii::$app->getSecurity()->generatePasswordHash($password);
260
    }
261
262
    /**
263
     * Generates "remember me" authentication key
264
     */
265
    public function generateAuthKey()
266
    {
267
        $this->auth_key = Yii::$app->getSecurity()->generateRandomString();
268
    }
269
270
    /**
271
     * Generates new password reset token
272
     */
273
    public function generatePasswordResetToken()
274
    {
275
        $this->password_reset_token = Yii::$app->getSecurity()->generateRandomString() . '_' . time();
276
    }
277
278
    /**
279
     * Removes password reset token
280
     */
281
    public function removePasswordResetToken()
282
    {
283
        $this->password_reset_token = null;
284
    }
285
286
    /**
287
     * @param $lastLogin
288
     */
289
    public function setLastLogin($lastLogin)
290
    {
291
        $this->last_login = $lastLogin;
292
    }
293
294
    /**
295
     * Update last login
296
     */
297
    public function updateLastLogin()
298
    {
299
        $this->updateAttributes(['last_login' => time()]);
300
    }
301
302
    /**
303
     * Resets password.
304
     *
305
     * @param string $password
306
     *
307
     * @return bool
308
     */
309
    public function resetPassword($password)
310
    {
311
        $this->setPassword($password);
312
313
        return $this->save(true, ['password_hash']);
314
    }
315
}
316