TwitterClient   A
last analyzed

Complexity

Total Complexity 31

Size/Duplication

Total Lines 384
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
wmc 31
lcom 1
cbo 8
dl 0
loc 384
rs 9.8
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A setClient() 0 4 1
A getHeader() 0 7 2
A setHeader() 0 4 1
A getSignature() 0 7 2
A setSignature() 0 10 1
A setResourceUrl() 0 4 1
A setHttpMethod() 0 4 1
A setTimestamp() 0 4 1
A setNonce() 0 4 1
A setVerifier() 0 4 1
A setPostFields() 0 7 1
A getAuthorizationHeader() 0 7 1
A prepareSignature() 0 21 4
A makeGetRequest() 0 20 3
A makePostRequest() 0 19 2
A handleException() 0 16 4
A preparePostOptions() 0 7 1
A get() 0 4 1
A post() 0 4 1
1
<?php
2
namespace Wheedle;
3
4
use \GuzzleHttp\Client;
5
use \GuzzleHttp\Exception\ClientException;
6
use \Snaggle\Client\Header\Header;
7
use \Snaggle\Client\Signatures\HmacSha1;
8
use \Snaggle\Client\Signatures\SignatureInterface;
9
use \Snaggle\Client\Credentials\AccessCredentials;
10
use \Snaggle\Client\Credentials\ConsumerCredentials;
11
use \Wheedle\Exceptions\UnauthorizedRequestException;
12
use \Wheedle\Exceptions\MissingResourceException;
13
use \Wheedle\Exceptions\RateLimitExceededException;
14
use \RuntimeException;
15
16
/**
17
 * A Twitter client that extends Guzzle or encapsulates the OAuth madness
18
 *
19
 * @author Matt Frost
20
 * @license http://opensource.org/licenses/MIT MIT
21
 * @package Wheedle
22
 */
23
class TwitterClient
24
{
25
    /**
26
     * HTTP Client capable of making HTTP Requests
27
     *
28
     * @var \GuzzleHttp\Client $client
29
     */
30
    private $client;
31
32
    /**
33
     * Header object that is used to generate the OAuth 1.0 header
34
      *
35
     * @var \Snaggle\Client\Header\Header $header
36
     */
37
    private $header;
38
39
    /**
40
     * A signature type used to generate the OAuth 1.0 signature
41
     *
42
     * @var \Snaggle\Client\Signatures\SignatureInterface $signature
43
     */
44
    private $signature;
45
46
    /**
47
     * A Snaggle\AccessCredentials instance with the appropriate key/secret
48
     *
49
     * @var \Snaggle\Client\Credentials\AccessCredentials
50
     */
51
    private $accessCredentials;
52
53
    /**
54
     * A Snaggle\ConsumerCredentials instance with the appropriate key/secret
55
     *
56
     * @var \Snaggle\Client\Credentials\ConsumerCredentials
57
     */
58
    private $consumerCredentials;
59
60
    /**
61
     * String representing the location of the resource
62
     *
63
     * @var string $resourceUrl
64
     */
65
    private $resourceUrl;
66
67
    /**
68
     * String representing the HTTP method with which to use the request
69
     *
70
     * @var string $httpMethod
71
     */
72
    private $httpMethod;
73
74
    /**
75
     * A timestamp for the request
76
     *
77
     * @var int $timestamp
78
     */
79
    private $timestamp = 0;
80
81
    /**
82
     * A nonce for the request
83
     *
84
     * @var string $nonce
85
     */
86
    private $nonce = null;
87
88
    /**
89
     * Verifier that is part of the temporary token exchange
90
     *
91
     * @var string $verifier
92
     */
93
    private $verifier = null;
94
95
    /**
96
     * Post requests require any form fields to be included for the signature, you can set them here
97
     *
98
     * @var Array $postFields
99
     */
100
    private $postFields = [];
101
102
    /**
103
     * Base Twitter API Endpoint
104
     *
105
     * @var const string $baseEndpoint
106
     */
107
    const TWITTER_BASE_ENDPOINT = 'https://api.twitter.com/1.1/';
108
109
    /**
110
     * @param AccessCredentials $accessCredentials
111
     * @param ConsumerCredentials $consumerCredentials
112
     */
113
    public function __construct(AccessCredentials $accessCredentials, ConsumerCredentials $consumerCredentials)
114
    {
115
        $this->accessCredentials = $accessCredentials;
116
        $this->consumerCredentials = $consumerCredentials;
117
        $this->client = new Client();
118
    }
119
120
    /**
121
     * Method set an instance of Guzzle HTTP client
122
     *
123
     * @param Client $client
124
     */
125
    public function setClient(Client $client)
126
    {
127
        $this->client = $client;
128
    }
129
130
    /**
131
     * Accessor method to retrieve a set header or create a new instance of header
132
     *
133
     * @return Header
134
     */
135
    public function getHeader()
136
    {
137
        if (!$this->header instanceof Header) {
138
            $this->header = new Header;
139
        }
140
        return $this->header;
141
    }
142
143
    /**
144
     * Access method to set an instance of header
145
     *
146
     * @param Header $header
147
     */
148
    public function setHeader(Header $header)
149
    {
150
        $this->header = $header;
151
    }
152
153
    /**
154
     * Accessor method to retrieve a set Signature or create a new instance
155
     *
156
     */
157
    public function getSignature()
158
    {
159
        if (!$this->signature instanceof HmacSha1) {
160
            $this->signature = new HmacSha1($this->consumerCredentials, $this->accessCredentials);
161
        }
162
        return $this->signature;
163
    }
164
165
    /**
166
     * Accessor method for setting a preconfigured signature which will set the other
167
     * properties from the data contained in the signature
168
     *
169
     * @param HmacSha1 $signature
170
     */
171
    public function setSignature(HmacSha1 $signature)
172
    {
173
        $this->signature = $signature;
174
        $this->resourceUrl = $signature->getResourceURL();
175
        $this->httpMethod = $signature->getHttpMethod();
176
        $this->nonce = $signature->getNonce();
177
        $this->timestamp = $signature->getTimestamp();
178
        $this->verifier = $signature->getVerifier();
179
        $this->postFields = $signature->getPostFields();
180
    }
181
182
    /**
183
     * Method to set the resource url
184
     *
185
     * @param string $url
186
     */
187
    public function setResourceUrl($url)
188
    {
189
        $this->resourceUrl = $url;
190
    }
191
192
    /**
193
     * Method to set the Http Method
194
     *
195
     * @param string $httpMethod
196
     */
197
    public function setHttpMethod($httpMethod)
198
    {
199
        $this->httpMethod = strtoupper($httpMethod);
200
    }
201
202
    /**
203
     * Method to set a timestamp
204
     *
205
     * @param int $timestamp
206
     */
207
    public function setTimestamp($timestamp)
208
    {
209
        $this->timestamp = $timestamp;
210
    }
211
212
    /**
213
     * Method to set a nonce
214
     *
215
     * @param string $nonce
216
     */
217
    public function setNonce($nonce)
218
    {
219
        $this->nonce = $nonce;
220
    }
221
222
    /**
223
     * Method to set the verifier for token requests
224
     *
225
     * @param string $verifier
226
     */
227
    public function setVerifier($verifier)
228
    {
229
        $this->verifier = $verifier;
230
    }
231
232
    /**
233
     * Method for setting the post fields
234
     *
235
     * @param Array $postFields
236
     */
237
    public function setPostFields(Array $postFields)
238
    {
239
        array_walk($postFields, function ($value, $key) use (&$postFields) {
240
            $postFields[$key] = rawurlencode($value);
241
        });
242
        $this->postFields = $postFields;
243
    }
244
245
    /**
246
     * Method to build the Authorization Header
247
     *
248
     * @return string
249
     */
250
    public function getAuthorizationHeader()
251
    {
252
        $header = $this->getHeader();
253
        $signature = $this->prepareSignature();
254
        $header->setSignature($signature);
255
        return $header->createAuthorizationHeader();
256
    }
257
258
    /**
259
     * Prepare the signature for use in the Authorization header
260
     *
261
     * @return Signature
262
     */
263
    private function prepareSignature()
264
    {
265
        $signature = $this->getSignature();
266
        $signature->setResourceURL($this->resourceUrl);
267
        $signature->setHttpMethod($this->httpMethod);
268
        $signature->setPostFields($this->postFields);
269
270
        if ($this->timestamp !== 0) {
271
            $signature->setTimestamp($this->timestamp);
272
        }
273
274
        if ($this->nonce !== null) {
275
            $signature->setNonce($this->nonce);
276
        }
277
278
        if ($this->verifier !== null) {
279
            $signature->setVerifier($this->verifier);
280
        }
281
282
        return $signature;
283
    }
284
285
    /**
286
     * Method to execute a GET request
287
     *
288
     * @param string $endpoint - endpoint to hit
289
     * @param Array $options parameters for the query string
290
     * @return string response from the Twitter endpoint
291
     * @throws UnauthorizedRequestException
292
     * @throws RateLimitExceededException
293
     * @throws RuntimeException
294
     */
295
    public function makeGetRequest($endpoint, $options)
296
    {
297
        $queryString = (empty($options)) ? '' : '?';
298
        $queryString .= http_build_query($options);
299
        $endpoint = self::TWITTER_BASE_ENDPOINT . $endpoint . $queryString;
300
        $this->setHttpMethod('GET');
301
        $this->setResourceUrl($endpoint);
302
        try {
303
            $response = $this->client->get($endpoint, [
304
                'headers' => [
305
                    'Authorization' => $this->getAuthorizationHeader()
306
                ]
307
            ]);
308
            return $response->getBody();
309
        } catch (\GuzzleHttp\Exception\ClientException $e) {
310
            // protected method - used to throw exception based on status code
311
            $exception = $this->handleException($e);
312
            throw $exception;
313
        }
314
    }
315
316
    /**
317
     * Method to execute a POST request
318
     *
319
     * @param string $endpoint - end point to hit
320
     * @param Array $options - parameters/post body
321
     * @return string response from Twitter Endpoint
322
     * @throws UnauthorizedRequestException
323
     * @throws RateLimitExceededException
324
     * @throws RuntimeException
325
     */
326
    public function makePostRequest($endpoint, $options)
327
    {
328
        $this->setHttpMethod('POST');
329
        $this->setResourceUrl(self::TWITTER_BASE_ENDPOINT . $endpoint);
330
        $this->setPostFields($this->preparePostOptions($options));
331
        try {
332
            $response = $this->client->post($endpoint, [
333
                'headers' => [
334
                    'Authorization' => $this->getAuthorizationHeader()
335
                ],
336
                'body' => $options
337
            ]);
338
            return $response->getBody();
339
        } catch (\GuzzleHttp\Exception\ClientException $e) {
340
            // protected method - used to throw exception based on status code
341
            $exception = $this->handleException($e);
342
            throw $exception;
343
        }
344
    }
345
346
    /**
347
     * Method to handle the selection of the correct exception to throw
348
     *
349
     * @param \GuzzleHttp\Exception\ClientException $e - Guzzle Client Exception
350
     * @return mixed - Exception to throw
351
     */
352
    protected function handleException(ClientException $e)
353
    {
354
        switch ($e->getCode()) {
355
            case 401:
356
                return new UnauthorizedRequestException($e->getMessage());
357
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
358
            case 404:
359
                return new MissingResourceException($e->getMessage());
360
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
361
            case 429:
362
                return new RateLimitExceededException($e->getMessage());
363
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
364
            default:
365
                return new RuntimeException($e->getMessage());
366
        }
367
    }
368
369
    /**
370
     * Method to prepare parameters for the base string by rawurlencoding them
371
     *
372
     * @param Array $options parameters or post body
373
     * @return Array array of options rawurlencoded
374
     */
375
    protected function preparePostOptions(Array $options)
376
    {
377
        array_walk($options, function ($value, $key) use (&$options) {
378
            $options[$key] = rawurlencode($value);
379
        });
380
        return $options;
381
    }
382
383
    /**
384
     * Wrapper method for makeGetRequest
385
     *
386
     * @param string $endpoint end point to hit
387
     * @param Array $options parameters
388
     * @return string response from Twitter Endpoint
389
     */
390
    public function get($endpoint, $options = [])
391
    {
392
        return $this->makeGetRequest($endpoint, $options);
393
    }
394
395
    /**
396
     * Wrapper method for makePostRequest
397
     *
398
     * @param string $endpoint endpoint to hit
399
     * @param Array $options parameters/post body
400
     * @return string response from endpoint
401
     */
402
    public function post($endpoint, $options = [])
403
    {
404
        return $this->makePostRequest($endpoint, $options);
405
    }
406
}
407