Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
79.63% covered (warning)
79.63%
43 / 54
93.33% covered (success)
93.33%
14 / 15
92.86% covered (success)
92.86%
13 / 14
92.31% covered (success)
92.31%
12 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
Identity
79.63% covered (warning)
79.63%
43 / 54
93.33% covered (success)
93.33%
14 / 15
92.86% covered (success)
92.86%
13 / 14
92.31% covered (success)
92.31%
12 / 13
14.07
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 create
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getId
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getEmail
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUsername
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPasswordHash
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRoles
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isComplete
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getValidationToken
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 pullEvents
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 complete
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 changeEmail
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 generateValidationToken
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace App\Auth\Domain\Model;
4
5use App\Auth\Domain\Event\EmailChangedEvent;
6use App\Auth\Domain\Event\IdentityCompletedEvent;
7use App\Auth\Domain\Event\IdentityCreatedEvent;
8use App\Auth\Domain\Exception\AuthErrorCode;
9use App\Shared\Domain\Event\DomainEvent;
10use App\Shared\Domain\Exception\ValidationException;
11use App\Shared\Domain\Model\EntityId;
12use App\Shared\Domain\Model\ProducesDomainEvents;
13use App\Shared\Domain\Validation\Validator;
14
15final class Identity implements ProducesDomainEvents
16{
17
18    /**
19     * @var array<DomainEvent>
20     */
21    private array $events = [];
22
23    /**
24     * @param EntityId $id
25     * @param string $email
26     * @param string $username
27     * @param string $passwordHash
28     * @param list<string> $roles
29     * @param boolean $isComplete
30     * @param string $validationToken
31     * @throws ValidationException
32    */
33    public function __construct(
34        private readonly EntityId $id,
35        public readonly string    $email,
36        private readonly string   $username,
37        private readonly string   $passwordHash,
38        private readonly array    $roles = ['ROLE_USER'],
39        private readonly bool $isComplete = false,
40        private readonly string $validationToken = ''
41    ) {
42        $validator = new Validator();
43        $validator
44            ->requireNotEmpty('id', $this->id)
45            ->requireNotEmpty('email', $this->email)
46            ->requireValidEmail('email', $this->email)
47            ->require('username', fn () => (bool)preg_match('/^[a-zA-Z0-9_-]+$/', $this->username), AuthErrorCode::INVALID_USERNAME)
48            ->requireNotEmpty('passwordHash', $this->passwordHash)
49            ->requireNotEmpty('roles', $this->roles)
50            ->validate();
51    }
52
53    /**
54     * @param EntityId $id
55     * @param string $email
56     * @param string $username
57     * @param string $passwordHash
58     * @param list<string> $roles
59     * @return self
60     */
61    public static function create(
62        EntityId $id,
63        string    $email,
64        string   $username,
65        string   $passwordHash,
66        array    $roles = ['ROLE_USER']
67    ): self {
68        $newUser = new self(
69            id: $id,
70            email: $email,
71            username: $username,
72            passwordHash: $passwordHash,
73            roles: $roles,
74            isComplete: false,
75        );
76        $newUser->events = [new IdentityCreatedEvent($newUser)];
77        return $newUser;
78    }
79
80    public function getId(): EntityId
81    {
82        return $this->id;
83    }
84
85    public function getEmail(): string
86    {
87        return $this->email;
88    }
89    public function getUsername(): string
90    {
91        return $this->username;
92    }
93    public function getPasswordHash(): string
94    {
95        return $this->passwordHash;
96    }
97
98    /**
99     * @return list<string>
100     */
101    public function getRoles(): array
102    {
103        return $this->roles;
104    }
105
106    public function isComplete(): bool
107    {
108        return $this->isComplete;
109    }
110
111    public function getValidationToken(): string
112    {
113        return $this->validationToken;
114    }
115
116    /**
117     * @return array<DomainEvent>
118     */
119    public function pullEvents(): array
120    {
121        $events = $this->events;
122        $this->events = [];
123        return $events;
124    }
125
126    public function complete(): self
127    {
128        if ($this->isComplete()) {
129            return $this;
130        }
131
132        $new = new self(
133            id: $this->id,
134            email: $this->email,
135            username: $this->username,
136            passwordHash: $this->passwordHash,
137            roles: $this->roles,
138            isComplete: true,
139            validationToken: $this->generateValidationToken()
140        );
141        $new->events = [new IdentityCompletedEvent($new)];
142        return $new;
143    }
144
145    public function changeEmail(string $email): self
146    {
147        $new = new self(
148            id: $this->id,
149            email: $email,
150            username: $this->username,
151            passwordHash: $this->passwordHash,
152            roles: $this->roles,
153            isComplete: true,
154            validationToken: $this->generateValidationToken()
155        );
156        $new->events = [new EmailChangedEvent($new)];
157        return $new;
158    }
159
160    private function generateValidationToken(): string
161    {
162        return EntityId::generate();
163    }
164}