Completed
Push — master ( 62012c...cb5fb6 )
by Beñat
03:27
created

SqlUserRepository::exist()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 5
nc 1
nop 1
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\Infrastructure\Persistence;
14
15
use BenGorUser\User\Domain\Model\User;
16
use BenGorUser\User\Domain\Model\UserEmail;
17
use BenGorUser\User\Domain\Model\UserId;
18
use BenGorUser\User\Domain\Model\UserPassword;
19
use BenGorUser\User\Domain\Model\UserRepository;
20
use BenGorUser\User\Domain\Model\UserRole;
21
use BenGorUser\User\Domain\Model\UserToken;
22
use BenGorUser\User\Infrastructure\Domain\Model\UserEventBus;
23
24
/**
25
 * Sql user repository class.
26
 *
27
 * @author Beñat Espiña <[email protected]>
28
 * @author Gorka Laucirica <[email protected]>
29
 */
30
final class SqlUserRepository implements UserRepository
31
{
32
    const DATE_FORMAT = 'Y-m-d H:i:s';
33
34
    /**
35
     * The pdo instance.
36
     *
37
     * @var \PDO
38
     */
39
    private $pdo;
40
41
    /**
42
     * The user event bus, it can be null.
43
     *
44
     * @var UserEventBus|null
45
     */
46
    private $eventBus;
47
48
    /**
49
     * Constructor.
50
     *
51
     * @param \PDO              $aPdo       The pdo instance
52
     * @param UserEventBus|null $anEventBus The user event bus, it can be null
53
     */
54
    public function __construct(\PDO $aPdo, UserEventBus $anEventBus = null)
55
    {
56
        $this->pdo = $aPdo;
57
        $this->eventBus = $anEventBus;
58
    }
59
60
    /**
61
     * {@inheritdoc}
62
     */
63
    public function userOfId(UserId $anId)
64
    {
65
        $statement = $this->execute('SELECT * FROM user WHERE id = :id', ['id' => $anId->id()]);
66
        if ($row = $statement->fetch(\PDO::FETCH_ASSOC)) {
67
            return $this->buildUser($row);
68
        }
69
    }
70
71
    /**
72
     * {@inheritdoc}
73
     */
74
    public function userOfEmail(UserEmail $anEmail)
75
    {
76
        $statement = $this->execute('SELECT * FROM user WHERE email = :email', ['email' => $anEmail->email()]);
77
        if ($row = $statement->fetch(\PDO::FETCH_ASSOC)) {
78
            return $this->buildUser($row);
79
        }
80
    }
81
82
    /**
83
     * {@inheritdoc}
84
     */
85
    public function userOfConfirmationToken(UserToken $aConfirmationToken)
86
    {
87
        $statement = $this->execute('SELECT * FROM user WHERE confirmation_token_token = :confirmationToken', [
88
            'confirmationToken' => $aConfirmationToken->token(),
89
        ]);
90
        if ($row = $statement->fetch(\PDO::FETCH_ASSOC)) {
91
            return $this->buildUser($row);
92
        }
93
    }
94
95
    /**
96
     * {@inheritdoc}
97
     */
98
    public function userOfInvitationToken(UserToken $anInvitationToken)
99
    {
100
        $statement = $this->execute('SELECT * FROM user WHERE invitation_token_token = :invitationToken', [
101
            'invitationToken' => $anInvitationToken->token(),
102
        ]);
103
        if ($row = $statement->fetch(\PDO::FETCH_ASSOC)) {
104
            return $this->buildUser($row);
105
        }
106
    }
107
108
    /**
109
     * {@inheritdoc}
110
     */
111
    public function userOfRememberPasswordToken(UserToken $aRememberPasswordToken)
112
    {
113
        $statement = $this->execute('SELECT * FROM user WHERE remember_password_token_token = :rememberPasswordToken', [
114
            'rememberPasswordToken' => $aRememberPasswordToken->token(),
115
        ]);
116
        if ($row = $statement->fetch(\PDO::FETCH_ASSOC)) {
117
            return $this->buildUser($row);
118
        }
119
    }
120
121
    /**
122
     * {@inheritdoc}
123
     */
124
    public function persist(User $aUser)
125
    {
126
        ($this->exist($aUser)) ? $this->update($aUser) : $this->insert($aUser);
127
128
        if ($this->eventBus instanceof UserEventBus) {
129
            $this->handle($aUser->events());
130
        }
131
    }
132
133
    /**
134
     * {@inheritdoc}
135
     */
136
    public function remove(User $aUser)
137
    {
138
        $this->execute('DELETE FROM user WHERE id = :id', ['id' => $aUser->id()->id()]);
139
140
        if ($this->eventBus instanceof UserEventBus) {
141
            $this->handle($aUser->events());
142
        }
143
    }
144
145
    /**
146
     * {@inheritdoc}
147
     */
148
    public function size()
149
    {
150
        return $this->pdo->query('SELECT COUNT(*) FROM user')->fetchColumn();
151
    }
152
153
    /**
154
     * Loads the user schema into database create the table
155
     * with user attribute properties as columns.
156
     */
157
    public function initSchema()
158
    {
159
        $this->pdo->exec(<<<'SQL'
160
DROP TABLE IF EXISTS user;
161
CREATE TABLE user (
162
    id CHAR(36) PRIMARY KEY,
163
    confirmation_token_token VARCHAR(36),
164
    confirmation_token_created_on DATETIME,
165
    created_on DATETIME NOT NULL,
166
    email VARCHAR(36) NOT NULL,
167
    invitation_token_token VARCHAR(36),
168
    invitation_token_created_on DATETIME,
169
    last_login DATETIME,
170
    password VARCHAR(30),
171
    remember_password_token_token VARCHAR(36),
172
    remember_password_token_created_on DATETIME,
173
    roles LONGTEXT NOT NULL COMMENT '(DC2Type:user_roles)',
174
    updated_on DATETIME NOT NULL
175
)
176
SQL
177
        );
178
    }
179
180
    /**
181
     * Checks if the user given exists in the database.
182
     *
183
     * @param User $aUser The user
184
     *
185
     * @return bool
186
     */
187
    private function exist(User $aUser)
188
    {
189
        $count = $this->execute(
190
            'SELECT COUNT(*) FROM user WHERE id = :id', [':id' => $aUser->id()->id()]
191
        )->fetchColumn();
192
193
        return (int)$count === 1;
194
    }
195
196
    /**
197
     * Prepares the insert SQL with the user given.
198
     *
199
     * @param User $aUser The user
200
     */
201
    private function insert(User $aUser)
202
    {
203
        $sql = 'INSERT INTO user (
204
            id,
205
            confirmation_token_token,
206
            confirmation_token_created_on,
207
            created_on,
208
            email,
209
            invitation_token_token,
210
            invitation_token_created_on,
211
            last_login,
212
            password,
213
            salt,
214
            remember_password_token_token,
215
            remember_password_token_created_on,
216
            roles,
217
            updated_on
218
        ) VALUES (
219
            :id,
220
            :confirmationTokenToken,
221
            :confirmationTokenCreatedOn,
222
            :createdOn,
223
            :email,
224
            :invitationTokenToken,
225
            :invitationTokenCreatedOn,
226
            :lastLogin,
227
            :password,
228
            :salt,
229
            :rememberPasswordTokenToken,
230
            :rememberPasswordTokenCreatedOn,
231
            :roles,
232
            :updatedOn
233
        )';
234
        $this->execute($sql, [
235
            'id'                             => $aUser->id()->id(),
236
            'confirmationTokenToken'         => $aUser->confirmationToken() ? $aUser->confirmationToken()->token() : null,
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 122 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
237
            'confirmationTokenCreatedOn'     => $aUser->confirmationToken() ? $aUser->confirmationToken()->createdOn() : null,
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 126 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
238
            'createdOn'                      => $aUser->createdOn()->format(self::DATE_FORMAT),
239
            'email'                          => $aUser->email()->email(),
240
            'invitationTokenToken'           => $aUser->invitationToken() ? $aUser->invitationToken()->token() : null,
241
            'invitationTokenCreatedOn'       => $aUser->invitationToken() ? $aUser->invitationToken()->createdOn() : null,
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 122 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
242
            'lastLogin'                      => $aUser->lastLogin() ? $aUser->lastLogin()->format(self::DATE_FORMAT) : null,
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 124 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
243
            'password'                       => $aUser->password()->encodedPassword(),
244
            'salt'                           => $aUser->password()->salt(),
245
            'rememberPasswordTokenToken'     => $aUser->rememberPasswordToken() ? $aUser->rememberPasswordToken()->token() : null,
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 130 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
246
            'rememberPasswordTokenCreatedOn' => $aUser->rememberPasswordToken() ? $aUser->rememberPasswordToken()->createdOn() : null,
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 134 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
247
            'roles'                          => $this->rolesToString($aUser->roles()),
248
            'updatedOn'                      => $aUser->updatedOn()->format(self::DATE_FORMAT),
249
        ]);
250
    }
