AbstractApi::request()   F
last analyzed

Complexity

Conditions 25
Paths 7560

Size

Total Lines 179
Code Lines 93

Duplication

Lines 29
Ratio 16.2 %

Importance

Changes 0
Metric Value
dl 29
loc 179
rs 2
c 0
b 0
f 0
cc 25
eloc 93
nc 7560
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
namespace FlexyProject\GitHub;
3
4
use Exception;
5
use FlexyProject\Curl\Client as CurlClient;
6
use Symfony\Component\HttpFoundation\Request;
7
8
/**
9
 * Class AbstractApi
10
 *
11
 * @package FlexyProject\GitHub
12
 */
13
abstract class AbstractApi
14
{
15
16
    /** API version */
17
    const API_VERSION = 'v3';
18
19
    /** API constants */
20
    const API_URL        = 'https://api.github.com';
21
    const API_UPLOADS    = 'https://uploads.github.com';
22
    const API_RAW_URL    = 'https://raw.github.com';
23
    const CONTENT_TYPE   = 'application/json';
24
    const DEFAULT_ACCEPT = 'application/vnd.github.' . self::API_VERSION . '+json';
25
    const USER_AGENT     = 'FlexyProject-GitHubAPI';
26
27
    /** Archive constants */
28
    const ARCHIVE_TARBALL = 'tarball';
29
    const ARCHIVE_ZIPBALL = 'zipball';
30
31
    /** Authentication constants */
32
    const OAUTH_AUTH             = 0;
33
    const OAUTH2_HEADER_AUTH     = 1;
34
    const OAUTH2_PARAMETERS_AUTH = 2;
35
36
    /** Branch constants */
37
    const BRANCH_MASTER  = 'master';
38
    const BRANCH_DEVELOP = 'develop';
39
40
    /** Direction constants */
41
    const DIRECTION_ASC  = 'asc';
42
    const DIRECTION_DESC = 'desc';
43
44
    /** Environment constants */
45
    const ENVIRONMENT_PRODUCTION = 'production';
46
    const ENVIRONMENT_STAGING    = 'staging';
47
    const ENVIRONMENT_QA         = 'qa';
48
49
    /** Events constants */
50
    const EVENTS_PULL         = 'pull';
51
    const EVENTS_PULL_REQUEST = 'pull_request';
52
    const EVENTS_PUSH         = 'push';
53
54
    /** Filter constants */
55
    const FILTER_ALL        = 'all';
56
    const FILTER_ASSIGNED   = 'assigned';
57
    const FILTER_CREATED    = 'created';
58
    const FILTER_MENTIONED  = 'mentioned';
59
    const FILTER_SUBSCRIBED = 'subscribed';
60
61
    /** Media types constants */
62
    const MEDIA_TYPE_JSON = 'json';
63
    const MEDIA_TYPE_RAW  = 'raw';
64
    const MEDIA_TYPE_FULL = 'full';
65
    const MEDIA_TYPE_TEXT = 'text';
66
67
    /** Modes constants */
68
    const MODE_MARKDOWN = 'markdown';
69
    const MODE_GFM      = 'gfm';
70
71
    /** Permissions constants */
72
    const PERMISSION_ADMIN = 'admin';
73
    const PERMISSION_PULL  = 'pull';
74
    const PERMISSION_PUSH  = 'push';
75
76
    /** Sort constants */
77
    const SORT_COMPLETENESS = 'completeness';
78
    const SORT_CREATED      = 'created';
79
    const SORT_DUE_DATE     = 'due_date';
80
    const SORT_FULL_NAME    = 'full_name';
81
    const SORT_NEWEST       = 'newest';
82
    const SORT_OLDEST       = 'oldest';
83
    const SORT_PUSHED       = 'pushed';
84
    const SORT_STARGAZERS   = 'stargazers';
85
    const SORT_UPDATED      = 'updated';
86
87
    /** State constants */
88
    const STATE_ACTIVE  = 'active';
89
    const STATE_ALL     = 'all';
90
    const STATE_CLOSED  = 'closed';
91
    const STATE_ERROR   = 'error';
92
    const STATE_FAILURE = 'failure';
93
    const STATE_OPEN    = 'open';
94
    const STATE_PENDING = 'pending';
95
    const STATE_SUCCESS = 'success';
96
97
    /** Task constants */
98
    const TASK_DEPLOY            = 'deploy';
99
    const TASK_DEPLOY_MIGRATIONS = 'deploy:migrations';
100
101
    /** Type constants */
102
    const TYPE_ALL        = 'all';
103
    const TYPE_COMMENTS   = 'comments';
104
    const TYPE_GISTS      = 'gists';
105
    const TYPE_HOOKS      = 'hooks';
106
    const TYPE_ISSUES     = 'issues';
107
    const TYPE_MEMBER     = 'member';
108
    const TYPE_MILESTONES = 'milestones';
109
    const TYPE_ORGS       = 'orgs';
110
    const TYPE_OWNER      = 'owner';
111
    const TYPE_PAGES      = 'pages';
112
    const TYPE_PUBLIC     = 'public';
113
    const TYPE_PULLS      = 'pulls';
114
    const TYPE_PRIVATE    = 'private';
115
    const TYPE_REPOS      = 'repos';
116
    const TYPE_USERS      = 'users';
117
118
    /** Properties */
119
    protected $accept         = self::DEFAULT_ACCEPT;
120
    protected $apiUrl         = self::API_URL;
121
    protected $authentication = self::OAUTH_AUTH;
122
    protected $clientId;
123
    protected $clientSecret;
124
    protected $contentType    = self::CONTENT_TYPE;
125
    protected $failure;
126
    protected $headers        = [];
127
    protected $httpAuth       = ['username' => '', 'password' => ''];
128
    protected $pagination;
129
    protected $request;
130
    protected $success;
131
    protected $timeout        = 240;
132
    protected $token          = '';
133
134
    /**
135
     * Constructor
136
     */
137
    public function __construct()
138
    {
139
        $this->request = Request::createFromGlobals();
140
    }
141
142
    /**
143
     * Get request
144
     *
145
     * @return Request
146
     */
147
    public function getRequest(): Request
148
    {
149
        return $this->request;
150
    }
151
152
    /**
153
     * Get accept
154
     *
155
     * @return mixed
156
     */
157
    public function getAccept()
158
    {
159
        return $this->accept;
160
    }
161
162
    /**
163
     * Set accept
164
     *
165
     * @param array|string $accept
166
     *
167
     * @return AbstractApi
168
     */
169
    public function setAccept($accept): AbstractApi
170
    {
171
        $this->accept = $accept;
172
173
        return $this;
174
    }
175
176
    /**
177
     * Get authentication
178
     *
179
     * @return int
180
     */
181
    public function getAuthentication(): int
182
    {
183
        return $this->authentication;
184
    }
185
186
    /**
187
     * Set authentication
188
     *
189
     * @param int $authentication
190
     *
191
     * @return AbstractApi
192
     */
193
    public function setAuthentication(int $authentication): AbstractApi
194
    {
195
        $this->authentication = $authentication;
196
197
        return $this;
198
    }
199
200
    /**
201
     * Get apiUrl
202
     *
203
     * @return string
204
     */
205
    public function getApiUrl(): string
206
    {
207
        return $this->apiUrl;
208
    }
209
210
    /**
211
     * Set apiUrl
212
     *
213
     * @param mixed $apiUrl
214
     *
215
     * @return AbstractApi
216
     */
217
    public function setApiUrl($apiUrl): AbstractApi
218
    {
219
        $this->apiUrl = $apiUrl;
220
221
        return $this;
222
    }
223
224
    /**
225
     * Get clientId
226
     *
227
     * @return null|int
228
     */
229
    public function getClientId()
230
    {
231
        return $this->clientId;
232
    }
233
234
    /**
235
     * Set clientId
236
     *
237
     * @param mixed $clientId
238
     *
239
     * @return AbstractApi
240
     */
241
    public function setClientId($clientId): AbstractApi
242
    {
243
        $this->clientId = $clientId;
244
245
        return $this;
246
    }
247
248
    /**
249
     * Get clientSecret
250
     *
251
     * @return null|string
252
     */
253
    public function getClientSecret()
254
    {
255
        return $this->clientSecret;
256
    }
257
258
    /**
259
     * Set clientSecret
260
     *
261
     * @param mixed $clientSecret
262
     *
263
     * @return AbstractApi
264
     */
265
    public function setClientSecret($clientSecret): AbstractApi
266
    {
267
        $this->clientSecret = $clientSecret;
268
269
        return $this;
270
    }
271
272
    /**
273
     * Get httpAuth
274
     *
275
     * @return array
276
     */
277
    public function getHttpAuth(): array
278
    {
279
        return $this->httpAuth;
280
    }
281
282
    /**
283
     * Set httpAuth
284
     *
285
     * @param string $username
286
     * @param string $password
287
     *
288
     * @return AbstractApi
289
     */
290
    public function setHttpAuth(string $username, string $password = ''): AbstractApi
291
    {
292
        $this->httpAuth['username'] = $username;
293
        $this->httpAuth['password'] = $password;
294
295
        return $this;
296
    }
297
298
    /**
299
     * Get token
300
     *
301
     * @return string
302
     */
303
    public function getToken(): string
304
    {
305
        return $this->token;
306
    }
307
308
    /**
309
     * Set token
310
     *
311
     * @param string $token
312
     * @param int    $authentication
313
     *
314
     * @return AbstractApi
315
     */
316
    public function setToken(string $token, int $authentication = self::OAUTH_AUTH): AbstractApi
317
    {
318
        $this->token = $token;
319
        $this->setAuthentication($authentication);
320
321
        return $this;
322
    }
323
324
    /**
325
     * Get timeout
326
     *
327
     * @return int
328
     */
329
    public function getTimeout(): int
330
    {
331
        return $this->timeout;
332
    }
333
334
    /**
335
     * Set timeout
336
     *
337
     * @param int $timeout
338
     *
339
     * @return AbstractApi
340
     */
341
    public function setTimeout(int $timeout): AbstractApi
342
    {
343
        $this->timeout = $timeout;
344
345
        return $this;
346
    }
347
348
    /**
349
     * Get contentType
350
     *
351
     * @return string
352
     */
353
    public function getContentType(): string
354
    {
355
        return $this->contentType;
356
    }
357
358
    /**
359
     * Set contentType
360
     *
361
     * @param string $contentType
362
     *
363
     * @return AbstractApi
364
     */
365
    public function setContentType(string $contentType): AbstractApi
366
    {
367
        $this->contentType = $contentType;
368
369
        return $this;
370
    }
371
372
    /**
373
     * Get headers
374
     *
375
     * @return array
376
     */
377
    public function getHeaders(): array
378
    {
379
        return $this->headers;
380
    }
381
382
    /**
383
     * Get pagination
384
     *
385
     * @return Pagination|null
386
     */
387
    public function getPagination()
388
    {
389
        return $this->pagination;
390
    }
391
392
    /**
393
     * Set pagination
394
     *
395
     * @param Pagination $pagination
396
     *
397
     * @return AbstractApi
398
     */
399
    public function setPagination(Pagination $pagination): AbstractApi
400
    {
401
        $this->pagination = $pagination;
402
403
        return $this;
404
    }
405
406
    /**
407
     * Curl request
408
     *
409
     * @param string      $url
410
     * @param string      $method
411
     * @param array       $postFields
412
     * @param null|string $apiUrl
413
     *
414
     * @return array
415
     */
416
    public function request(string $url, string $method = Request::METHOD_GET, array $postFields = [],
417
                            string $apiUrl = null): array
418
    {
419
        /** Building url */
420
        if (null === $apiUrl) {
421
            $apiUrl = $this->getApiUrl();
422
        }
423
        $url   = $apiUrl . $url;
424
        $query = [];
425
426
        /**
427
         * OAuth2 Key/Secret authentication
428
         *
429
         * @see https://developer.github.com/v3/#oauth2-keysecret
430
         */
431
        if (null !== $this->getClientId() && null !== $this->getClientSecret()) {
432
            $query['client_id']     = $this->getClientId();
433
            $query['client_secret'] = $this->getClientSecret();
434
        } /**
435
         * Basic authentication via OAuth2 Token (sent as a parameter)
436
         *
437
         * @see https://developer.github.com/v3/#oauth2-token-sent-as-a-parameter
438
         */ else if ($this->getAuthentication() === self::OAUTH2_PARAMETERS_AUTH) {
439
            $query['access_token'] = $this->getToken();
440
        }
441
442
        /**
443
         * Pagination
444
         * Requests that return multiple items will be paginated to 30 items by default.
445
         * You can specify further pages with the ?page parameter.
446
         * For some resources, you can also set a custom page size up to 100 with the ?per_page parameter.
447
         * Note that for technical reasons not all endpoints respect the ?per_page parameter,
448
         *
449
         * @see https://developer.github.com/v3/#pagination
450
         */
451
        if (null !== $this->getPagination()) {
452
            if (null !== $this->getPagination()->getPage()) {
453
                $query['page'] = $this->getPagination()->getPage();
454
            }
455
            if (null !== $this->getPagination()->getLimit()) {
456
                $query['per_page'] = $this->getPagination()->getLimit();
457
            }
458
        }
459
460
        /**
461
         * Add URL-encoded query string parameters
462
         */
463
        if (!empty($query)) {
464
            $url .= (strstr($url, '?') !== false ? '&' : '?');
465
            $url .= http_build_query($query);
466
        }
467
468
        /** Call curl */
469
        $curl = new CurlClient();
470
        $curl->setOption([
471
            CURLOPT_USERAGENT      => self::USER_AGENT,
472
            CURLOPT_TIMEOUT        => $this->getTimeout(),
473
            CURLOPT_HEADER         => false, // Use $client->getHeaders() to get full header
474
            CURLOPT_FOLLOWLOCATION => true,
475
            CURLOPT_HTTPHEADER     => [
476
                'Accept: ' . $this->getAccept(),
477
                'Content-Type: ' . $this->getContentType()
478
            ],
479
            CURLOPT_URL            => $url
480
        ]);
481
482
        /**
483
         * Basic authentication via username and Password
484
         *
485
         * @see https://developer.github.com/v3/auth/#via-username-and-password
486
         */
487
        if (!empty($this->getHttpAuth())) {
488
            if (!isset($this->getHttpAuth()['password']) || empty($this->getHttpAuth()['password'])) {
489
                $curl->setOption([
490
                    CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
491
                    CURLOPT_USERPWD  => $this->getHttpAuth()['username']
492
                ]);
493
            } else {
494
                $curl->setOption([
495
                    CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
496
                    CURLOPT_USERPWD  => sprintf('%s:%s', $this->getHttpAuth()['username'],
497
                        $this->getHttpAuth()['password'])
498
                ]);
499
            }
500
        }
501
502
        if (!empty($this->getToken()) && $this->getAuthentication() !== self::OAUTH2_PARAMETERS_AUTH) {
503
            /**
504
             * Basic authentication via OAuth token
505
             *
506
             * @see https://developer.github.com/v3/auth/#via-oauth-tokens
507
             **/
508
            if ($this->getAuthentication() === self::OAUTH_AUTH) {
509
                $curl->setOption([
510
                    CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
511
                    CURLOPT_USERPWD  => sprintf('%s:x-oauth-basic', $this->getToken())
512
                ]);
513
            } /**
514
             * Basic authentication via OAuth2 Token (sent in a header)
515
             *
516
             * @see https://developer.github.com/v3/#oauth2-token-sent-in-a-header
517
             */ else if ($this->getAuthentication() === self::OAUTH2_HEADER_AUTH) {
518
                $curl->setOption([
519
                    CURLOPT_HTTPAUTH   => CURLAUTH_BASIC,
520
                    CURLOPT_HTTPHEADER => [sprintf('Authorization: token %s', $this->getToken())]
521
                ]);
522
            }
523
        }
524
525
        /** Methods */
526
        switch ($method) {
527
            /** @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.7 */
528
            case Request::METHOD_DELETE:
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
529
                /** @see http://tools.ietf.org/html/rfc5789 */
530 View Code Duplication
            case Request::METHOD_PATCH:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
531
                $curl->setOption([
532
                    CURLOPT_CUSTOMREQUEST => $method,
533
                    CURLOPT_POST          => true,
534
                    CURLOPT_POSTFIELDS    => json_encode(array_filter($postFields))
535
                ]);
536
                break;
537
538
            /** @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3 */
539
            case Request::METHOD_GET:
540
                $curl->setOption(CURLOPT_HTTPGET, true);
541
                break;
542
543
            /** @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4 */
544
            case Request::METHOD_HEAD:
545
                $curl->setOption([
546
                    CURLOPT_CUSTOMREQUEST => $method,
547
                    CURLOPT_NOBODY        => true
548
                ]);
549
                break;
550
551
            /** @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5 */
552 View Code Duplication
            case Request::METHOD_POST:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
553
                $curl->setOption([
554
                    CURLOPT_POST       => true,
555
                    CURLOPT_POSTFIELDS => json_encode(array_filter($postFields))
556
                ]);
557
                break;
558
559
            /** @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.6 */
560
            case Request::METHOD_PUT:
561
                $curl->setOption([
562
                    CURLOPT_CUSTOMREQUEST => $method,
563
                    CURLOPT_PUT           => true,
564
                    CURLOPT_HTTPHEADER    => [
565
                        'X-HTTP-Method-Override: PUT',
566
                        'Content-type: application/x-www-form-urlencoded'
567
                    ]
568
                ]);
569
                break;
570
571
            default:
572
                break;
573
        }
574
575 View Code Duplication
        $curl->success(function (CurlClient $instance) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
576
            $this->headers = $instance->getHeaders();
577
            $this->success = $instance->getResponse();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $this->success is correct as $instance->getResponse() (which targets FlexyProject\Curl\Client::getResponse()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
578
            $data          = json_decode($this->success, true);
579
            if (JSON_ERROR_NONE === json_last_error()) {
580
                $this->success = $data;
581
            }
582
        });
583 View Code Duplication
        $curl->error(function (CurlClient $instance) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
584
            $this->headers = $instance->getHeaders();
585
            $this->failure = $instance->getResponse();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $this->failure is correct as $instance->getResponse() (which targets FlexyProject\Curl\Client::getResponse()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
586
            $data          = json_decode($this->failure, true);
587
            if (JSON_ERROR_NONE === json_last_error()) {
588
                $this->failure = $data;
589
            }
590
        });
591
        $curl->perform();
592
593
        return (array)($this->success ?? $this->failure);
594
    }
595
596
    /**
597
     * Return a formatted string. Modified version of sprintf using colon(:)
598
     *
599
     * @param string $string
600
     * @param array  $params
601
     *
602
     * @return String
603
     * @throws Exception
604
     */
605
    public function sprintf(string $string, ...$params): string
606
    {
607
        preg_match_all('/\:([A-Za-z0-9_]+)/', $string, $matches);
608
        $matches = $matches[1];
609
610
        if (count($matches)) {
611
            $tokens   = [];
612
            $replaces = [];
613
614
            foreach ($matches as $key => $value) {
615
                if (count($params) > 1 || !is_array($params[0])) {
616
                    if (!array_key_exists($key, $params)) {
617
                        throw new Exception('Too few arguments, missing argument: ' . $key);
618
                    }
619
                    $replaces[] = $params[$key];
620
                } else {
621
                    if (!array_key_exists($value, $params[0])) {
622
                        throw new Exception('Missing array argument: ' . $key);
623
                    }
624
                    $replaces[] = $params[0][$value];
625
                }
626
                $tokens[] = ':' . $value;
627
            }
628
629
            $string = str_replace($tokens, $replaces, $string);
630
        }
631
632
        return $string;
633
    }
634
}