User::beforeSave()   B
last analyzed

Complexity

Conditions 6
Paths 11

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 23
rs 8.9297
c 0
b 0
f 0
ccs 7
cts 7
cp 1
cc 6
nc 11
nop 1
crap 6
1
<?php
2
3
namespace app\models\entity;
4
5
use Yii;
6
use yii\base\NotSupportedException;
7
use yii\db\ActiveRecord;
8
use yii\web\IdentityInterface;
9
use yii\filters\RateLimitInterface;
10
use yii\behaviors\TimestampBehavior;
11
12
/**
13
 * This is the model class for table "{{%user}}"
14
 *
15
 * @property integer $id
16
 * @property string $email
17
 * @property string $password
18
 * @property string $password_reset_token
19
 * @property string $email_confirm_token
20
 * @property string $auth_key
21
 * @property string $date_confirm
22
 * @property string $date_create
23
 * @property string $date_update
24
 * @property string $date_login
25
 * @property integer $ip
26
 * @property string $role_name
27
 * @property integer $status
28
 *
29
 * @property AuthItem $role
30
 * @property UserProfile $profile
31
 * @property UserProvider[] $providers
32
 */
33
class User extends \yii\db\ActiveRecord implements RateLimitInterface, IdentityInterface
34
{
35
    const STATUS_DELETED = 0;
36
    const STATUS_ACTIVE  = 1;
37
    const STATUS_BLOCKED = 2;
38
39
    const ROLE_SUPERUSER = 'SuperUser';
40
41
    /**
42
     * @var string
43
     */
44
    public $passwordNew;
45
46
    /**
47
     * @inheritdoc
48
     */
49 111
    public static function tableName()
50
    {
51 111
        return '{{%user}}';
52
    }
53
54
    /**
55
     * @inheritdoc
56
     */
57 43
    public function attributeLabels()
58
    {
59
        return [
60 43
            'id' => Yii::t('app', 'ID'),
61 43
            'email' => Yii::t('app', 'Email'),
62 43
            'password' => Yii::t('app', 'Password'),
63 43
            'date_create' => Yii::t('app', 'Date create'),
64 43
            'date_update' => Yii::t('app', 'Date update'),
65 43
            'date_login' => Yii::t('app', 'Last login'),
66 43
            'ip' => Yii::t('app', 'IP'),
67 43
            'role_name' => Yii::t('app', 'Role'),
68 43
            'status' => Yii::t('app', 'Status'),
69 43
70
            'passwordNew' => Yii::t('app', 'New password'),
71 43
        ];
72
    }
73
74
    /**
75
     * @inheritdoc
76
     */
77
    public function behaviors()
78 74
    {
79
        return [
80
            [
81
                'class' => TimestampBehavior::class,
82 74
                'createdAtAttribute' => 'date_create',
83 74
                'updatedAtAttribute' => 'date_update',
84 74
                'value' => new \yii\db\Expression('NOW()'),
85 74
            ],
86
        ];
87
    }
88
89
    /**
90
     * @inheritdoc
91
     */
92
    public function events()
93
    {
94
        return [
95
            ActiveRecord::EVENT_BEFORE_DELETE => 'beforeDelete',
96
        ];
97
    }
98
99
    public function transactions()
100
    {
101 9
        return [
102
            'create' => self::OP_ALL,
103
            'update' => self::OP_ALL,
104 9
            'delete' => self::OP_ALL,
105 9
        ];
106 9
    }
107
108
    public function getRateLimit($request, $action)
109
    {
110
        return [Yii::$app->params['user.rateLimitMax'], Yii::$app->params['user.rateLimitTime']];
111
    }
112
113 9
    public function loadAllowance($request, $action)
114
    {
115 9
        return [$this->allowance, $this->allowance_updated_at];
0 ignored issues
show
Documentation introduced by
The property allowance does not exist on object<app\models\entity\User>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Documentation introduced by
The property allowance_updated_at does not exist on object<app\models\entity\User>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
116
    }
117
118
    public function saveAllowance($request, $action, $allowance, $timestamp)
119
    {
120
        $this->allowance = $allowance;
0 ignored issues
show
Documentation introduced by
The property allowance does not exist on object<app\models\entity\User>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
121 3
        $this->allowance_updated_at = $timestamp;
0 ignored issues
show
Documentation introduced by
The property allowance_updated_at does not exist on object<app\models\entity\User>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
122
        $this->save();
123 3
    }
124
125
    /**
126
     * @return \yii\db\ActiveQuery
127
     */
128
    public function getProfile()
129 10
    {
130
        return $this->hasOne(UserProfile::class, ['user_id' => 'id']);
131 10
    }
132
133
    /**
134
     * @param array $attributes
135
     */
136
    public function setProfile($attributes = [])
137
    {
138
        return $this->populateRelation('profile', new UserProfile($attributes));
139
    }
140
141
    /**
142
     * @return \yii\db\ActiveQuery
143
     */
144
    public function getProviders()
145 10
    {
146
        return $this->hasMany(UserProvider::class, ['user_id' => 'id']);
147 10
    }
148
149
    /**
150
     * @param array $attributes
151
     */
152
    public function setProviders($attributes = [])
153
    {
154 80
        return $this->populateRelation('providers', [new UserProvider($attributes)]);
155
    }
156 80
157
    /**
158
     * @return \yii\db\ActiveQuery
159
     */
160
    public function getRole()
161
    {
162 9
        return $this->hasOne(AuthItem::class, ['name' => 'role_name']);
163
    }
164 9
165 9
    /**
166 3
     * @inheritdoc
167 3
     * @return \app\models\query\UserQuery The active query used by this AR class
168 3
     */
169
    public static function find()
170
    {
171 3
        return new \app\models\query\UserQuery(get_called_class());
172
    }
173
174
    /**
175
     * @inheritdoc
176 9
     */
177 1
    public function beforeSave($insert)
178
    {
179
        if (parent::beforeSave($insert)) {
180 9
            if ($insert) {
181
                $this->generateAuthKey();
182
                if (!Yii::$app instanceof \yii\console\Application) {
183
                    $this->ip = ip2long(Yii::$app->request->getUserIP());
184
                }
185
186
                if ($this->profile === null) {
187
                    $this->setProfile();
188
                }
189
            }
190
191 9
            if (!empty($this->passwordNew)) {
192
                $this->setPassword($this->passwordNew);
193 9
            }
194
195 9
            return true;
196 7
        }
197
198
        return false;
199 9
    }
200
201
    /**
202
     * @inheritdoc
203
     * @property \entity\UserProfile $profile
204
     * @property \entity\UserProvider $providers
205
     */
206 9
    public function afterSave($insert, $changedAttributes)
207
    {
208
        parent::afterSave($insert, $changedAttributes);
209
210
        if ($this->profile !== null) {
211
            $this->link('profile', $this->profile);
212
        }
213
214
        if ($this->providers !== null && count($this->providers)) {
215
            foreach ($this->providers as $provider) {
216 9
                if ($provider) {
217 9
                    $this->link('providers', $provider);
218 9
                }
219
            }
220
        }
221
    }
222
223
    /**
224
     * @inheritdoc
225
     * @param boolean $runValidation
226 6
     * @param array $attributeNames
227
     * @return boolean
228
     */
229 6
    public function save($runValidation = true, $attributeNames = null)
230 6
    {
231 6
        return $this->getDb()->transaction(function () use ($runValidation, $attributeNames) {
232
            return parent::save($runValidation, $attributeNames);
233
        });
234
    }
235
236
    /**
237
     * Get all statuses
238
     *
239
     * @param string[]
240 6
     */
241
    public static function getStatuses(): array
242 6
    {
243 6
        return [
244
            self::STATUS_DELETED => Yii::t('app', 'Deleted'),
245
            self::STATUS_BLOCKED => Yii::t('app', 'Locked'),
246
            self::STATUS_ACTIVE  => Yii::t('app', 'Active'),
247
        ];
248
    }
249
250
    /**
251
     * Get status name
252
     *
253
     * @return string
254
     */
255
    public function getStatusName(): string
256
    {
257
        $statuses = self::getStatuses();
258
        return isset($statuses[$this->status]) ? $statuses[$this->status] : '';
259
    }
260
261 6
    /**
262
     * Whether the user is deleted
263 6
     *
264
     * @param bool
265
     */
266
    public function isDeleted(): bool
267
    {
268
        return $this->status == self::STATUS_DELETED;
269
    }
270
271 30
    /**
272
     * Whether the user is blocked
273 30
     *
274
     * @param bool
275
     */
276
    public function isBlocked(): bool
277
    {
278
        return $this->status == self::STATUS_BLOCKED;
279
    }
280
281 10
    /**
282
     * Whether the user is active
283 10
     *
284
     * @param bool
285
     */
286
    public function isActive(): bool
287
    {
288
        return $this->status == self::STATUS_ACTIVE;
289
    }
290
291 15
    /**
292
     * Whether the user is confirmed
293 15
     *
294
     * @param bool
295
     */
296
    public function isConfirmed(): bool
297
    {
298
        return strtotime($this->date_confirm) > 0;
299 2
    }
300
301 2
    /**
302 2
     * Whether the user is SuperUser
303 2
     *
304
     * @return bool
305
     */
306
    public function isSuperUser(): bool
307
    {
308
        return $this->role_name === self::ROLE_SUPERUSER;
309
    }
310 8
311
    /**
312 8
     * Set confirmed
313 4
     */
314 4
    public function setConfirmed(): void
315 4
    {
316
        $this->email_confirm_token = null;
317
        $this->date_confirm = new \yii\db\Expression('NOW()');
318
    }
319
320
    /**
321
     * Get status description
322
     *
323 28
     * @return string
324
     */
325 28
    public function getStatusDescription(): string
326
    {
327
        if ($this->status == self::STATUS_BLOCKED) {
328
            return Yii::t('app', 'Your account has been suspended');
329
        } elseif ($this->status == self::STATUS_DELETED) {
330
            return Yii::t('app', 'Your account has been deleted');
331
        }
332
        return Yii::t('app', 'Your account is activated');
333
    }
334
335
    /**
336
     * @inheritdoc
337
     */
338
    public function getId()
339
    {
340
        return $this->id;
341
    }
342
343
    /**
344
     * @inheritdoc
345
     */
346
    public function getAuthKey()
347 3
    {
348
        return $this->auth_key;
349 3
    }
350 3
351
    /**
352
     * @inheritdoc
353
     */
354
    public function validateAuthKey($authKey)
355
    {
356
        return $this->getAuthKey() === $authKey;
357
    }
358 22
359
    /**
360 22
     * Generates "remember me" authentication key
361
     */
362
    public function generateAuthKey()
363
    {
364 22
        $this->auth_key = Yii::$app->security->generateRandomString();
365
    }
366
367
    /**
368
     * Validates password
369
     *
370
     * @param string $password Password to validate
371
     * @return boolean If password provided is valid for current user
372 6
     */
373
    public function validatePassword(string $password): bool
374 6
    {
375 6
        if (empty($this->password)) {
376
            return false;
377
        }
378
379
        return Yii::$app->security->validatePassword($password, $this->password);
380
    }
381
382 1
    /**
383
     * Set a new password
384 1
     *
385 1
     * @param string $password
386
     */
387
    public function setPassword(string $password): void
388
    {
389
        $this->password = Yii::$app->security->generatePasswordHash($password);
390 2
    }
391
392 2
    /**
393 2
     * Set new password reset token
394
     *
395
     * @param string $token
396
     */
397
    public function setPasswordResetToken(string $token): void
398
    {
399
        $this->password_reset_token = $token;
400 4
    }
401
402 4
    /**
403 4
     * Removes password reset token
404 4
     */
405
    public function removePasswordResetToken(): void
406
    {
407
        $this->password_reset_token = null;
408
    }
409 20
410
    /**
411 20
     * Set new confirm email token
412
     *
413
     * @param string $token
414
     */
415
    public function setEmailConfirmToken(string $token): void
416
    {
417
        $this->email_confirm_token = $token;
418
        $this->date_confirm = null;
419
    }
420
421
    public function getName()
422
    {
423
        return $this->profile->full_name;
424
    }
425 12
426
    /**
427 12
     * @inheritdoc
428 12
     */
429 12
    public static function findIdentity($id)
430
    {
431 12
        return static::findOne([$id]);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return static::findOne(array($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.

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...
432
    }
433
434
    /**
435
     * @inheritdoc
436
     */
437
    public static function findIdentityByAccessToken($token, $type = null)
438
    {
439
        throw new NotSupportedException('findIdentityByAccessToken is not implemented.');
440
    }
441
442
    /**
443
     * Update date login
444
     */
445
    public function updateDateLogin(): void
446
    {
447
        $this->updateAttributes([
448
            'date_login' => new \yii\db\Expression('NOW()'),
449
            'ip' => ip2long(Yii::$app->request->getUserIP())
450
        ]);
451
    }
452
453
    /**
454
     * @return bool
455
     */
456
    public function beforeDelete()
457
    {
458
        Yii::$app->authManager->revokeAll($this->id);
459
460
        if ($this->profile !== null) {
461
            $this->profile->delete();
462
        }
463
464
        return true;
465
    }
466
}
467