251
252
    /**
253
     * Prepares the update SQL with the user given.
254
     *
255
     * @param User $aUser The user
256
     */
257
    private function update(User $aUser)
258
    {
259
        $sql = 'UPDATE user SET
260
            confirmation_token_token = :confirmationTokenToken,
261
            confirmation_token_created_on = :confirmationTokenCreatedOn,
262
            invitation_token_token = :invitationTokenToken,
263
            invitation_token_created_on = :invitationTokenCreatedOn,
264
            last_login = :lastLogin,
265
            password = :password,
266
            remember_password_token_token = :rememberPasswordTokenToken,
267
            remember_password_token_created_on = :rememberPasswordTokenCreatedOn,
268
            roles = :roles,
269
            updated_on = :updatedOn
270
            WHERE id = :id';
271
        $this->execute($sql, [
272
            'id'                             => $aUser->id()->id(),
273
            'confirmationTokenToken'         => $aUser->confirmationToken() ? $aUser->confirmationToken()->token() : null,
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 122 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
274
            'confirmationTokenCreatedOn'     => $aUser->confirmationToken() ? $aUser->confirmationToken()->createdOn() : null,
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 126 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
275
            'invitationTokenToken'           => $aUser->invitationToken() ? $aUser->invitationToken()->token() : null,
276
            'invitationTokenCreatedOn'       => $aUser->invitationToken() ? $aUser->invitationToken()->createdOn() : null,
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 122 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
277
            'lastLogin'                      => $aUser->lastLogin() ? $aUser->lastLogin()->format(self::DATE_FORMAT) : null,
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 124 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
278
            'password'                       => $aUser->password()->encodedPassword(),
279
            'rememberPasswordTokenToken'     => $aUser->rememberPasswordToken() ? $aUser->rememberPasswordToken()->token() : null,
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 130 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
280
            'rememberPasswordTokenCreatedOn' => $aUser->rememberPasswordToken() ? $aUser->rememberPasswordToken()->createdOn() : null,
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 134 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
281
            'roles'                          => $this->rolesToString($aUser->roles()),
282
            'updatedOn'                      => $aUser->updatedOn()->format(self::DATE_FORMAT),
283
        ]);
284
    }
