• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

codeigniter4 / shield / 27704172325

17 Jun 2026 04:30PM UTC coverage: 92.987%. Remained the same
27704172325

Pull #1342

github

web-flow
Merge 5ba7c8dd5 into 4aff326ea
Pull Request #1342: chore(deps-dev): update codeigniter/phpstan-codeigniter requirement from ^1.3 to ^2.0

2970 of 3194 relevant lines covered (92.99%)

28.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.41
/src/Entities/User.php
1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * This file is part of CodeIgniter Shield.
7
 *
8
 * (c) CodeIgniter Foundation <admin@codeigniter.com>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE file that was distributed with this source code.
12
 */
13

14
namespace CodeIgniter\Shield\Entities;
15

16
use CodeIgniter\Database\Exceptions\DataException;
17
use CodeIgniter\Entity\Entity;
18
use CodeIgniter\I18n\Time;
19
use CodeIgniter\Shield\Authentication\Authenticators\Session;
20
use CodeIgniter\Shield\Authentication\Traits\HasAccessTokens;
21
use CodeIgniter\Shield\Authentication\Traits\HasHmacTokens;
22
use CodeIgniter\Shield\Authorization\Traits\Authorizable;
23
use CodeIgniter\Shield\Models\LoginModel;
24
use CodeIgniter\Shield\Models\UserIdentityModel;
25
use CodeIgniter\Shield\Traits\Activatable;
26
use CodeIgniter\Shield\Traits\Bannable;
27
use CodeIgniter\Shield\Traits\Resettable;
28

29
/**
30
 * @property string|null             $email
31
 * @property int|string|null         $id
32
 * @property list<UserIdentity>|null $identities
33
 * @property Time|null               $last_active
34
 * @property string|null             $password
35
 * @property string|null             $password_hash
36
 * @property string|null             $username
37
 */
