TwitterOAuth::request()   B
last analyzed

Complexity

Conditions 10
Paths 80

Size

Total Lines 66
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 46
dl 0
loc 66
rs 7.3115
c 0
b 0
f 0
cc 10
nc 80
nop 4

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * The most popular PHP library for use with the Twitter OAuth REST API.
4
 *
5
 * @license MIT
6
 */
7
8
namespace App\Http\Controllers\Common\Twitter;
9
10
use App\Http\Controllers\Common\Twitter\Util\JsonDecoder;
11
12
/**
13
 * TwitterOAuth class for interacting with the Twitter API.
14
 *
15
 * @author Abraham Williams <[email protected]>
16
 */
17
class TwitterOAuth extends Config
18
{
19
    const API_VERSION = '1.1';
20
    const API_HOST = 'https://api.twitter.com';
21
    const UPLOAD_HOST = 'https://upload.twitter.com';
22
    const UPLOAD_CHUNK = 40960; // 1024 * 40
23
24
    /** @var Response details about the result of the last request */
25
    private $response;
26
    /** @var string|null Application bearer token */
27
    private $bearer;
28
    /** @var Consumer Twitter application details */
29
    private $consumer;
30
    /** @var Token|null User access token details */
31
    private $token;
32
    /** @var HmacSha1 OAuth 1 signature type used by Twitter */
33
    private $signatureMethod;
34
35
    /**
36
     * Constructor.
37
     *
38
     * @param string      $consumerKey      The Application Consumer Key
39
     * @param string      $consumerSecret   The Application Consumer Secret
40
     * @param string|null $oauthToken       The Client Token (optional)
41
     * @param string|null $oauthTokenSecret The Client Token Secret (optional)
42
     */
43
    public function __construct($consumerKey, $consumerSecret, $oauthToken = null, $oauthTokenSecret = null)
44
    {
45
        $this->resetLastResponse();
46
        $this->signatureMethod = new HmacSha1();
47
        $this->consumer = new Consumer($consumerKey, $consumerSecret);
48
        if (! empty($oauthToken) && ! empty($oauthTokenSecret)) {
49
            $this->token = new Token($oauthToken, $oauthTokenSecret);
50
        }
51
        if (empty($oauthToken) && ! empty($oauthTokenSecret)) {
52
            $this->bearer = $oauthTokenSecret;
53
        }
54
    }
55
56
    /**
57
     * @param string $oauthToken
58
     * @param string $oauthTokenSecret
59
     */
60
    public function setOauthToken($oauthToken, $oauthTokenSecret)
61
    {
62
        $this->token = new Token($oauthToken, $oauthTokenSecret);
63
    }
64
65
    /**
66
     * @return string|null
67
     */
68
    public function getLastApiPath()
69
    {
70
        return $this->response->getApiPath();
71
    }
72
73
    /**
74
     * @return int
75
     */
76
    public function getLastHttpCode()
77
    {
78
        return $this->response->getHttpCode();
79
    }
80
81
    /**
82
     * @return array
83
     */
84
    public function getLastXHeaders()
85
    {
86
        return $this->response->getXHeaders();
87
    }
88
89
    /**
90
     * @return array|object|null
91
     */
92
    public function getLastBody()
93
    {
94
        return $this->response->getBody();
95
    }
96
97
    /**
98
     * Resets the last response cache.
99
     */
100
    public function resetLastResponse()
101
    {
102
        $this->response = new Response();
103
    }
104
105
    /**
106
     * Make URLs for user browser navigation.
107
     *
108
     * @param string $path
109
     * @param array  $parameters
110
     *
111
     * @return string
112
     */
113
    public function url($path, array $parameters)
114
    {
115
        $this->resetLastResponse();
116
        $this->response->setApiPath($path);
117
        $query = http_build_query($parameters);
118
119
        return sprintf('%s/%s?%s', self::API_HOST, $path, $query);
120
    }
121
122
    /**
123
     * Make /oauth/* requests to the API.
124
     *
125
     * @param string $path
126
     * @param array  $parameters
127
     *
128
     * @throws TwitterOAuthException
129
     *
130
     * @return array
131
     */
132
    public function oauth($path, array $parameters = [])
133
    {
134
        $response = [];
135
        $this->resetLastResponse();
136
        $this->response->setApiPath($path);
137
        $url = sprintf('%s/%s', self::API_HOST, $path);
138
        $result = $this->oAuthRequest($url, 'POST', $parameters);
139
140
        if ($this->getLastHttpCode() != 200) {
141
            throw new TwitterOAuthException($result);
142
        }
143
144
        parse_str($result, $response);
145
        $this->response->setBody($response);
146
147
        return $response;
148
    }
149
150
    /**
151
     * Make /oauth2/* requests to the API.
152
     *
153
     * @param string $path
154
     * @param array  $parameters
155
     *
156
     * @return array|object
157
     */
158
    public function oauth2($path, array $parameters = [])
159
    {
160
        $method = 'POST';
161
        $this->resetLastResponse();
162
        $this->response->setApiPath($path);
163
        $url = sprintf('%s/%s', self::API_HOST, $path);
164
        $request = Request::fromConsumerAndToken($this->consumer, $this->token, $method, $url, $parameters);
165
        $authorization = 'Authorization: Basic '.$this->encodeAppAuthorization($this->consumer);
166
        $result = $this->request($request->getNormalizedHttpUrl(), $method, $authorization, $parameters);
167
        $response = JsonDecoder::decode($result, $this->decodeJsonAsArray);
168
        $this->response->setBody($response);
169
170
        return $response;
171
    }
172
173
    /**
174
     * Make GET requests to the API.
175
     *
176
     * @param string $path
177
     * @param array  $parameters
178
     *
179
     * @return array|object
180
     */
181
    public function get($path, array $parameters = [])
182
    {
183
        return $this->http('GET', self::API_HOST, $path, $parameters);
184
    }
185
186
    /**
187
     * Make POST requests to the API.
188
     *
189
     * @param string $path
190
     * @param array  $parameters
191
     *
192
     * @return array|object
193
     */
194
    public function post($path, array $parameters = [])
195
    {
196
        return $this->http('POST', self::API_HOST, $path, $parameters);
197
    }
198
199
    /**
200
     * Make DELETE requests to the API.
201
     *
202
     * @param string $path
203
     * @param array  $parameters
204
     *
205
     * @return array|object
206
     */
207
    public function delete($path, array $parameters = [])
208
    {
209
        return $this->http('DELETE', self::API_HOST, $path, $parameters);
210
    }
211
212
    /**
213
     * Make PUT requests to the API.
214
     *
215
     * @param string $path
216
     * @param array  $parameters
217
     *
218
     * @return array|object
219
     */
220
    public function put($path, array $parameters = [])
221
    {
222
        return $this->http('PUT', self::API_HOST, $path, $parameters);
223
    }
224
225
    /**
226
     * Upload media to upload.twitter.com.
227
     *
228
     * @param string $path
229
     * @param array  $parameters
230
     * @param bool   $chunked
231
     *
232
     * @return array|object
233
     */
234
    public function upload($path, array $parameters = [], $chunked = false)
235
    {
236
        if ($chunked) {
237
            return $this->uploadMediaChunked($path, $parameters);
238
        } else {
239
            return $this->uploadMediaNotChunked($path, $parameters);
240
        }
241
    }
242
243
    /**
244
     * Private method to upload media (not chunked) to upload.twitter.com.
245
     *
246
     * @param string $path
247
     * @param array  $parameters
248
     *
249
     * @return array|object
250
     */
251
    private function uploadMediaNotChunked($path, $parameters)
252
    {
253
        $file = file_get_contents($parameters['media']);
254
        $base = base64_encode($file);
255
        $parameters['media'] = $base;
256
257
        return $this->http('POST', self::UPLOAD_HOST, $path, $parameters);
258
    }
259
260
    /**
261
     * Private method to upload media (chunked) to upload.twitter.com.
262
     *
263
     * @param string $path
264
     * @param array  $parameters
265
     *
266
     * @return array|object
267
     */
268
    private function uploadMediaChunked($path, $parameters)
269
    {
270
        // Init
271
        $init = $this->http('POST', self::UPLOAD_HOST, $path, [
272
            'command'     => 'INIT',
273
            'media_type'  => $parameters['media_type'],
274
            'total_bytes' => filesize($parameters['media']),
275
        ]);
276
        // Append
277
        $segment_index = 0;
278
        $media = fopen($parameters['media'], 'rb');
279
        while (! feof($media)) {
280
            $this->http('POST', self::UPLOAD_HOST, 'media/upload', [
281
                'command'       => 'APPEND',
282
                'media_id'      => $init->media_id_string,
283
                'segment_index' => $segment_index++,
284
                'media_data'    => base64_encode(fread($media, self::UPLOAD_CHUNK)),
285
            ]);
286
        }
287
        fclose($media);
288
        // Finalize
289
        $finalize = $this->http('POST', self::UPLOAD_HOST, 'media/upload', [
290
            'command'  => 'FINALIZE',
291
            'media_id' => $init->media_id_string,
292
        ]);
293
294
        return $finalize;
295
    }
296
297
    /**
298
     * @param string $method
299
     * @param string $host
300
     * @param string $path
301
     * @param array  $parameters
302
     *
303
     * @return array|object
304
     */
305
    private function http($method, $host, $path, array $parameters)
306
    {
307
        $this->resetLastResponse();
308
        $url = sprintf('%s/%s/%s.json', $host, self::API_VERSION, $path);
309
        $this->response->setApiPath($path);
310
        $result = $this->oAuthRequest($url, $method, $parameters);
311
        $response = JsonDecoder::decode($result, $this->decodeJsonAsArray);
312
        $this->response->setBody($response);
313
314
        return $response;
315
    }
316
317
    /**
318
     * Format and sign an OAuth / API request.
319
     *
320
     * @param string $url
321
     * @param string $method
322
     * @param array  $parameters
323
     *
324
     * @throws TwitterOAuthException
325
     *
326
     * @return string
327
     */
328
    private function oAuthRequest($url, $method, array $parameters)
329
    {
330
        $request = Request::fromConsumerAndToken($this->consumer, $this->token, $method, $url, $parameters);
331
        if (array_key_exists('oauth_callback', $parameters)) {
332
            // Twitter doesn't like oauth_callback as a parameter.
333
            unset($parameters['oauth_callback']);
334
        }
335
        if ($this->bearer === null) {
336
            $request->signRequest($this->signatureMethod, $this->consumer, $this->token);
337
            $authorization = $request->toHeader();
338
        } else {
339
            $authorization = 'Authorization: Bearer '.$this->bearer;
340
        }
341
342
        return $this->request($request->getNormalizedHttpUrl(), $method, $authorization, $parameters);
343
    }
344
345
    /**
346
     * Make an HTTP request.
347
     *
348
     * @param string $url
349
     * @param string $method
350
     * @param string $authorization
351
     * @param array  $postfields
352
     *
353
     * @throws TwitterOAuthException
354
     *
355
     * @return string
356
     */
357
    private function request($url, $method, $authorization, $postfields)
358
    {
359
        /* Curl settings */
360
        $options = [
361
            // CURLOPT_VERBOSE => true,
362
            CURLOPT_CAINFO         => __DIR__.DIRECTORY_SEPARATOR.'cacert.pem',
363
            CURLOPT_CONNECTTIMEOUT => $this->connectionTimeout,
364
            CURLOPT_HEADER         => true,
365
            CURLOPT_HTTPHEADER     => ['Accept: application/json', $authorization, 'Expect:'],
366
            CURLOPT_RETURNTRANSFER => true,
367
            CURLOPT_SSL_VERIFYHOST => 2,
368
            CURLOPT_SSL_VERIFYPEER => true,
369
            CURLOPT_TIMEOUT        => $this->timeout,
370
            CURLOPT_URL            => $url,
371
            CURLOPT_USERAGENT      => $this->userAgent,
372
        ];
373
374
        if ($this->gzipEncoding) {
375
            $options[CURLOPT_ENCODING] = 'gzip';
376
        }
377
378
        if (! empty($this->proxy)) {
379
            $options[CURLOPT_PROXY] = $this->proxy['CURLOPT_PROXY'];
380
            $options[CURLOPT_PROXYUSERPWD] = $this->proxy['CURLOPT_PROXYUSERPWD'];
381
            $options[CURLOPT_PROXYPORT] = $this->proxy['CURLOPT_PROXYPORT'];
382
            $options[CURLOPT_PROXYAUTH] = CURLAUTH_BASIC;
383
            $options[CURLOPT_PROXYTYPE] = CURLPROXY_HTTP;
384
        }
385
386
        switch ($method) {
387
            case 'GET':
388
                break;
389
            case 'POST':
390
                $options[CURLOPT_POST] = true;
391
                $options[CURLOPT_POSTFIELDS] = Util::buildHttpQuery($postfields);
392
                break;
393
            case 'DELETE':
394
                $options[CURLOPT_CUSTOMREQUEST] = 'DELETE';
395
                break;
396
            case 'PUT':
397
                $options[CURLOPT_CUSTOMREQUEST] = 'PUT';
398
                break;
399
        }
400
401
        if (in_array($method, ['GET', 'PUT', 'DELETE']) && ! empty($postfields)) {
402
            $options[CURLOPT_URL] .= '?'.Util::buildHttpQuery($postfields);
403
        }
404
405
        $curlHandle = curl_init();
406
        curl_setopt_array($curlHandle, $options);
407
        $response = curl_exec($curlHandle);
408
409
        // Throw exceptions on cURL errors.
410
        if (curl_errno($curlHandle) > 0) {
411
            throw new TwitterOAuthException(curl_error($curlHandle), curl_errno($curlHandle));
412
        }
413
414
        $this->response->setHttpCode(curl_getinfo($curlHandle, CURLINFO_HTTP_CODE));
415
        $parts = explode("\r\n\r\n", $response);
416
        $responseBody = array_pop($parts);
417
        $responseHeader = array_pop($parts);
418
        $this->response->setHeaders($this->parseHeaders($responseHeader));
419
420
        curl_close($curlHandle);
421
422
        return $responseBody;
423
    }
424
425
    /**
426
     * Get the header info to store.
427
     *
428
     * @param string $header
429
     *
430
     * @return array
431
     */
432
    private function parseHeaders($header)
433
    {
434
        $headers = [];
435
        foreach (explode("\r\n", $header) as $line) {
436
            if (strpos($line, ':') !== false) {
437
                [$key, $value] = explode(': ', $line);
438
                $key = str_replace('-', '_', strtolower($key));
439
                $headers[$key] = trim($value);
440
            }
441
        }
442
443
        return $headers;
444
    }
445
446
    /**
447
     * Encode application authorization header with base64.
448
     *
449
     * @param Consumer $consumer
450
     *
451
     * @return string
452
     */
453
    private function encodeAppAuthorization($consumer)
454
    {
455
        // TODO: key and secret should be rfc 1738 encoded
456
        $key = $consumer->key;
457
        $secret = $consumer->secret;
458
459
        return base64_encode($key.':'.$secret);
460
    }
461
}
462