285
286
    /**
287
     * Wrapper that encapsulates the same
288
     * logic about execute the query in PDO.
289
     *
290
     * @param string $aSql       The SQL
291
     * @param array  $parameters Array which contains the parameters of SQL
292
     *
293
     * @return \PDOStatement
294
     */
295
    private function execute($aSql, array $parameters)
296
    {
297
        $statement = $this->pdo->prepare($aSql);
298
        $statement->execute($parameters);
299
300
        return $statement;
301
    }
302
303
    /**
304
     * Builds the user with the given sql row attributes.
305
     *
306
     * @param array $row Array which contains attributes of user
307
     *
308
     * @return User
309
     */
310
    private function buildUser($row)
311
    {
312
        $createdOn = new \DateTimeImmutable($row['created_on']);
313
        $updatedOn = new \DateTimeImmutable($row['updated_on']);
314
        $lastLogin = null === $row['last_login']
315
            ? null
316
            : new \DateTimeImmutable($row['last_login']);
317
318
        $confirmationToken = null;
319
        if (null !== $row['confirmation_token_token']) {
320
            $confirmationToken = new UserToken($row['confirmation_token_token']);
321
            $this->set($confirmationToken, 'createdOn', new \DateTimeImmutable($row['confirmation_token_created_on']));
322
        }
323
        $invitationToken = null;
324
        if (null !== $row['invitation_token_token']) {
325
            $invitationToken = new UserToken($row['invitation_token_token']);
326
            $this->set($invitationToken, 'createdOn', new \DateTimeImmutable($row['invitation_token_created_on']);
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected ';', expecting ',' or ')'
Loading history...
327
        }
328
        $rememberPasswordToken = null;
329
        if (null !== $row['remember_password_token_token']) {
330
            $rememberPasswordToken = new UserToken($row['remember_password_token_token']);
331
            $this->set($rememberPasswordToken, 'createdOn', new \DateTimeImmutable($row['remember_password_token_created_on']);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 127 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
332
        }
333
334
        $user = User::signUp(
335
            new UserId($row['id']),
336
            new UserEmail($row['email']),
337
            UserPassword::fromEncoded($row['password'], $row['salt']),
338
            $this->rolesToArray($row['roles'])
339
        );
340
341
        $user = $this->set($user, 'createdOn', $createdOn);
342
        $user = $this->set($user, 'updatedOn', $updatedOn);
343
        $user = $this->set($user, 'lastLogin', $lastLogin);
344
        $user = $this->set($user, 'confirmationToken', $confirmationToken);
345
        $user = $this->set($user, 'invitationToken', $invitationToken);
346
        $user = $this->set($user, 'rememberPasswordToken', $rememberPasswordToken);
347
348
        return $user;
349
    }
350
351
    /**
352
     * Transforms given user roles into encoded plain json array.
353
     *
354
     * @param array $userRoles Array which contains the user roles
355
     *
356
     * @return string
357
     */
358
    private function rolesToString(array $userRoles)
359
    {
360
        return json_encode(
361
            array_map(function (UserRole $userRole) {
362
                return $userRole->role();
363
            }, $userRoles)
364
        );
365
    }
366
367
    /**
368
     * Transforms given user roles encoded array into user roles collection.
369
     *
370
     * @param array $userRoles Encoded json array
371
     *
372
     * @return UserRole[]
373
     */
374
    private function rolesToArray($userRoles)
375
    {
376
        return array_map(function ($userRole) {
377
            return new UserRole($userRole);
378
        }, json_decode($userRoles));
379
    }
380
381
    /**
382
     * Populates by Reflection the domain object with the given SQL plain values.
383
     *
384
     * @param object $object        The domain object
385
     * @param string $propertyName  The property name
386
     * @param mixed  $propertyValue The property value
387
     *
388
     * @return object
389
     */
390
    private function set($object, $propertyName, $propertyValue)
391
    {
392
        $reflectionUser = new \ReflectionClass($object);
393
394
        $reflectionProperty = $reflectionUser->getProperty($propertyName);
395
        $reflectionProperty->setAccessible(true);
396
        $reflectionProperty->setValue($object, $propertyValue);
397
398
        return $object;
399
    }
400
401
    /**
402
     * Handles the given events with event bus.
403
     *
404
     * @param array $events A collection of user domain events
405
     */
406
    private function handle($events)
407
    {
408
        foreach ($events as $event) {
409
            $this->eventBus->handle($event);
410
        }
411
    }
412
}
413