1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Skobkin\Bundle\PointToolsBundle\Service; |
4
|
|
|
|
5
|
|
|
use Doctrine\ORM\EntityManager; |
6
|
|
|
use Doctrine\ORM\EntityRepository; |
7
|
|
|
use GuzzleHttp\ClientInterface; |
8
|
|
|
use GuzzleHttp\Exception\RequestException; |
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(ClientInterface $httpClient, EntityManager $entityManager, Serializer $serializer) |
|
|
|
|
48
|
|
|
{ |
49
|
12 |
|
parent::__construct($httpClient); |
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 (RequestException $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 (RequestException $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
|
|
|
* |
112
|
|
|
* @return User[] |
113
|
|
|
* |
114
|
|
|
* @throws ApiException |
115
|
|
|
* @throws InvalidResponseException |
116
|
|
|
* @throws UserNotFoundException |
117
|
|
|
*/ |
118
|
|
View Code Duplication |
public function getUserSubscribersByLogin(string $login): array |
|
|
|
|
119
|
|
|
{ |
120
|
|
|
try { |
121
|
|
|
$usersList = $this->getGetRequestData('/api/user/'.urlencode($login).'/subscribers', [], true); |
122
|
|
|
} catch (RequestException $e) { |
123
|
|
|
if (Response::HTTP_NOT_FOUND === $e->getResponse()->getStatusCode()) { |
124
|
|
|
throw new UserNotFoundException('User not found', 0, $e, null, $login); |
125
|
|
|
} else { |
126
|
|
|
throw $e; |
127
|
|
|
} |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
return $this->getUsersFromList($usersList); |
|
|
|
|
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* Get user subscribers by user id |
135
|
|
|
* |
136
|
|
|
* @param int $id |
137
|
|
|
* |
138
|
|
|
* @return User[] |
139
|
|
|
* |
140
|
|
|
* @throws ApiException |
141
|
|
|
* @throws InvalidResponseException |
142
|
|
|
* @throws UserNotFoundException |
143
|
|
|
*/ |
144
|
|
View Code Duplication |
public function getUserSubscribersById(int $id): array |
|
|
|
|
145
|
|
|
{ |
146
|
|
|
try { |
147
|
|
|
$usersList = $this->getGetRequestData('/api/user/id/'.(int) $id.'/subscribers', [], true); |
148
|
|
|
} catch (RequestException $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
|
|
|
* |
164
|
|
|
* @return User[] |
165
|
|
|
* |
166
|
|
|
* @throws ApiException |
167
|
|
|
* @throws InvalidResponseException |
168
|
|
|
* @throws UserNotFoundException |
169
|
|
|
*/ |
170
|
|
View Code Duplication |
public function getUserSubscriptionsByLogin(string $login): array |
|
|
|
|
171
|
|
|
{ |
172
|
|
|
try { |
173
|
|
|
$usersList = $this->getGetRequestData('/api/user/'.urlencode($login).'/subscriptions', [], true); |
174
|
|
|
} catch (RequestException $e) { |
175
|
|
|
if (Response::HTTP_NOT_FOUND === $e->getResponse()->getStatusCode()) { |
176
|
|
|
throw new UserNotFoundException('User not found', 0, $e, null, $login); |
177
|
|
|
} else { |
178
|
|
|
throw $e; |
179
|
|
|
} |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
return $this->getUsersFromList($usersList); |
|
|
|
|
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
/** |
186
|
|
|
* Get user subscriptions by user id |
187
|
|
|
* |
188
|
|
|
* @param int $id |
189
|
|
|
* |
190
|
|
|
* @return User[] |
191
|
|
|
* |
192
|
|
|
* @throws ApiException |
193
|
|
|
* @throws InvalidResponseException |
194
|
|
|
* @throws UserNotFoundException |
195
|
|
|
*/ |
196
|
|
View Code Duplication |
public function getUserSubscriptionsById(int $id): array |
|
|
|
|
197
|
|
|
{ |
198
|
|
|
try { |
199
|
|
|
$usersList = $this->getGetRequestData('/api/user/id/'.(int) $id.'/subscriptions', [], true); |
200
|
|
|
} catch (RequestException $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
|
|
|
* |
216
|
|
|
* @return User |
217
|
|
|
* |
218
|
|
|
* @throws UserNotFoundException |
219
|
|
|
* @throws RequestException |
220
|
|
|
*/ |
221
|
|
View Code Duplication |
public function getUserByLogin(string $login): User |
|
|
|
|
222
|
|
|
{ |
223
|
|
|
try { |
224
|
|
|
$userInfo = $this->getGetRequestData('/api/user/login/'.urlencode($login), [], true); |
225
|
|
|
} catch (RequestException $e) { |
226
|
|
|
if (Response::HTTP_NOT_FOUND === $e->getResponse()->getStatusCode()) { |
227
|
|
|
throw new UserNotFoundException('User not found', 0, $e, null, $login); |
228
|
|
|
} else { |
229
|
|
|
throw $e; |
230
|
|
|
} |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
return $this->getUserFromUserInfo($userInfo); |
|
|
|
|
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
/** |
237
|
|
|
* Get single user by id |
238
|
|
|
* |
239
|
|
|
* @param int $id |
240
|
|
|
* |
241
|
|
|
* @return User |
242
|
|
|
* |
243
|
|
|
* @throws UserNotFoundException |
244
|
|
|
* @throws RequestException |
245
|
|
|
*/ |
246
|
|
View Code Duplication |
public function getUserById(int $id): User |
|
|
|
|
247
|
|
|
{ |
248
|
|
|
try { |
249
|
|
|
$userInfo = $this->getGetRequestData('/api/user/id/'.$id, [], true); |
250
|
|
|
} catch (RequestException $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
|
|
|
* |
266
|
|
|
* @return User |
267
|
|
|
* |
268
|
|
|
* @throws ApiException |
269
|
|
|
* @throws InvalidResponseException |
270
|
|
|
*/ |
271
|
|
|
public function getUserFromUserInfo(array $userInfo): User |
272
|
|
|
{ |
273
|
|
|
// @todo Refactor to UserFactory->createFromArray() |
274
|
|
|
if (array_key_exists('id', $userInfo) && array_key_exists('login', $userInfo) && array_key_exists('name', $userInfo) && is_numeric($userInfo['id'])) { |
275
|
|
|
/** @var User $user */ |
276
|
|
View Code Duplication |
if (null === ($user = $this->userRepository->find($userInfo['id']))) { |
|
|
|
|
277
|
|
|
// Creating new user |
278
|
|
|
$user = new User($userInfo['id']); |
279
|
|
|
$this->em->persist($user); |
280
|
|
|
} |
281
|
|
|
|
282
|
|
|
// Updating data |
283
|
|
|
$user |
284
|
|
|
->setLogin($userInfo['login']) |
285
|
|
|
->setName($userInfo['name']) |
286
|
|
|
; |
287
|
|
|
|
288
|
|
|
return $user; |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
throw new InvalidResponseException('Invalid API response. Mandatory fields do not exist.'); |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
/** |
295
|
|
|
* Get array of User objects from API response containing user list |
296
|
|
|
* |
297
|
|
|
* @param array $users |
298
|
|
|
* |
299
|
|
|
* @return User[] |
300
|
|
|
* |
301
|
|
|
* @throws ApiException |
302
|
|
|
* @throws InvalidResponseException |
303
|
|
|
*/ |
304
|
|
|
private function getUsersFromList(array $users = []): array |
305
|
|
|
{ |
306
|
|
|
/** @var User[] $resultUsers */ |
307
|
|
|
$resultUsers = []; |
308
|
|
|
|
309
|
|
|
foreach ($users as $userInfo) { |
310
|
|
|
if (array_key_exists('id', $userInfo) && array_key_exists('login', $userInfo) && array_key_exists('name', $userInfo) && is_numeric($userInfo['id'])) { |
311
|
|
|
|
312
|
|
|
// @todo Optimize with prehashed id's list |
313
|
|
View Code Duplication |
if (null === ($user = $this->userRepository->find($userInfo['id']))) { |
|
|
|
|
314
|
|
|
$user = new User((int) $userInfo['id']); |
315
|
|
|
$this->em->persist($user); |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
// Updating data |
319
|
|
|
$user |
320
|
|
|
->setLogin($userInfo['login']) |
321
|
|
|
->setName($userInfo['name']) |
322
|
|
|
; |
323
|
|
|
|
324
|
|
|
$resultUsers[] = $user; |
325
|
|
|
} else { |
326
|
|
|
throw new InvalidResponseException('Invalid API response. Mandatory fields do not exist.'); |
327
|
|
|
} |
328
|
|
|
} |
329
|
|
|
|
330
|
|
|
return $resultUsers; |
331
|
|
|
} |
332
|
|
|
|
333
|
|
|
/** |
334
|
|
|
* Creates URL of avatar with specified size by User object |
335
|
|
|
* |
336
|
|
|
* @param User $user |
337
|
|
|
* @param string $size |
338
|
|
|
* |
339
|
|
|
* @return string |
340
|
|
|
*/ |
341
|
|
|
public function getAvatarUrl(User $user, string $size): string |
342
|
|
|
{ |
343
|
|
|
return $this->getAvatarUrlByLogin($user->getLogin(), $size); |
344
|
3 |
|
} |
345
|
|
|
|
346
|
3 |
|
/** |
347
|
|
|
* Creates URL of avatar with specified size by login string |
348
|
|
|
* |
349
|
|
|
* @param string $login |
350
|
|
|
* @param string $size |
351
|
|
|
* |
352
|
|
|
* @return string |
353
|
|
|
*/ |
354
|
|
|
public function getAvatarUrlByLogin(string $login, string $size): string |
355
|
|
|
{ |
356
|
|
|
if (!in_array($size, [self::AVATAR_SIZE_SMALL, self::AVATAR_SIZE_MEDIUM, self::AVATAR_SIZE_LARGE], true)) { |
357
|
4 |
|
throw new \InvalidArgumentException('Avatar size must be one of restricted variants. See UserApi class AVATAR_SIZE_* constants.'); |
358
|
|
|
} |
359
|
4 |
|
|
360
|
|
|
return $this->avatarsBaseUrl.urlencode($login).'/'.$size; |
361
|
|
|
} |
362
|
|
|
} |
363
|
|
|
|
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:If that code throws an exception and the
EntityManager
is closed. Any other code which depends on the same instance of theEntityManager
during this request will fail.On the other hand, if you instead inject the
ManagerRegistry
, thegetManager()
method guarantees that you will always get a usable manager instance.