Passed
Pull Request — master (#12)
by kenny
02:28
created

Publisher::renewAuthToken()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 23
rs 9.552
c 0
b 0
f 0
1
<?php
2
3
namespace One;
4
5
use Guzzle\Http\Client;
6
use Guzzle\Http\Exception\BadResponseException;
7
use Guzzle\Http\Exception\ClientErrorResponseException;
8
use Guzzle\Http\Message\RequestInterface;
9
use One\Model\Article;
10
use One\Model\Model;
11
use Psr\Log\LoggerAwareInterface;
12
use Psr\Log\LoggerInterface;
13
14
/**
15
 * Publisher class
16
 * main class to be used that interfacing to the API
17
 */
18
class Publisher implements LoggerAwareInterface {
19
	const DEFAULT_MAX_ATTEMPT = 4;
20
21
	const REST_SERVER = 'https://dev.one.co.id';
22
	const AUTHENTICATION = '/oauth/token';
23
	const ARTICLE_CHECK_ENDPOINT = '/api/article';
24
	const ARTICLE_ENDPOINT = '/api/publisher/article';
25
26
	/**
27
	 * attachment url destination
28
	 *
29
	 * @var array
30
	 */
31
	private $attachmentUrl;
32
33
	/**
34
	 * Logger variable, if set log activity to this obejct each time sending request and receiving response
35
	 *
36
	 * @var \Psr\Log\LoggerInterface
37
	 */
38
	private $logger = null;
39
40
	/**
41
	 * credentials props
42
	 *
43
	 * @var string $clientId
44
	 * @var string $clientSecret
45
	 */
46
	private $clientId;
47
	private $clientSecret;
48
49
	/**
50
	 * Oauth access token response
51
	 *
52
	 * @var string $accessToken
53
	 */
54
	private $accessToken = null;
55
56
	/**
57
	 * publisher custom options
58
	 *
59
	 * @var \One\Collection $options
60
	 */
61
	private $options;
62
63
	/**
64
	 * http transaction Client
65
	 *
66
	 * @var \Guzzle\Http\Client
67
	 */
68
	private $httpClient;
69
70
	/**
71
	 * constructor
72
	 *
73
	 * @param string $clientId
74
	 * @param string $clientSecret
75
	 * @param array $options
76
	 */
77
	public function __construct($clientId, $clientSecret, $options = array()) {
78
		$this->clientId = $clientId;
79
		$this->clientSecret = $clientSecret;
80
81
		$this->assessOptions($options);
82
83
		$this->attachmentUrl = array(
84
			Article::ATTACHMENT_FIELD_GALLERY => self::ARTICLE_ENDPOINT . '/{article_id}/gallery',
85
			Article::ATTACHMENT_FIELD_PAGE => self::ARTICLE_ENDPOINT . '/{article_id}/page',
86
			Article::ATTACHMENT_FIELD_PHOTO => self::ARTICLE_ENDPOINT . '/{article_id}/photo',
87
			Article::ATTACHMENT_FIELD_VIDEO => self::ARTICLE_ENDPOINT . '/{article_id}/video',
88
		);
89
	}
90
91
	/**
92
	 * recycleToken from callback. If use external token storage could leveraged on this
93
	 *
94
	 * @param \Closure $tokenProducer
95
	 * @return self
96
	 */
97
	public function recycleToken(\Closure $tokenProducer) {
98
		return $this->setAuthorizationHeader($tokenProducer());
99
	}
100
101
	/**
102
	 * assessing and custom option
103
	 *
104
	 * @param array $options
105
	 * @return void
106
	 */
107
	private function assessOptions($options) {
108
		$defaultOptions = array(
109
			'rest_server' => self::REST_SERVER,
110
			'auth_url' => self::AUTHENTICATION,
111
			'max_attempt' => self::DEFAULT_MAX_ATTEMPT,
112
			'default_headers' => array(
113
				"Accept" => "application/json",
114
			),
115
		);
116
117
		$this->options = new Collection(
118
			array_merge(
119
				$defaultOptions,
120
				$options
121
			)
122
		);
123
124
		if (isset($options['access_token'])) {
125
			$this->setAuthorizationHeader($options['access_token']);
126
		}
127
128
		$this->httpClient = new Client(
129
			$this->options->get('rest_server')
130
		);
131
	}
132
133
	/**
134
	 * one gate menu for request creation.
135
	 *
136
	 * @param string $method
137
	 * @param string $path
138
	 * @param \One\Collection|array $header
139
	 * @param \One\Collection|array $body
140
	 * @param array $options
141
	 * @return string
142
	 */
143
	private function requestGate($method, $path, $header = array(), $body = array(), $options = array()) {
144
		if (empty($this->accessToken)) {
145
			$this->renewAuthToken();
146
		}
147
148
		return (string) $this->sendRequest(
149
			$this->httpClient->createRequest(
150
				$method,
151
				$path,
152
				array_merge(
153
					$this->options->get('default_headers'),
154
					$header
155
				),
156
				$body,
0 ignored issues
show
Bug introduced by
It seems like $body defined by parameter $body on line 143 can also be of type object<One\Collection>; however, Guzzle\Http\Client::createRequest() does only seem to accept string|resource|array|ob...tityBodyInterface>|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
157
				$options
158
			)
159
		);
160
	}
161
162
	/**
163
	 * actually send request created here, separated for easier attempt count and handling exception
164
	 *
165
	 * @param \Guzzle\Http\Message\RequestInterface $request
166
	 * @param integer $attempt
167
	 * @return \Guzzle\Http\EntityBodyInterface|string|null
168
	 * @throws \Exception
169
	 * @throws \Guzzle\Http\Exception\ClientErrorResponseException
170
	 * @throws \Guzzle\Http\Exception\BadResponseException
171
	 */
172
	private function sendRequest(RequestInterface $request, $attempt = 0) {
173
		if ($attempt >= $this->options->get('max_attempt')) {
174
			throw new \Exception("MAX attempt reached for " . $request->getUrl() . " with payload " . (string) $request);
175
		}
176
177
		try {
178
			$response = $request->send();
179
			if ($response->getStatusCode() == 200) {
180
				return $response->getBody();
181
			}
182
			if ($response->getStatusCode() == 429) {
183
				$this->renewAuthToken();
184
			}
185
186
			return $this->sendRequest($request, $attempt++);
187
		} catch (ClientErrorResponseException $err) {
188
			if ($err->getResponse()->getStatusCode() == 429) {
189
				$this->renewAuthToken();
190
				return $this->sendRequest($err->getRequest(), $attempt++);
191
			}
192
193
			throw $err;
194
		} catch (\Exception $err) {
195
			throw $err;
196
		}
197
	}
198
199
	/**
200
	 * renewing access_token
201
	 *
202
	 * @return self
203
	 * @throws \Exception
204
	 */
205
	private function renewAuthToken() {
206
		$token = (string) $this->sendRequest(
207
			$this->httpClient->post(
208
				self::AUTHENTICATION,
209
				$this->options->get('default_headers'),
210
				array(
211
					"grant_type" => "client_credentials",
212
					"client_id" => $this->clientId,
213
					"client_secret" => $this->clientSecret,
214
				)
215
			)
216
		);
217
218
		$token = json_decode($token, true);
219
220
		if (empty($token)) {
221
			throw new \Exception("Access token request return empty response");
222
		}
223
224
		return $this->setAuthorizationHeader(
225
			$token['access_token']
226
		);
227
	}
228
229
	/**
230
	 * set header for OAuth 2.0
231
	 *
232
	 * @param string $accessToken
233
	 * @return self
234
	 */
235
	private function setAuthorizationHeader($accessToken) {
236
		$this->accessToken = $accessToken;
237
238
		$this->options->set(
239
			'default_headers',
240
			array_merge(
241
				$this->options->get('default_headers'),
242
				array(
243
					"Authorization" => "Bearer " . $accessToken,
244
				)
245
			)
246
		);
247
248
		return $this;
249
	}
250
251
	/**
252
	 * get Attachment Submission url Endpoint at rest API
253
	 *
254
	 * @param string $idArticle
255
	 * @param string $field
256
	 * @return string
257
	 */
258
	private function getAttachmentEndPoint($idArticle, $field) {
259
		return $this->replaceEndPointId(
260
			$idArticle,
261
			$this->attachmentUrl[$field]
262
		);
263
	}
264
265
	/**
266
	 * get article endpoint for deleting api
267
	 *
268
	 * @param string $identifier
269
	 * @return string
270
	 */
271
	private function getArticleWithIdEndPoint($identifier) {
272
		return self::ARTICLE_ENDPOINT . "/$identifier";
273
	}
274
275
	/**
276
	 * function that actually replace article_id inside endpoint pattern
277
	 *
278
	 * @param string $identifier
279
	 * @param string $url
280
	 * @return string
281
	 */
282
	private function replaceEndPointId($identifier, $url) {
283
		return str_replace(
284
			'{article_id}',
285
			$identifier,
286
			$url
287
		);
288
	}
289
290
	/**
291
	 * normalizing payload. not yet implemented totally, currently just bypass a toArray() function from collection.
292
	 *
293
	 * @param \One\Collection $collection
294
	 * @return array
295
	 */
296
	private function normalizePayload(Collection $collection) {
297
		return $collection->toArray();
298
	}
299
300
	/**
301
	 * submitting article here, return new Object cloned from original
302
	 *
303
	 * @param \One\Model\Article $article
304
	 * @return \One\Model\Article
305
	 */
306
	public function submitArticle(Article $article) {
307
		$responseArticle = $this->post(
308
			self::ARTICLE_ENDPOINT,
309
			$this->normalizePayload(
310
				$article->getCollection()
311
			)
312
		);
313
314
		$responseArticle = json_decode($responseArticle, true);
315
		$article->setId($responseArticle['data']['id']);
316
317
		foreach (Article::getPossibleAttachment() as $field) {
318
			if ($article->hasAttachment($field)) {
319
				foreach ($article->getAttachmentByField($field) as $attachment) {
320
					$this->submitAttachment(
321
						$article->getId(),
322
						$attachment,
323
						$field
324
					);
325
				}
326
			}
327
		}
328
329
		return $article;
330
	}
331
332
	/**
333
	 * submit each attachment of an article here
334
	 *
335
	 * @param string $idArticle
336
	 * @param \One\Model\Model $attachment
337
	 * @param string $field
338
	 * @return array
339
	 */
340
	public function submitAttachment($idArticle, Model $attachment, $field) {
341
		return json_decode(
342
			$this->post(
343
				$this->getAttachmentEndPoint($idArticle, $field),
344
				$this->normalizePayload(
345
					$attachment->getCollection()
346
				)
347
			),
348
			true
349
		);
350
	}
351
352
	/**
353
	 * get article from rest API
354
	 *
355
	 * @param string $idArticle
356
	 * @return string json
357
	 */
358
	public function getArticle($idArticle) {
359
		return $this->get(
360
			self::ARTICLE_CHECK_ENDPOINT . "/$idArticle"
361
		);
362
	}
363
364
	/**
365
	 * get list article by publisher
366
	 *
367
	 * @return string json
368
	 */
369
	public function listArticle() {
370
		return $this->get(
371
			self::ARTICLE_ENDPOINT
372
		);
373
	}
374
375
	/**
376
	 * delete article based on id
377
	 *
378
	 * @param string $idArticle
379
	 * @return string
380
	 */
381
	public function deleteArticle($idArticle) {
382
		$articleOnRest = $this->getArticle($idArticle);
383
384
		if (!empty($articleOnRest)) {
385
			$articleOnRest = json_decode($articleOnRest, true);
386
387
			if (isset($articleOnRest['data'])) {
388
				foreach (Article::getDeleteableAttachment() as $field) {
389
					if (isset($articleOnRest['data'][$field])) {
390
						foreach ($articleOnRest['data'][$field] as $attachment) {
391
							if (isset($attachment[$field . '_order'])) {
392
								$this->deleteAttachment($idArticle, $field, $attachment[$field . '_order']);
393
							}
394
						}
395
					}
396
				}
397
			}
398
399
			return $this->delete(
400
				$this->getArticleWithIdEndPoint($idArticle)
401
			);
402
		}
403
	}
404
405
	/**
406
	 * delete attachment of an article
407
	 *
408
	 * @param string $idArticle
409
	 * @param string $field
410
	 * @param string $order
411
	 * @return string
412
	 */
413
	public function deleteAttachment($idArticle, $field, $order) {
414
		return $this->delete(
415
			$this->getAttachmentEndPoint($idArticle, $field) . "/$order"
416
		);
417
	}
418
419
	/**
420
	 * get proxy
421
	 *
422
	 * @param string $path
423
	 * @param \One\Collection|array $header
424
	 * @param array $options
425
	 * @return string
426
	 */
427 View Code Duplication
	final public function get($path, $header = array(), $options = array()) {
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...
428
		if (isset($this->logger) && !is_null($this->logger)) {
429
			$this->logger->info("Hi, from post, path is " . $path);
430
		}
431
		return $this->requestGate(
432
			'GET',
433
			$path,
434
			$header,
435
			array(),
436
			$options
437
		);
438
	}
439
440
	/**
441
	 * post proxy
442
	 *
443
	 * @param string $path
444
	 * @param \One\Collection|array $body
445
	 * @param \One\Collection|array $header
446
	 * @param array $options
447
	 * @return string
448
	 */
449 View Code Duplication
	final public function post($path, $body, $header = array(), $options = array()) {
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...
450
451
		if (isset($this->logger) && !is_null($this->logger)) {
452
			$this->logger->info("Hi, from post " . $path);
453
		}
454
		return $this->requestGate(
455
			'POST',
456
			$path,
457
			$header,
458
			$body,
459
			$options
460
		);
461
	}
462
463
	/**
464
	 * delete proxy
465
	 *
466
	 * @param string $path
467
	 * @param \One\Collection|array $body
468
	 * @param \One\Collection|array $header
469
	 * @param array $options
470
	 * @return string
471
	 */
472
	final public function delete($path, $body = array(), $header = array(), $options = array()) {
473
		return $this->requestGate(
474
			'DELETE',
475
			$path,
476
			$header,
477
			$body,
478
			$options
479
		);
480
	}
481
482
	/**
483
	 * @inheritDoc
484
	 */
485
	public function setLogger(LoggerInterface $logger) {
486
		$this->logger = $logger;
487
	}
488
489
}
490