|
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) |
|
|
|
|
|
|
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) |
|
|
|
|
|
|
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) |
|
|
|
|
|
|
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) |
|
|
|
|
|
|
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) |
|
|
|
|
|
|
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) |
|
|
|
|
|
|
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) |
|
|
|
|
|
|
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']))) { |
|
|
|
|
|
|
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']))) { |
|
|
|
|
|
|
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
|
|
|
|
The
EntityManagermight 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
EntityManageris closed. Any other code which depends on the same instance of theEntityManagerduring 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.