38
class User extends Entity
39
{
40
    use Authorizable;
41
    use HasAccessTokens;
42
    use HasHmacTokens;
43
    use Resettable;
44
    use Activatable;
45
    use Bannable;
46

47
    /**
48
     * @var list<UserIdentity>|null
49
     */
50
    private ?array $identities = null;
51

52
    private ?string $email         = null;
53
    private ?string $password      = null;
54
    private ?string $password_hash = null;
55

56
    /**
57
     * @var list<string>
58
     */
59
    protected $dates = [
60
        'created_at',
61
        'updated_at',
62
        'deleted_at',
63
        'last_active',
64
    ];
65

66
    /**
67
     * @var array<string, string>
68
     */
69
    protected $casts = [
70
        'id'          => '?integer',
71
        'active'      => 'int-bool',
72
        'permissions' => 'array',
73
        'groups'      => 'array',
74
    ];
75

76
    /**
77
     * Returns the first identity of the given $type for this user.
78
     *
79
     * @param string $type See const ID_TYPE_* in Authenticator.
80
     *                     'email_2fa'|'email_activate'|'email_password'|'magic-link'|'access_token'
81
     */
82
    public function getIdentity(string $type): ?UserIdentity
83
    {
84
        $identities = $this->getIdentities($type);
270✔
85

86
        return count($identities) ? array_shift($identities) : null;
270✔
87
    }
88

89
    /**
90
     * ensures that all of the user's identities are loaded
91
     * into the instance for faster access later.
92
     */
93
    private function populateIdentities(): void
94
    {
95
        if ($this->identities === null) {
270✔
96
            /** @var UserIdentityModel $identityModel */
97
            $identityModel = model(UserIdentityModel::class);
270✔
98

99
            $this->identities = $identityModel->getIdentities($this);
270✔
100
        }
101
    }
102

103
    /**
104
     * Accessor method for this user's UserIdentity objects.
105
     * Will populate if they don't exist.
106
     *
107
     * @param string $type 'all' returns all identities.
108
     *
109
     * @return list<UserIdentity>
110
     */
111
    public function getIdentities(string $type = 'all'): array
112
    {
113
        $this->populateIdentities();
270✔
114

115
        if ($type === 'all') {
270✔
116
            return $this->identities;
6✔
117
        }
118

119
        $identities = [];
270✔
120

121
        foreach ($this->identities as $identity) {
270✔
122
            if ($identity->type === $type) {
52✔
123
                $identities[] = $identity;
52✔
124
            }
125
        }
126

127
        return $identities;
270✔
128
    }
129

130
    public function setIdentities(array $identities): void
131
    {
132
        $this->identities = $identities;
4✔
133
    }
134

135
    /**
136
     * Creates a new identity for this user with an email/password
137
     * combination.
138
     *
139
     * @phpstan-param array{email: string, password: string} $credentials
140
     */
141
    public function createEmailIdentity(array $credentials): void
142
    {
143
        /** @var UserIdentityModel $identityModel */
144
        $identityModel = model(UserIdentityModel::class);
100✔
145

146
        $identityModel->createEmailIdentity($this, $credentials);
100✔
147

148
        // Ensure we will reload all identities
149
        $this->identities = null;
100✔
150
    }
151

152
    /**
153
     * Returns the user's Email/Password identity.
154
     */
155
    public function getEmailIdentity(): ?UserIdentity
156
    {
157
        return $this->getIdentity(Session::ID_TYPE_EMAIL_PASSWORD);
270✔
158
    }
159

160
    /**
161
     * If $email, $password, or $password_hash have been updated,
162
     * will update the user's email identity record with the
163
     * correct values.
164
     */
165
    public function saveEmailIdentity(): bool
166
    {
167
        if (($this->email === null || $this->email === '') && ($this->password === null || $this->password === '') && ($this->password_hash === null || $this->password_hash === '')) {
277✔
168
            return true;
266✔
169
        }
170

171
        $identity = $this->getEmailIdentity();
26✔
172
        if ($identity === null) {
26✔
173
            // Ensure we reload all identities
174
            $this->identities = null;
23✔
175

176
            $this->createEmailIdentity([
23✔
177
                'email'    => $this->email,
23✔
178
                'password' => '',
23✔
179
            ]);
23✔
180

181
            $identity = $this->getEmailIdentity();
23✔
182
        }
183

184
        if ($this->email !== null && $this->email !== '') {
26✔
185
            $identity->secret = $this->email;
24✔
186
        }
187

188
        if ($this->password !== null && $this->password !== '') {
26✔
189
            $identity->secret2 = service('passwords')->hash($this->password);
22✔
190
        }
191

192
        if ($this->password_hash !== null && $this->password_hash !== '' && ($this->password === null || $this->password === '')) {
26✔
193
            $identity->secret2 = $this->password_hash;
6✔
194
        }
195

196
        /** @var UserIdentityModel $identityModel */
197
        $identityModel = model(UserIdentityModel::class);
26✔
198

199
        try {
200
            /** @throws DataException */
201
            $identityModel->save($identity);
26✔
202
        } catch (DataException $e) {
1✔
203
            // There may be no data to update.
204
            $messages = [
1✔
205
                lang('Database.emptyDataset', ['insert']),
1✔
206
                lang('Database.emptyDataset', ['update']),
1✔
207
            ];
1✔
208
            if (in_array($e->getMessage(), $messages, true)) {
1✔
209
                return true;
1✔
210
            }
211

212
            throw $e;
×
213
        }
214

215
        return true;
25✔
216
    }
217

218
    /**
219
     * Update the last used at date for an identity record.
220
     */
221
    public function touchIdentity(UserIdentity $identity): void
222
    {
223
        /** @var UserIdentityModel $identityModel */
224
        $identityModel = model(UserIdentityModel::class);
14✔
225

226
        $identityModel->touchIdentity($identity);
14✔
227
    }
228

229
    /**
230
     * Accessor method to grab the user's email address.
231
     * Will cache it in $this->email, since it has
232
     * to hit the database the first time to get it, most likely.
233
     */
234
    public function getEmail(): ?string
235
    {
236
        if ($this->email === null) {
294✔
237
            $this->email = $this->getEmailIdentity()->secret ?? null;
259✔
238
        }
239

240
        return $this->email;
294✔
241
    }
242

243
    public function setEmail(string $email): void
244
    {
245
        $this->email = $email;
296✔
246
    }
247

248
    public function getPassword(): ?string
249
    {
250
        return $this->password;
270✔
251
    }
252

253
    public function setPassword(string $password): User
254
    {
255
        $this->password = $password;
272✔
256

257
        return $this;
272✔
258
    }
259

260
    public function setPasswordHash(string $hash): User
261
    {
262
        $this->password_hash = $hash;
271✔
263

264
        return $this;
271✔
265
    }
266

267
    /**
268
     * Accessor method to grab the user's password hash.
269
     * Will cache it in $this->attributes, since it has
270
     * to hit the database the first time to get it, most likely.
271
     */
272
    public function getPasswordHash(): ?string
273
    {
274
        if ($this->password_hash === null) {
270✔
275
            $this->password_hash = $this->getEmailIdentity()->secret2 ?? null;
270✔
276
        }
277

278
        return $this->password_hash;
270✔
279
    }
280

281
    /**
282
     * Returns the previous login information for this user
283
     */
284
    public function previousLogin(): ?Login
285
    {
286
        /** @var LoginModel $logins */
287
        $logins = model(LoginModel::class);
1✔
288

289
        return $logins->previousLogin($this);
1✔
290
    }
291

292
    /**
293
     * Returns the last login information for this user as
294
     */
295
    public function lastLogin(): ?Login
296
    {
297
        /** @var LoginModel $logins */
298
        $logins = model(LoginModel::class);
1✔
299

300
        return $logins->lastLogin($this);
1✔
301
    }
302
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc