Completed
Push — master ( ceacae...8ed125 )
by Beñat
9s
created

User::logout()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 13
rs 9.4285
cc 2
eloc 7
nc 2
nop 0
1
<?php
2
3
/*
4
 * This file is part of the BenGorUser package.
5
 *
6
 * (c) Beñat Espiña <[email protected]>
7
 * (c) Gorka Laucirica <[email protected]>
8
 *
9
 * For the full copyright and license information, please view the LICENSE
10
 * file that was distributed with this source code.
11
 */
12
13
namespace BenGorUser\User\Domain\Model;
14
15
use BenGorUser\User\Domain\Model\Event\UserEnabled;
16
use BenGorUser\User\Domain\Model\Event\UserInvited;
17
use BenGorUser\User\Domain\Model\Event\UserLoggedIn;
18
use BenGorUser\User\Domain\Model\Event\UserLoggedOut;
19
use BenGorUser\User\Domain\Model\Event\UserRegistered;
20
use BenGorUser\User\Domain\Model\Event\UserRememberPasswordRequested;
21
use BenGorUser\User\Domain\Model\Event\UserRoleGranted;
22
use BenGorUser\User\Domain\Model\Event\UserRoleRevoked;
23
use BenGorUser\User\Domain\Model\Exception\UserInactiveException;
24
use BenGorUser\User\Domain\Model\Exception\UserInvitationAlreadyAcceptedException;
25
use BenGorUser\User\Domain\Model\Exception\UserPasswordInvalidException;
26
use BenGorUser\User\Domain\Model\Exception\UserRoleAlreadyGrantedException;
27
use BenGorUser\User\Domain\Model\Exception\UserRoleAlreadyRevokedException;
28
use BenGorUser\User\Domain\Model\Exception\UserRoleInvalidException;
29
30
/**
31
 * User domain class.
32
 *
33
 * @author Beñat Espiña <[email protected]>
34
 * @author Gorka Laucirica <[email protected]>
35
 */
36
class User extends UserAggregateRoot
37
{
38
    /**
39
     * The id.
40
     *
41
     * @var UserId
42
     */
43
    protected $id;
44
45
    /**
46
     * The confirmation token.
47
     *
48
     * @var UserToken
49
     */
50
    protected $confirmationToken;
51
52
    /**
53
     * Created on.
54
     *
55
     * @var \DateTimeInterface
56
     */
57
    protected $createdOn;
58
59
    /**
60
     * The email.
61
     *
62
     * @var UserEmail
63
     */
64
    protected $email;
65
66
    /**
67
     * The invitation token.
68
     *
69
     * @var UserToken
70
     */
71
    protected $invitationToken;
72
73
    /**
74
     * The last login.
75
     *
76
     * @var \DateTimeInterface|null
77
     */
78
    protected $lastLogin;
79
80
    /**
81
     * The password.
82
     *
83
     * @var UserPassword
84
     */
85
    protected $password;
86
87
    /**
88
     * The remember password token.
89
     *
90
     * @var UserToken
91
     */
92
    protected $rememberPasswordToken;
93
94
    /**
95
     * Array which contains roles.
96
     *
97
     * @var UserRole[]
98
     */
99
    protected $roles;
100
101
    /**
102
     * Updated on.
103
     *
104
     * @var \DateTimeInterface
105
     */
106
    protected $updatedOn;
107
108
    /**
109
     * Constructor.
110
     *
111
     * @param UserId       $anId      The id
112
     * @param UserEmail    $anEmail   The email
113
     * @param UserPassword $aPassword The encoded password
0 ignored issues
show
Documentation introduced by
Should the type for parameter $aPassword not be null|UserPassword?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
114
     * @param array        $userRoles Array which contains the roles
115
     */
116
    protected function __construct(
117
        UserId $anId,
118
        UserEmail $anEmail,
119
        UserPassword $aPassword = null,
120
        array $userRoles = []
121
    ) {
122
        $this->id = $anId;
123
        $this->email = $anEmail;
124
        $this->password = $aPassword;
125
        $this->confirmationToken = new UserToken();
126
        $this->createdOn = new \DateTimeImmutable();
127
        $this->updatedOn = new \DateTimeImmutable();
128
129
        $this->roles = [];
130
        foreach ($userRoles as $userRole) {
131
            $this->grant($userRole);
132
        }
133
    }
134
135
    /**
136
     * Sign up user.
137
     *
138
     * @param UserId       $anId      The id
139
     * @param UserEmail    $anEmail   The email
140
     * @param UserPassword $aPassword The encoded password
141
     * @param array        $userRoles Array which contains the roles
142
     *
143
     * @return static
144
     */
145 View Code Duplication
    public static function signUp(UserId $anId, UserEmail $anEmail, UserPassword $aPassword, array $userRoles)
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...
146
    {
147
        $user = new static($anId, $anEmail, $aPassword, $userRoles);
148
        $user->publish(
149
            new UserRegistered(
150
                $user->id(),
151
                $user->email(),
152
                $user->confirmationToken()
153
            )
154
        );
155
156
        return $user;
157
    }
158
159
    /**
160
     * Invites user.
161
     *
162
     * @param UserId    $anId    The id
163
     * @param UserEmail $anEmail The email
164
     *
165
     * @return static
166
     */
167 View Code Duplication
    public static function invite(UserId $anId, UserEmail $anEmail)
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...
168
    {
169
        $user = new static($anId, $anEmail);
170
        $user->invitationToken = new UserToken();
171
        $user->publish(
172
            new UserInvited(
173
                $user->id(),
174
                $user->email(),
175
                $user->invitationToken()
176
            )
177
        );
178
179
        return $user;
180
    }
181
182
    /**
183
     * Gets the id.
184
     *
185
     * @return UserId
186
     */
187
    public function id()
188
    {
189
        return $this->id;
190
    }
191
192
    /**
193
     * Updates the user password.
194
     *
195
     * @param UserPassword $aPassword The old password
196
     */
197
    public function changePassword(UserPassword $aPassword)
198
    {
199
        $this->password = $aPassword;
200
        $this->rememberPasswordToken = null;
201
    }
202
203
    /**
204
     * Gets the confirmation token.
205
     *
206
     * @return UserToken
207
     */
208
    public function confirmationToken()
209
    {
210
        return $this->confirmationToken;
211
    }
212
213
    /**
214
     * Gets the created on.
215
     *
216
     * @return \DateTimeInterface
217
     */
218
    public function createdOn()
219
    {
220
        return $this->createdOn;
221
    }
222
223
    /**
224
     * Gets the email.
225
     *
226
     * @return UserEmail
227
     */
228
    public function email()
229
    {
230
        return $this->email;
231
    }
232
233
    /**
234
     * Enables the user account.
235
     */
236
    public function enableAccount()
237
    {
238
        $this->confirmationToken = null;
239
240
        $this->publish(
241
            new UserEnabled(
242
                $this->id,
243
                $this->email
244
            )
245
        );
246
    }
247
248
    /**
249
     * Adds the given role.
250
     *
251
     * @param UserRole $aRole The user role
252
     */
253
    public function grant(UserRole $aRole)
254
    {
255
        if (false === $this->isRoleAllowed($aRole)) {
256
            throw new UserRoleInvalidException();
257
        }
258
        if (true === $this->isGranted($aRole)) {
259
            throw new UserRoleAlreadyGrantedException();
260
        }
261
        $this->roles[] = $aRole;
262
263
        $this->publish(
264
            new UserRoleGranted(
265
                $this->id,
266
                $this->email,
267
                $aRole
268
            )
269
        );
270
    }
271
272
    /**
273
     * Gets the invitation token.
274
     *
275
     * @return UserToken
276
     */
277
    public function invitationToken()
278
    {
279
        return $this->invitationToken;
280
    }
281
282
    /**
283
     * Checks if the user is enabled or not.
284
     *
285
     * @return bool
286
     */
287
    public function isEnabled()
288
    {
289
        return null === $this->confirmationToken || null === $this->confirmationToken->token();
290
    }
291
292
    /**
293
     * Checks if the user has the given role.
294
     *
295
     * @param UserRole $aRole The user role
296
     *
297
     * @return bool
298
     */
299
    public function isGranted(UserRole $aRole)
300
    {
301
        foreach ($this->roles as $role) {
302
            if ($role->equals($aRole)) {
303
                return true;
304
            }
305
        }
306
307
        return false;
308
    }
309
310
    /**
311
     * Checks if the role given appears between allowed roles.
312
     *
313
     * @param UserRole $aRole The user role
314
     *
315
     * @return bool
316
     */
317
    public function isRoleAllowed(UserRole $aRole)
318
    {
319
        return in_array($aRole->role(), static::availableRoles(), true);
320
    }
321
322
    /**
323
     * Gets the last login.
324
     *
325
     * @return \DateTimeInterface
0 ignored issues
show
Documentation introduced by
Should the return type not be \DateTimeInterface|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
326
     */
327
    public function lastLogin()
328
    {
329
        return $this->lastLogin;
330
    }
331
332
    /**
333
     * Validates user login for the given password.
334
     *
335
     * @param string              $aPlainPassword Plain password used to log in
336
     * @param UserPasswordEncoder $anEncoder      The encoder used to encode the password
337
     *
338
     * @throws UserInactiveException        when the user is not enabled
339
     * @throws UserPasswordInvalidException when the user password is invalid
340
     */
341
    public function login($aPlainPassword, UserPasswordEncoder $anEncoder)
342
    {
343
        if (false === $this->isEnabled()) {
344
            throw new UserInactiveException();
345
        }
346
        if (false === $this->password()->equals($aPlainPassword, $anEncoder)) {
347
            throw new UserPasswordInvalidException();
348
        }
349
        $this->lastLogin = new \DateTimeImmutable();
350
351
        $this->publish(
352
            new UserLoggedIn(
353
                $this->id,
354
                $this->email
355
            )
356
        );
357
    }
358
359
    /**
360
     * Updated the user state after logout.
361
     *
362
     * @throws UserInactiveException when the user is not enabled
363
     */
364
    public function logout()
365
    {
366
        if (false === $this->isEnabled()) {
367
            throw new UserInactiveException();
368
        }
369
370
        $this->publish(
371
            new UserLoggedOut(
372
                $this->id,
373
                $this->email
374
            )
375
        );
376
    }
377
378
    /**
379
     * Gets the password.
380
     *
381
     * @return UserPassword
382
     */
383
    public function password()
384
    {
385
        return $this->password;
386
    }
387
388
    /**
389
     * Updates the invitation token.
390
     */
391
    public function regenerateInvitationToken()
392
    {
393
        if (null === $this->invitationToken) {
394
            throw new UserInvitationAlreadyAcceptedException();
395
        }
396
        $this->invitationToken = new UserToken();
397
    }
398
399
    /**
400
     * Gets the remember password token.
401
     *
402
     * @return UserToken
403
     */
404
    public function rememberPasswordToken()
405
    {
406
        return $this->rememberPasswordToken;
407
    }
408
409
    /**
410
     * Remembers the password.
411
     */
412
    public function rememberPassword()
413
    {
414
        $this->rememberPasswordToken = new UserToken();
415
416
        $this->publish(
417
            new UserRememberPasswordRequested(
418
                $this->id,
419
                $this->email,
420
                $this->rememberPasswordToken
421
            )
422
        );
423
    }
424
425
    /**
426
     * Removes the given role.
427
     *
428
     * @param UserRole $aRole The user role
429
     */
430
    public function revoke(UserRole $aRole)
431
    {
432
        if (false === $this->isRoleAllowed($aRole)) {
433
            throw new UserRoleInvalidException();
434
        }
435
        foreach ($this->roles as $key => $role) {
436
            if ($role->equals($aRole)) {
437
                unset($this->roles[$key]);
438
                break;
439
            }
440
            throw new UserRoleAlreadyRevokedException();
441
        }
442
        $this->publish(
443
            new UserRoleRevoked(
444
                $this->id,
445
                $this->email,
446
                $aRole
447
            )
448
        );
449
    }
450
451
    /**
452
     * Gets the roles.
453
     *
454
     * @return UserRole[]
455
     */
456
    public function roles()
457
    {
458
        return $this->roles;
459
    }
460
461
    /**
462
     * Gets the updated on.
463
     *
464
     * @return \DateTimeInterface
465
     */
466
    public function updatedOn()
467
    {
468
        return $this->updatedOn;
469
    }
470
471
    /**
472
     * Gets the available roles in scalar type.
473
     *
474
     * This method is an extension point that it allows
475
     * to add more roles easily in the domain.
476
     *
477
     * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string[].

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
478
     */
479
    public static function availableRoles()
480
    {
481
        return ['ROLE_USER', 'ROLE_ADMIN'];
482
    }
483
}
484