Total Complexity | 57 |
Total Lines | 569 |
Duplicated Lines | 0 % |
Changes | 10 | ||
Bugs | 0 | Features | 0 |
Complex classes like LiveStream often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use LiveStream, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
17 | class LiveStream |
||
18 | { |
||
19 | /** |
||
20 | * Live Stream API Base URL. |
||
21 | */ |
||
22 | const BASE_URL = 'https://livestreamapis.com/v3/'; |
||
23 | |||
24 | /** |
||
25 | * LiveStream API HTTP Codes. |
||
26 | */ |
||
27 | const ERROR_CODES = [ |
||
28 | 400 => 'Bad Request: Your request is not properly constructed.', |
||
29 | 401 => 'Unauthorized: Your API key is incorrect.', |
||
30 | 403 => 'Forbidden:', |
||
31 | 405 => 'Method Not Allowed: You tried to access a resource with an invalid method.', |
||
32 | 406 => 'Not Acceptable: You requested a format that is not JSON.', |
||
33 | 500 => 'Internal Server Error: We had a problem with our server. Please try again later.', |
||
34 | 503 => 'Service Unavailable: We are temporarily offline for maintanance. Please try again later.' |
||
35 | ]; |
||
36 | |||
37 | /** |
||
38 | * API key. |
||
39 | * |
||
40 | * @var string |
||
41 | */ |
||
42 | private $apiKey; |
||
43 | |||
44 | /** |
||
45 | * Client ID. |
||
46 | * |
||
47 | * @var int |
||
48 | */ |
||
49 | private $clientId; |
||
50 | |||
51 | /** |
||
52 | * Scope. |
||
53 | * |
||
54 | * @var string |
||
55 | */ |
||
56 | private $scope; |
||
57 | |||
58 | /** |
||
59 | * Secure access token. |
||
60 | * |
||
61 | * @var string |
||
62 | */ |
||
63 | private $token; |
||
64 | |||
65 | /** |
||
66 | * Secure access token timestamp. |
||
67 | * |
||
68 | * @var int |
||
69 | */ |
||
70 | private $tokenTimestamp; |
||
71 | |||
72 | /** |
||
73 | * Class Constructor. |
||
74 | * |
||
75 | * When only an API key is provided key auth method is used. When an API |
||
76 | * key, client ID, and scope are provided secure token auth is used. |
||
77 | * |
||
78 | * @param string $apiKey |
||
79 | * @param int|null $clientId |
||
80 | * @param string|null $scope |
||
81 | * Valid scopes are: all, readonly, playback |
||
82 | * |
||
83 | * @see https://livestream.com/developers/docs/api/#authentication |
||
84 | */ |
||
85 | public function __construct( |
||
86 | string $apiKey, |
||
87 | ?int $clientId = null, |
||
88 | ?string $scope = null |
||
89 | ) |
||
90 | { |
||
91 | $this->apiKey = $apiKey; |
||
92 | $this->clientId = $clientId; |
||
93 | $this->scope = $scope; |
||
94 | |||
95 | if ($this->clientId !== null && $this->scope !== null) { |
||
96 | $this->refreshToken(); |
||
97 | } |
||
98 | } |
||
99 | |||
100 | /** |
||
101 | * Get Linked LiveStream Accounts |
||
102 | * |
||
103 | * @return array Array of LiveStream Accounts |
||
104 | */ |
||
105 | public function getAccounts(): array |
||
106 | { |
||
107 | $response = $this->request('accounts'); |
||
108 | |||
109 | $accounts = []; |
||
110 | |||
111 | foreach (json_decode($response) as $account) { |
||
112 | $accounts[] = Account::fromObject($account); |
||
113 | } |
||
114 | |||
115 | return $accounts; |
||
116 | } |
||
117 | |||
118 | /** |
||
119 | * Get Specific Account |
||
120 | * |
||
121 | * @param integer $accountId |
||
122 | * @return Account|null |
||
123 | */ |
||
124 | public function getAccount(int $accountId): ?Account |
||
125 | { |
||
126 | $response = $this->request("accounts/$accountId"); |
||
127 | if ($response === null) return null; |
||
128 | |||
129 | return Account::fromObject(json_decode($response)); |
||
130 | } |
||
131 | |||
132 | /** |
||
133 | * Create New Event |
||
134 | * |
||
135 | * @param integer $accountId |
||
136 | * @param Event $event |
||
137 | * @return boolean |
||
138 | */ |
||
139 | public function createEvent(int $accountId, Event &$event): bool |
||
140 | { |
||
141 | $event->validate(); |
||
142 | |||
143 | $response = $this->request("accounts/$accountId/events", 'post', $event); |
||
144 | |||
145 | if ($response === null) return false; |
||
146 | |||
147 | $event = Event::fromObject(json_decode($response)); |
||
148 | |||
149 | return true; |
||
150 | } |
||
151 | |||
152 | /** |
||
153 | * Update Event. |
||
154 | * |
||
155 | * @param integer $accountId |
||
156 | * @param LiveStream\Resources\Event $event |
||
|
|||
157 | * @return boolean |
||
158 | */ |
||
159 | public function updateEvent(int $accountId, Event $event): bool |
||
160 | { |
||
161 | $event->validate(true); |
||
162 | |||
163 | $response = $this->request("accounts/$accountId/events/$event->id", 'put', $event); |
||
164 | |||
165 | if ($response === null) return false; |
||
166 | |||
167 | return true; |
||
168 | } |
||
169 | |||
170 | /** |
||
171 | * Delete Event |
||
172 | * |
||
173 | * @param integer $accountId |
||
174 | * @param integer $eventId |
||
175 | * |
||
176 | * @return boolean |
||
177 | */ |
||
178 | public function deleteEvent(int $accountId, int $eventId): ?Event |
||
185 | } |
||
186 | |||
187 | /** |
||
188 | * Update Event Logo. |
||
189 | * |
||
190 | * @param integer $accountId |
||
191 | * @param integer $eventId |
||
192 | * @param string $filePath |
||
193 | * @return boolean |
||
194 | */ |
||
195 | public function updateEventLogo(int $accountId, int $eventId, string $filePath): Event |
||
196 | { |
||
197 | if (!in_array(mime_content_type($filePath), [ |
||
198 | 'image/png', |
||
199 | 'image/jpg', |
||
200 | 'image/jpeg', |
||
201 | 'image/gif' |
||
202 | ])) { |
||
203 | throw new InValidResourceException('Logo', 'poster (MIME Type must be one of image/png, image/jpg, image/gif)'); |
||
204 | } |
||
205 | |||
206 | $response = $this->request("accounts/$accountId/events/$eventId/logo", 'put', null, null, [ |
||
207 | 'logo' => new CURLFile($filePath, mime_content_type($filePath), basename($filePath)) |
||
208 | ]); |
||
209 | |||
210 | if ($response == null) return null; |
||
211 | |||
212 | return Event::fromObject(json_decode($response)); |
||
213 | } |
||
214 | |||
215 | /** |
||
216 | * Gets Events by type. |
||
217 | * |
||
218 | * @param string $type |
||
219 | * Valid values: draft, past, private, or upcoming. |
||
220 | * @param int $accountId |
||
221 | * @param int $page |
||
222 | * @param int $maxItems |
||
223 | * @param string $order |
||
224 | * |
||
225 | * @return array |
||
226 | * |
||
227 | * @throws \LiveStream\Exceptions\LiveStreamException |
||
228 | */ |
||
229 | protected function getEvents( |
||
230 | string $type, |
||
231 | int $accountId, |
||
232 | int $page, |
||
233 | int $maxItems, |
||
234 | string $order |
||
235 | ) { |
||
236 | $events = []; |
||
237 | |||
238 | $response = $this->request("accounts/{$accountId}/{$type}_events", 'get', null, [ |
||
239 | 'page' => $page, |
||
240 | 'maxItems' => $maxItems, |
||
241 | 'order' => $order |
||
242 | ]); |
||
243 | |||
244 | if ($response === null) return $events; |
||
245 | |||
246 | foreach (json_decode($response)->data as $event) { |
||
247 | $events[] = Event::fromObject($event); |
||
248 | } |
||
249 | |||
250 | return $events; |
||
251 | } |
||
252 | |||
253 | /** |
||
254 | * Get Draft Events |
||
255 | * |
||
256 | * @param integer $accountId |
||
257 | * @param integer $page |
||
258 | * @param integer $maxItems |
||
259 | * @param string $order |
||
260 | * |
||
261 | * @return array |
||
262 | * |
||
263 | * @throws \LiveStream\Exceptions\LiveStreamException |
||
264 | * |
||
265 | * @see https://livestream.com/developers/docs/api/#get-draft-events |
||
266 | */ |
||
267 | public function getDraftEvents( |
||
274 | } |
||
275 | |||
276 | /** |
||
277 | * Get Past Events |
||
278 | * |
||
279 | * @param integer $accountId |
||
280 | * @param integer $page |
||
281 | * @param integer $maxItems |
||
282 | * @param string $order |
||
283 | * |
||
284 | * @return array |
||
285 | * |
||
286 | * @throws \LiveStream\Exceptions\LiveStreamException |
||
287 | * |
||
288 | * @see https://livestream.com/developers/docs/api/#get-past-events |
||
289 | */ |
||
290 | public function getPastEvents( |
||
291 | int $accountId, |
||
292 | int $page = 1, |
||
293 | int $maxItems = 10, |
||
294 | string $order = 'desc' |
||
295 | ): array { |
||
296 | return $this->getEvents('past', $accountId, $page, $maxItems, $order); |
||
297 | } |
||
298 | |||
299 | /** |
||
300 | * Get Private Events |
||
301 | * |
||
302 | * @param integer $accountId |
||
303 | * @param integer $page |
||
304 | * @param integer $maxItems |
||
305 | * @param string $order |
||
306 | * |
||
307 | * @return array |
||
308 | * |
||
309 | * @throws \LiveStream\Exceptions\LiveStreamException |
||
310 | * |
||
311 | * @see https://livestream.com/developers/docs/api/#get-private-events |
||
312 | */ |
||
313 | public function getPrivateEvents( |
||
314 | int $accountId, |
||
315 | int $page = 1, |
||
316 | int $maxItems = 10, |
||
317 | string $order = 'asc' |
||
318 | ): array { |
||
319 | return $this->getEvents('private', $accountId, $page, $maxItems, $order); |
||
320 | } |
||
321 | |||
322 | /** |
||
323 | * Get Upcoming Events |
||
324 | * |
||
325 | * @param integer $accountId |
||
326 | * @param integer $page |
||
327 | * @param integer $maxItems |
||
328 | * @param string $order |
||
329 | * |
||
330 | * @return array |
||
331 | * |
||
332 | * @throws \LiveStream\Exceptions\LiveStreamException |
||
333 | * |
||
334 | * @see https://livestream.com/developers/docs/api/#get-upcoming-events |
||
335 | */ |
||
336 | public function geUpcomingEvents( |
||
337 | int $accountId, |
||
338 | int $page = 1, |
||
339 | int $maxItems = 10, |
||
340 | string $order = 'asc' |
||
341 | ): array { |
||
342 | return $this->getEvents('upcoming', $accountId, $page, $maxItems, $order); |
||
343 | } |
||
344 | |||
345 | /** |
||
346 | * Get RTMP Key. |
||
347 | * |
||
348 | * @param integer $accountId |
||
349 | * @param integer $eventId |
||
350 | * |
||
351 | * @return \LiveStream\Resources\RTMPKey|null |
||
352 | */ |
||
353 | public function getRtmpKey( |
||
354 | int $accountId, |
||
355 | int $eventId, |
||
356 | bool $notifyFollowers = false, |
||
357 | bool $publishVideo = false, |
||
358 | bool $saveVideo = false |
||
359 | ): ?RTMPKey { |
||
360 | |||
361 | $response = $this->request("accounts/$accountId/events/$eventId/rtmp", 'get', null, [ |
||
362 | 'notifyFollowers' => $notifyFollowers, |
||
363 | 'publishVideo' => $publishVideo, |
||
364 | 'saveVideo' => $saveVideo |
||
365 | ]); |
||
366 | |||
367 | if ($response === null) return null; |
||
368 | |||
369 | return RTMPKey::fromObject(json_decode($response)); |
||
370 | } |
||
371 | |||
372 | /** |
||
373 | * Reset RTMPKey |
||
374 | * |
||
375 | * @param integer $accountId |
||
376 | * @param integer $eventId |
||
377 | * @return \LiveStream\Resources\RTMPKey|null |
||
378 | */ |
||
379 | public function resetRtmpKey(int $accountId, int $eventId): ?RTMPKey |
||
380 | { |
||
381 | $response = $this->request("accounts/$accountId/events/$eventId/rtmp", 'put'); |
||
382 | |||
383 | if ($response === null) return null; |
||
384 | |||
385 | return RTMPKey::fromObject(json_decode($response)); |
||
386 | } |
||
387 | |||
388 | /** |
||
389 | * Gets current live Video for an Event, if one exists. |
||
390 | * |
||
391 | * @param int $accountId |
||
392 | * @param int $eventId |
||
393 | * @param ?int $offsetPostId |
||
394 | * @param ?int $older |
||
395 | * @param ?int $newer |
||
396 | * |
||
397 | * @return \LiveStream\Resources\Video|null |
||
398 | * |
||
399 | * @see https://livestream.com/developers/docs/api/#get-videos |
||
400 | * |
||
401 | * @throws \LiveStream\Exceptions\LiveStreamException |
||
402 | */ |
||
403 | public function getLiveVideo( |
||
428 | } |
||
429 | |||
430 | /** |
||
431 | * Gets URL of a Video's HLS stream (m3u8) file with access token. |
||
432 | * |
||
433 | * Requires a secure access token. Generates the URL using the "playback" |
||
434 | * scope. |
||
435 | * |
||
436 | * @param \LiveStream\Resources\Video $video |
||
437 | * |
||
438 | * @return string|null |
||
439 | */ |
||
440 | public function getVideoHlsFileUrl(Video $video): ?string { |
||
441 | if (!$video->m3u8) return null; |
||
442 | |||
443 | $token = $this->generateToken('playback'); |
||
444 | $query = [ |
||
445 | 'clientId' => $this->clientId, |
||
446 | 'timestamp' => $token['timestamp'], |
||
447 | 'token' => $token['token'] |
||
448 | ]; |
||
449 | return $video->m3u8 . '?' . http_build_query($query); |
||
450 | } |
||
451 | |||
452 | /** |
||
453 | * Gets contents of a Video's HLS stream (m3u8) file. |
||
454 | * |
||
455 | * Requires a secure access token. |
||
456 | * |
||
457 | * @param \LiveStream\Resources\Video $video |
||
458 | * |
||
459 | * @return string|null |
||
460 | * @throws \LiveStream\Exceptions\LiveStreamException |
||
461 | */ |
||
462 | public function getVideoHlsFileContents(Video $video): ?string { |
||
463 | if (!$video->m3u8) return null; |
||
464 | |||
465 | $endpoint = substr($video->m3u8, strlen(self::BASE_URL)); |
||
466 | return $this->request($endpoint); |
||
467 | } |
||
468 | |||
469 | /** |
||
470 | * Refreshes a secure access token if invalid (5 minute life time). |
||
471 | * |
||
472 | * @see https://github.com/Livestream/livestream-api-samples/tree/master/php/secure-token-auth-sample |
||
473 | */ |
||
474 | private function refreshToken(): void |
||
475 | { |
||
476 | $now = round(microtime(true) * 1000); |
||
477 | if (!$this->tokenTimestamp || round(($now - $this->tokenTimestamp)/1000) > 300) { |
||
478 | $token = $this->generateToken(); |
||
479 | $this->tokenTimestamp = $token['timestamp']; |
||
480 | $this->token = $token['token']; |
||
481 | } |
||
482 | } |
||
483 | |||
484 | /** |
||
485 | * Generates a new token with optional scope override. |
||
486 | * |
||
487 | * @param string|null $scope |
||
488 | * |
||
489 | * @return array |
||
490 | */ |
||
491 | private function generateToken(?string $scope = null): array { |
||
492 | $scope = $scope ?? $this->scope; |
||
493 | $timestamp = round(microtime(true) * 1000); |
||
494 | $token = hash_hmac('md5', "{$this->apiKey}:{$scope}:{$timestamp}", $this->apiKey); |
||
495 | return [ |
||
496 | 'timestamp' => (int) $timestamp, |
||
497 | 'scope' => $scope, |
||
498 | 'token' => $token, |
||
499 | ]; |
||
500 | } |
||
501 | |||
502 | /** |
||
503 | * CURL Request |
||
504 | * |
||
505 | * @param string $endpoint |
||
506 | * @param string $verb |
||
507 | * @param \LiveStream\Interfaces\Resource|null $body |
||
508 | * @param array|null $query |
||
509 | * @param array|null $multipartFormData |
||
510 | * |
||
511 | * @return string|null |
||
512 | * |
||
513 | * @throws \LiveStream\Exceptions\LiveStreamException |
||
514 | */ |
||
515 | private function request( |
||
516 | string $endpoint, |
||
517 | string $verb = 'get', |
||
518 | ?Resource $body = null, |
||
519 | ?array $query = null, |
||
520 | ?array $multipartFormData = null |
||
521 | ): ?string { |
||
522 | $ch = curl_init(); |
||
523 | |||
524 | if (!$ch) throw new Exception("Could not initialize CURL."); |
||
525 | |||
526 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); |
||
527 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); |
||
528 | |||
529 | if ($this->token && $this->clientId && $this->tokenTimestamp) { |
||
530 | $this->refreshToken(); |
||
531 | $query['timestamp'] = $this->tokenTimestamp; |
||
532 | $query['clientId'] = $this->clientId; |
||
533 | $query['token'] = $this->token; |
||
534 | } |
||
535 | else { |
||
536 | curl_setopt($ch, CURLOPT_USERPWD, $this->apiKey . ':'); |
||
537 | } |
||
538 | |||
539 | curl_setopt( |
||
540 | $ch, |
||
541 | CURLOPT_URL, |
||
542 | $this->get_base_url() . $endpoint . ($query ? '?' . http_build_query($query) : '') |
||
543 | ); |
||
544 | |||
545 | if ($verb != 'get') { |
||
546 | if ($verb == 'post') curl_setopt($ch, CURLOPT_POST, true); |
||
547 | if ($verb == 'put') curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); |
||
548 | if ($verb == 'delete') curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE'); |
||
549 | if ($body) { |
||
550 | curl_setopt($ch, CURLOPT_HTTPHEADER, [ |
||
551 | 'Content-Type: ' . $body->getContentType() |
||
552 | ]); |
||
553 | curl_setopt($ch, CURLOPT_POSTFIELDS, $body->getResourceBody()); |
||
554 | } elseif ($multipartFormData) { |
||
555 | curl_setopt($ch, CURLOPT_HTTPHEADER, [ |
||
556 | 'Content-Type: multipart/form-data' |
||
557 | ]); |
||
558 | curl_setopt($ch, CURLOPT_POSTFIELDS, $multipartFormData); |
||
559 | } |
||
560 | } |
||
561 | |||
562 | $response = curl_exec($ch); |
||
563 | $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); |
||
564 | |||
565 | curl_close($ch); |
||
566 | |||
567 | if ($code == 200 || $code == 201) return $response; |
||
568 | |||
569 | if ($code == 404) return null; |
||
570 | |||
571 | if ($code <= 199) throw new Exception("A CURL erorr with code '$code', has occurred."); |
||
572 | |||
573 | if ($code == 403) throw new LiveStreamException(self::ERROR_CODES[$code] . ' ' . json_decode($response)->message); |
||
574 | |||
575 | throw new LiveStreamException(self::ERROR_CODES[$code]); |
||
576 | } |
||
577 | |||
578 | /** |
||
579 | * Get Base URL. |
||
580 | * |
||
581 | * @return string |
||
582 | */ |
||
583 | private function get_base_url(): string |
||
586 | } |
||
587 | } |
||
588 |