Completed
Push — master ( 499ad2...479a86 )
by Beñat
04:33
created

SqlUserRepository::buildUser()   B

Complexity

Conditions 5
Paths 16

Size

Total Lines 40
Code Lines 30

Duplication

Lines 12
Ratio 30 %

Importance

Changes 0
Metric Value
dl 12
loc 40
rs 8.439
c 0
b 0
f 0
cc 5
eloc 30
nc 16
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 View Code Duplication
    public function userOfId(UserId $anId)
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...
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 View Code Duplication
    public function userOfEmail(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...
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 View Code Duplication
    public function userOfConfirmationToken(UserToken $aConfirmationToken)
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...
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 View Code Duplication
    public function userOfInvitationToken(UserToken $anInvitationToken)
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...
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 View Code Duplication
    public function userOfRememberPasswordToken(UserToken $aRememberPasswordToken)
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...
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 View Code Duplication
        if (null !== $row['confirmation_token_token']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
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 View Code Duplication
        if (null !== $row['invitation_token_token']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
325
            $invitationToken = new UserToken($row['invitation_token_token']);
326
            $this->set($invitationToken, 'createdOn', new \DateTimeImmutable($row['invitation_token_created_on']));
327
        }
328
        $rememberPasswordToken = null;
329 View Code Duplication
        if (null !== $row['remember_password_token_token']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
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 128 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