Passed
Push — master ( 6c1980...4e24b5 )
by Alexey
03:34
created

UserApi::getUsersFromList()   C

Complexity

Conditions 8
Paths 5

Size

Total Lines 32
Code Lines 16

Duplication

Lines 4
Ratio 12.5 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 4
loc 32
ccs 0
cts 14
cp 0
rs 5.3846
cc 8
eloc 16
nc 5
nop 1
crap 72
1
<?php
2
3
namespace Skobkin\Bundle\PointToolsBundle\Service;
4
5
use Doctrine\ORM\EntityManager;
6
use Doctrine\ORM\EntityRepository;
7
use Guzzle\Http\Exception\ClientErrorResponseException;
8
use Guzzle\Service\Client;
9
use JMS\Serializer\Serializer;
10
use Skobkin\Bundle\PointToolsBundle\DTO\Api\Auth;
11
use Skobkin\Bundle\PointToolsBundle\Entity\User;
12
use Skobkin\Bundle\PointToolsBundle\Service\Exceptions\ApiException;
13
use Skobkin\Bundle\PointToolsBundle\Service\Exceptions\InvalidResponseException;
14
use Skobkin\Bundle\PointToolsBundle\Service\Exceptions\UserNotFoundException;
15
use Symfony\Component\HttpFoundation\Response;
16
17
/**
18
 * Basic Point.im user API functions from /api/user/*
19
 */
20
class UserApi extends AbstractApi
21
{
22
    const AVATAR_SIZE_SMALL = '24';
23
    const AVATAR_SIZE_MEDIUM = '40';
24
    const AVATAR_SIZE_LARGE = '80';
25
26
    /**
27
     * @var string Base URL for user avatars
28
     */
29
    protected $avatarsBaseUrl = '//point.im/avatar/';
30
31
    /**
32
     * @var EntityManager
33
     */
34
    protected $em;
35
36
    /**
37
     * @var EntityRepository
38
     */
39
    protected $userRepository;
40
41
    /**
42
     * @var Serializer
43
     */
44
    private $serializer;
45
46
47 12
    public function __construct(Client $httpClient, $https = true, $baseUrl = null, EntityManager $entityManager, Serializer $serializer)
0 ignored issues
show
Bug introduced by
You have injected the EntityManager via parameter $entityManager. 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...
48
    {
49 12
        parent::__construct($httpClient, $https, $baseUrl);
50
51 12
        $this->em = $entityManager;
52 12
        $this->serializer = $serializer;
53 12
        $this->userRepository = $this->em->getRepository('SkobkinPointToolsBundle:User');
54 12
    }
55
56
    public function isAuthDataValid(string $login, string $password): bool
57
    {
58
        $auth = $this->authenticate($login, $password);
59
60
        if (null === $auth->getError() && null !== $auth->getToken()) {
61
            $this->logout($auth);
62
63
            return true;
64
        }
65
66
        return false;
67
    }
68
69
    public function authenticate(string $login, string $password): Auth
70
    {
71
        try {
72
            $authData = $this->getPostRequestData(
73
                '/api/login',
74
                [
75
                    'login' => $login,
76
                    'password' => $password,
77
                ]
78
            );
79
80
            return $this->serializer->deserialize($authData, Auth::class, 'json');
81
        } catch (ClientErrorResponseException $e) {
82
            if (Response::HTTP_NOT_FOUND === $e->getResponse()->getStatusCode()) {
83
                throw new InvalidResponseException('API method not found', 0, $e);
84
            } else {
85
                throw $e;
86
            }
87
        }
88
    }
89
90
    public function logout(Auth $auth): bool
91
    {
92
        try {
93
            $this->getPostRequestData('/api/logout', ['csrf_token' => $auth->getCsRfToken()]);
94
95
            return true;
96
        } catch (ClientErrorResponseException $e) {
97
            if (Response::HTTP_NOT_FOUND === $e->getResponse()->getStatusCode()) {
98
                throw new InvalidResponseException('API method not found', 0, $e);
99
            } elseif (Response::HTTP_FORBIDDEN === $e->getResponse()->getStatusCode()) {
100
                return true;
101
            } else {
102
                throw $e;
103
            }
104
        }
105
    }
106
107
    /**
108
     * Get user subscribers by user login
109
     *
110
     * @param string $login
111
     * @return User[]
112
     * @throws ApiException
113
     * @throws InvalidResponseException
114
     * @throws UserNotFoundException
115
     */
116 View Code Duplication
    public function getUserSubscribersByLogin($login)
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...
117
    {
118
        try {
119
            $usersList = $this->getGetRequestData('/api/user/'.urlencode($login).'/subscribers', [], true);
120
        } catch (ClientErrorResponseException $e) {
121
            if (Response::HTTP_NOT_FOUND === $e->getResponse()->getStatusCode()) {
122
                throw new UserNotFoundException('User not found', 0, $e, null, $login);
123
            } else {
124
                throw $e;
125
            }
126
        }
127
128
        return $this->getUsersFromList($usersList);
129
    }
130
131
    /**
132
     * Get user subscribers by user id
133
     *
134
     * @param $id
135
     * @return User[]
136
     * @throws ApiException
137
     * @throws InvalidResponseException
138
     * @throws UserNotFoundException
139
     */
140 View Code Duplication
    public function getUserSubscribersById($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...
141
    {
142
        if (!is_numeric($id)) {
143
            throw new \InvalidArgumentException('$id must be an integer');
144
        }
145
146
        try {
147
            $usersList = $this->getGetRequestData('/api/user/id/'.(int) $id.'/subscribers', [], true);
148
        } catch (ClientErrorResponseException $e) {
149
            if (Response::HTTP_NOT_FOUND === $e->getResponse()->getStatusCode()) {
150
                throw new UserNotFoundException('User not found', 0, $e, $id);
151
            } else {
152
                throw $e;
153
            }
154
        }
155
156
        return $this->getUsersFromList($usersList);
157
    }
158
159
    /**
160
     * Get user subscriptions by user login
161
     *
162
     * @param string $login
163
     * @return User[]
164
     * @throws ApiException
165
     * @throws InvalidResponseException
166
     * @throws UserNotFoundException
167
     */
168 View Code Duplication
    public function getUserSubscriptionsByLogin($login)
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...
169
    {
170
        try {
171
            $usersList = $this->getGetRequestData('/api/user/'.urlencode($login).'/subscriptions', [], true);
172
        } catch (ClientErrorResponseException $e) {
173
            if (Response::HTTP_NOT_FOUND === $e->getResponse()->getStatusCode()) {
174
                throw new UserNotFoundException('User not found', 0, $e, null, $login);
175
            } else {
176
                throw $e;
177
            }
178
        }
179
180
        return $this->getUsersFromList($usersList);
181
    }
182
183
    /**
184
     * Get user subscriptions by user id
185
     *
186
     * @param $id
187
     * @return User[]
188
     * @throws ApiException
189
     * @throws InvalidResponseException
190
     * @throws UserNotFoundException
191
     */
192 View Code Duplication
    public function getUserSubscriptionsById($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...
193
    {
194
        if (!is_numeric($id)) {
195
            throw new \InvalidArgumentException('$id must be an integer');
196
        }
197
198
        try {
199
            $usersList = $this->getGetRequestData('/api/user/id/'.(int) $id.'/subscriptions', [], true);
200
        } catch (ClientErrorResponseException $e) {
201
            if (Response::HTTP_NOT_FOUND === $e->getResponse()->getStatusCode()) {
202
                throw new UserNotFoundException('User not found', 0, $e, $id);
203
            } else {
204
                throw $e;
205
            }
206
        }
207
208
        return $this->getUsersFromList($usersList);
209
    }
210
211
    /**
212
     * Get single user by login
213
     *
214
     * @param string $login
215
     * @return User
216
     * @throws UserNotFoundException
217
     * @throws ClientErrorResponseException
218
     */
219 View Code Duplication
    public function getUserByLogin($login)
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...
220
    {
221
        try {
222
            $userInfo = $this->getGetRequestData('/api/user/login/'.urlencode($login), [], true);
223
        } catch (ClientErrorResponseException $e) {
224
            if (Response::HTTP_NOT_FOUND === $e->getResponse()->getStatusCode()) {
225
                throw new UserNotFoundException('User not found', 0, $e, null, $login);
226
            } else {
227
                throw $e;
228
            }
229
        }
230
231
        return $this->getUserFromUserInfo($userInfo);
232
    }
233
234
    /**
235
     * Get single user by id
236
     *
237
     * @param $id
238
     * @return User
239
     * @throws UserNotFoundException
240
     * @throws ClientErrorResponseException
241
     */
242 View Code Duplication
    public function getUserById($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...
243
    {
244
        if (!is_numeric($id)) {
245
            throw new \InvalidArgumentException('$id must be an integer');
246
        }
247
248
        try {
249
            $userInfo = $this->getGetRequestData('/api/user/id/'.(int) $id, [], true);
250
        } catch (ClientErrorResponseException $e) {
251
            if (Response::HTTP_NOT_FOUND === $e->getResponse()->getStatusCode()) {
252
                throw new UserNotFoundException('User not found', 0, $e, $id);
253
            } else {
254
                throw $e;
255
            }
256
        }
257
258
        return $this->getUserFromUserInfo($userInfo);
259
    }
260
261
    /**
262
     * Finds and updates or create new user from API response data
263
     *
264
     * @param array $userInfo
265
     * @return User
266
     * @throws ApiException
267
     * @throws InvalidResponseException
268
     */
269
    public function getUserFromUserInfo(array $userInfo)
270
    {
271
        if (!is_array($userInfo)) {
272
            throw new \InvalidArgumentException('$userInfo must be an array');
273
        }
274
275
        // @todo Refactor to UserFactory->createFromArray()
276
        if (array_key_exists('id', $userInfo) && array_key_exists('login', $userInfo) && array_key_exists('name', $userInfo) && is_numeric($userInfo['id'])) {
277
            /** @var User $user */
278 View Code Duplication
            if (null === ($user = $this->userRepository->find($userInfo['id']))) {
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...
279
                // Creating new user
280
                $user = new User($userInfo['id']);
281
                $this->em->persist($user);
282
            }
283
284
            // Updating data
285
            $user
286
                ->setLogin($userInfo['login'])
287
                ->setName($userInfo['name'])
288
            ;
289
290
            return $user;
291
        }
292
293
        throw new InvalidResponseException('Invalid API response. Mandatory fields do not exist.');
294
    }
295
296
    /**
297
     * Get array of User objects from API response containing user list
298
     *
299
     * @param array $users
300
     * @return User[]
301
     * @throws ApiException
302
     * @throws InvalidResponseException
303
     */
304
    private function getUsersFromList(array $users = [])
305
    {
306
        if (!is_array($users)) {
307
            throw new \InvalidArgumentException('$users must be an array');
308
        }
309
310
        /** @var User[] $resultUsers */
311
        $resultUsers = [];
312
313
        foreach ($users as $userInfo) {
314
            if (array_key_exists('id', $userInfo) && array_key_exists('login', $userInfo) && array_key_exists('name', $userInfo) && is_numeric($userInfo['id'])) {
315
316
                // @todo Optimize with prehashed id's list
317 View Code Duplication
                if (null === ($user = $this->userRepository->find($userInfo['id']))) {
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...
318
                    $user = new User((int) $userInfo['id']);
319
                    $this->em->persist($user);
320
                }
321
322
                // Updating data
323
                $user
324
                    ->setLogin($userInfo['login'])
325
                    ->setName($userInfo['name'])
326
                ;
327
328
                $resultUsers[] = $user;
329
            } else {
330
                throw new InvalidResponseException('Invalid API response. Mandatory fields do not exist.');
331
            }
332
        }
333
334
        return $resultUsers;
335
    }
336
337
    /**
338
     * Creates URL of avatar with specified size by User object
339
     *
340
     * @param User $user
341
     * @param int $size
342
     * @return string
343
     */
344 3
    public function getAvatarUrl(User $user, $size)
345
    {
346 3
        return $this->getAvatarUrlByLogin($user->getLogin(), $size);
347
    }
348
349
    /**
350
     * Creates URL of avatar with specified size by login string
351
     *
352
     * @param $login
353
     * @param $size
354
     *
355
     * @return string
356
     */
357 4
    public function getAvatarUrlByLogin($login, $size)
358
    {
359 4
        if (!in_array($size, [self::AVATAR_SIZE_SMALL, self::AVATAR_SIZE_MEDIUM, self::AVATAR_SIZE_LARGE], true)) {
360
            throw new \InvalidArgumentException('Avatar size must be one of restricted variants. See UserApi class AVATAR_SIZE_* constants.');
361
        }
362
363 4
        return $this->avatarsBaseUrl.urlencode($login).'/'.$size;
364
    }
365
}
366