Completed
Push — master ( 4bad87...d6e1be )
by Derek Stephen
07:20
created

User::authenticate()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 36
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 36
ccs 0
cts 21
cp 0
rs 6.7272
cc 7
eloc 22
nc 7
nop 2
crap 56
1
<?php
2
3
namespace Del\Service;
4
5
use DateTime;
6
use Del\Criteria\UserCriteria;
7
use Del\Entity\EmailLink;
8
use Del\Entity\Person;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Del\Service\Person.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
9
use Del\Entity\User as UserEntity;
10
use Del\Exception\EmailLinkException;
11
use Del\Exception\UserException;
12
use Del\Repository\User as UserRepository;
13
use Del\Service\Person as PersonService;
14
use Del\Value\User\State;
15
use Doctrine\ORM\EntityManager;
16
use InvalidArgumentException;
17
use Zend\Crypt\Password\Bcrypt;
18
19
class User
20
{
21
    /** @var EntityManager $em */
22
    protected $em;
23
24
    /** @var  PersonService */
25
    private $personSvc;
26
27 3
    public function __construct(EntityManager $em, PersonService $personSvc)
0 ignored issues
show
Bug introduced by
You have injected the EntityManager via parameter $em. This is generally not recommended as it might get closed and become unusable. Instead, it is recommended to inject the ManagerRegistry and retrieve the EntityManager via getManager() each time you need it.

The EntityManager might become unusable for example if a transaction is rolled back and it gets closed. Let’s assume that somewhere in your application, or in a third-party library, there is code such as the following:

function someFunction(ManagerRegistry $registry) {
    $em = $registry->getManager();
    $em->getConnection()->beginTransaction();
    try {
        // Do something.
        $em->getConnection()->commit();
    } catch (\Exception $ex) {
        $em->getConnection()->rollback();
        $em->close();

        throw $ex;
    }
}

If that code throws an exception and the EntityManager is closed. Any other code which depends on the same instance of the EntityManager during this request will fail.

On the other hand, if you instead inject the ManagerRegistry, the getManager() method guarantees that you will always get a usable manager instance.

Loading history...
28
    {
29 3
        $this->em = $em;
30 3
        $this->personSvc = $personSvc;
31 3
    }
32
33
   /** 
34
    * @param array $data
35
    * @return UserEntity
36
    */
37 3
    public function createFromArray(array $data)
38
    {
39 3
        $user = new UserEntity();
40 3
        $user->setPerson(new Person());
41 3
        isset($data['id']) ? $user->setId($data['id']) : null;
42 3
        isset($data['email']) ? $user->setEmail($data['email']) : null;
43 3
        isset($data['password']) ? $user->setPassword($data['password']) : null;
44 3
        isset($data['state']) ? $user->setState(new State($data['state'])) : null;
45 3
        isset($data['registrationDate']) ? $user->setRegistrationDate(new DateTime($data['registrationDate'])) : null;
46 3
        isset($data['lastLogin']) ? $user->setLastLogin(new DateTime($data['lastLogin'])) : null;
47 3
        return $user;
48
    }
49
50
51
52
53
    /**
54
     * @return array
55
     */
56 1
    public function toArray(UserEntity $user)
57
    {
58
        return array
59
        (
60 1
            'id' => $user->getID(),
61 1
            'email' => $user->getEmail(),
62 1
            'person' => $user->getPerson(),
63 1
            'password' => $user->getPassword(),
64 1
            'state' => $user->getState()->getValue(),
65 1
            'registrationDate' => is_null($user->getRegistrationDate()) ? null : $user->getRegistrationDate()->format('Y-m-d H:i:s'),
66 1
            'lastLoginDate' => is_null($user->getLastLoginDate()) ? null : $user->getLastLoginDate()->format('Y-m-d H:i:s'),
67 1
        );
68
    }
69
70
    /**
71
     * @param UserEntity $user
72
     * @return UserEntity
73
     */
74 1
    public function saveUser(UserEntity $user)
75
    {
76 1
        return $this->getUserRepository()->save($user);
77
    }
78
79
    /**
80
     * @param int $id
81
     * @return UserEntity|null
82
     */
83 View Code Duplication
    public function findUserById($id)
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...
84
    {
85
        $criteria = new UserCriteria();
86
        $criteria->setId($id);
87
        $results = $this->getUserRepository()->findByCriteria($criteria);
88
        return (count($results)) ? $results[0] : null;
89
    }
90
91
    /**
92
     * @param string $email
93
     * @return UserEntity|null
94
     */
95 View Code Duplication
    public function findUserByEmail($email)
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...
96
    {
97
        $criteria = new UserCriteria();
98
        $criteria->setEmail($email);
99
        $result = $this->getUserRepository()->findByCriteria($criteria);
100
        return count($result) ? $result[0] : null;
101
    }
102
103
   /**
104
    * @return UserRepository
105
    */
106 1
    private function getUserRepository()
107
    {
108 1
        return $this->em->getRepository('Del\Entity\User');
109
    }
110
111
    /**
112
     * @return \Del\Repository\EmailLink
113
     */
114
    private function getEmailLinkRepository()
115
    {
116
        return $this->em->getRepository('Del\Entity\EmailLink');
117
    }
118
119
    public function registerUser(array $data)
120
    {
121
        if (!$data['email'] || !$data['password'] || !$data['confirm']) {
122
            throw new InvalidArgumentException();
123
        }
124
        if ($data['password'] !== $data['confirm']) {
125
            throw new UserException(UserException::WRONG_PASSWORD);
126
        }
127
128
        $criteria = new UserCriteria();
129
        $criteria->setEmail($data['email']);
130
        $user = $this->getUserRepository()->findByCriteria($criteria);
131
        if(!empty($user)) {
132
            throw new UserException(UserException::USER_EXISTS);
133
        }
134
135
        $person = new Person();
136
        $user = new UserEntity();
137
        $state = new State(State::STATE_UNACTIVATED);
138
        $user->setPerson($person)
139
             ->setEmail($data['email'])
140
             ->setRegistrationDate(new DateTime())
141
             ->setState($state);
142
143
        $bcrypt = new Bcrypt();
144
        $bcrypt->setCost(14);
145
146
        $encryptedPassword = $bcrypt->create($data['password']);
147
        $user->setPassword($encryptedPassword);
148
149
        $this->saveUser($user);
150
        return $user;
151
    }
152
153
    /**
154
     * @param UserEntity $user
155
     * @param $password
156
     * @return UserEntity
157
     */
158
    public function changePassword(UserEntity $user, $password)
159
    {
160
        $bcrypt = new Bcrypt();
161
        $bcrypt->setCost(14);
162
163
        $encryptedPassword = $bcrypt->create($password);
164
        $user->setPassword($encryptedPassword);
165
166
        $this->saveUser($user);
167
        return $user;
168
    }
169
170
    /**
171
     * @param UserEntity $user
172
     * @param int $expiry_days
173
     */
174
    public function generateEmailLink(UserEntity $user, $expiry_days = 7)
175
    {
176
        $date = new DateTime();
177
        $date->modify('+'.$expiry_days.' days');
178
        $token = md5(uniqid(rand(), true));
179
        $link = new EmailLink();
180
        $link->setUser($user);
181
        $link->setToken($token);
182
        $link->setExpiryDate($date);
183
        return $this->getEmailLinkRepository()->save($link);
184
    }
185
186
    /**
187
     * @param EmailLink $link
188
     */
189
    public function deleteEmailLink(EmailLink $link)
190
    {
191
        /** @var EmailLink $link */
192
        $link = $this->em->merge($link);
193
        $this->getEmailLinkRepository()->delete($link);
194
    }
195
196
    /**
197
     * @param $email
198
     * @param $token
199
     * @throws EmailLinkException
200
     */
201
    public function findEmailLink($email, $token)
202
    {
203
        $link = $this->getEmailLinkRepository()->findByToken($token);
204
        if(!$link) {
205
            throw new EmailLinkException(EmailLinkException::LINK_NOT_FOUND);
206
        }
207
        if($link->getUser()->getEmail() != $email) {
208
            throw new EmailLinkException(EmailLinkException::LINK_NO_MATCH);
209
        }
210
        if($link->getExpiryDate() < new DateTime()) {
211
            throw new EmailLinkException(EmailLinkException::LINK_EXPIRED);
212
        }
213
        return $link;
214
    }
215
216
    /**
217
     * @param string $email
218
     * @param string $password
219
     * @return int
220
     * @throws UserException
221
     */
222
    function authenticate($email, $password)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
223
    {
224
        $criteria = new UserCriteria();
225
        $criteria->setEmail($email);
226
227
        /** @var UserEntity $user  */
228
        $user = $this->getUserRepository()->findByCriteria($criteria)[0];
229
230
        if(!$user) {
231
            throw new UserException(UserException::USER_NOT_FOUND);
232
        }
233
234
        switch($user->getState()->getValue()) {
235
            case State::STATE_UNACTIVATED :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
236
                throw new UserException(UserException::USER_UNACTIVATED);
237
                break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
238
            case State::STATE_DISABLED :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
239
            case State::STATE_SUSPENDED :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
240
                throw new UserException(UserException::USER_DISABLED);
241
                break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
242
            case State::STATE_BANNED :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
243
                throw new UserException(UserException::USER_BANNED);
244
                break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
245
246
        }
247
248
        $bcrypt = new Bcrypt();
249
        $bcrypt->setCost(14);
250
251
        if(!$bcrypt->verify($password, $user->getPassword()))
252
        {
253
            throw new UserException(UserException::WRONG_PASSWORD);
254
        }
255
256
        return $user->getID();
257
    }
258
259
    /**
260
     * @param UserCriteria $criteria
261
     * @return array
262
     */
263
    public function findByCriteria(UserCriteria $criteria)
264
    {
265
        return $this->findByCriteria($criteria);
266
    }
267
268
    /**
269
     * @param UserEntity $user
270
     * @param $password
271
     * @return bool
272
     */
273
    public function checkPassword(UserEntity $user, $password)
274
    {
275
        $bcrypt = new Bcrypt();
276
        $bcrypt->setCost(14);
277
278
        return $bcrypt->verify($password, $user->getPassword());
279
    }
280
281
}
282