Corundum   B
last analyzed

Complexity

Total Complexity 41

Size/Duplication

Total Lines 507
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 41
dl 0
loc 507
ccs 0
cts 175
cp 0
rs 8.2769
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A authorize() 0 21 2
A value() 0 8 2
A getResults() 0 3 1
A multipart() 0 20 3
A __construct() 0 8 1
A apiSend() 0 9 2
A setTokens() 0 3 1
B uploadSlice() 0 27 2
A getBody() 0 4 1
B upload() 0 24 3
A getTokens() 0 3 1
B apiRequest() 0 55 6
A refreshToken() 0 10 1
A getCode() 0 8 2
A fake() 0 8 2
B tokenUpdate() 0 23 4
A getToken() 0 10 2
A verify() 0 8 1
A update() 0 23 2
B delete() 0 25 2

How to fix   Complexity   

Complex Class

Complex classes like Corundum 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 Corundum, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Bavix\SDK;
4
5
use Bavix\Exceptions\Invalid;
6
use Bavix\Helpers\Arr;
7
use Bavix\Helpers\File;
8
use Bavix\Helpers\JSON;
9
use Bavix\Helpers\Str;
10
use Bavix\Slice\Slice;
11
use Carbon\Carbon;
12
use GuzzleHttp\Client;
13
use Psr\Http\Message\ResponseInterface;
14
15
class Corundum
16
{
17
18
    /**
19
     * @var array
20
     */
21
    protected $tokens = [];
22
23
    /**
24
     * @var string
25
     */
26
    protected $urlToken;
27
28
    /**
29
     * @var string
30
     */
31
    protected $basic;
32
33
    /**
34
     * @var Slice
35
     */
36
    protected $slice;
37
38
    /**
39
     * @var Slice
40
     */
41
    protected $fake;
42
43
    /**
44
     * @var ResponseInterface
45
     */
46
    protected $response;
47
48
    /**
49
     * @var Slice
50
     */
51
    protected $results;
52
53
    /**
54
     * Corundum constructor.
55
     *
56
     * @param Slice $slice
57
     */
58
    public function __construct(Slice $slice)
59
    {
60
        $clientId = $slice->getRequired('app.client_id');
61
        $secret   = $slice->getRequired('app.client_secret');
62
63
        $this->slice    = $slice;
64
        $this->basic    = \base64_encode($clientId . ':' . $secret);
0 ignored issues
show
Bug introduced by
Are you sure $clientId of type array|mixed can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

64
        $this->basic    = \base64_encode(/** @scrutinizer ignore-type */ $clientId . ':' . $secret);
Loading history...
Bug introduced by
Are you sure $secret of type array|mixed can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

64
        $this->basic    = \base64_encode($clientId . ':' . /** @scrutinizer ignore-type */ $secret);
Loading history...
65
        $this->urlToken = $slice->getRequired('app.url.token');
0 ignored issues
show
Documentation Bug introduced by
It seems like $slice->getRequired('app.url.token') can also be of type array. However, the property $urlToken is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
66
    }
67
68
    /**
69
     * @return array
70
     */
71
    public function getTokens(): array
72
    {
73
        return $this->tokens;
74
    }
75
76
    /**
77
     * @param array $tokens
78
     */
79
    public function setTokens(array $tokens)
80
    {
81
        $this->tokens = $tokens;
82
    }
83
84
    /**
85
     * @return Slice
86
     */
87
    protected function fake(): Slice
88
    {
89
        if (!$this->fake)
90
        {
91
            $this->fake = new Slice([]);
92
        }
93
94
        return $this->fake;
95
    }
96
97
    /**
98
     * @return string
99
     *
100
     * @throws \RuntimeException
101
     */
102
    public function getBody(): string
103
    {
104
        return $this->response->getBody()
105
            ->getContents();
106
    }
107
108
    /**
109
     * @return Slice|null
110
     */
111
    public function getResults()
112
    {
113
        return $this->results;
114
    }
115
116
    /**
117
     * @return int|null
118
     */
119
    public function getCode()
120
    {
121
        if ($this->response)
122
        {
123
            return $this->response->getStatusCode();
124
        }
125
126
        return null;
127
    }
128
129
    /**
130
     * @param mixed $value
131
     *
132
     * @return mixed
133
     */
134
    protected function value($value)
135
    {
136
        if (Str::first($value) === '@')
137
        {
138
            return fopen(Str::withoutFirst($value), 'rb');
139
        }
140
141
        return $value;
142
    }
143
144
    /**
145
     * @param array $params
146
     *
147
     * @return array
148
     */
149
    protected function multipart(array $params): array
150
    {
151
        $results = [];
152
153
        foreach ($params as $key => $value)
154
        {
155
            $content = [
156
                'name'     => $key,
157
                'contents' => $this->value($value)
158
            ];
159
160
            if (\is_resource($content['contents']))
161
            {
162
                $content['filename'] = basename($value);
163
            }
164
165
            $results[] = $content;
166
        }
167
168
        return $results;
169
    }
170
171
    /**
172
     * @param Slice $options
173
     *
174
     * @return Slice|null
175
     *
176
     * @throws Invalid
177
     */
178
    protected function apiRequest(Slice $options)
179
    {
180
        $allow404 = $options->getData('allow404', false);
181
        $type     = $options->getData('token_type', 'Basic');
182
        $method   = $options->getData('method', 'POST');
183
        $token    = $options->getData('access_token', $this->basic);
184
        $url      = $options->getData('url', $this->urlToken);
185
        $params   = $options->getData('params', []);
186
        $headers  = $options->getData('headers', []);
187
188
        // headers
189
        $headers['Authorization'] = $type . ' ' . $token;
0 ignored issues
show
Bug introduced by
Are you sure $token of type string|array|mixed can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

189
        $headers['Authorization'] = $type . ' ' . /** @scrutinizer ignore-type */ $token;
Loading history...
Bug introduced by
Are you sure $type of type string|array|mixed can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

189
        $headers['Authorization'] = /** @scrutinizer ignore-type */ $type . ' ' . $token;
Loading history...
190
191
        $client = new Client([
192
            'debug'       => $this->slice->getData('debug', false),
193
            'http_errors' => false
194
        ]);
195
196
        $data = [
197
            'headers'   => $headers,
198
            'multipart' => $this->multipart($params),
199
        ];
200
201
        $this->response = $client->request(
202
            Str::upp($method),
0 ignored issues
show
Bug introduced by
It seems like $method can also be of type array; however, parameter $string of Bavix\Helpers\Str::upp() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

202
            Str::upp(/** @scrutinizer ignore-type */ $method),
Loading history...
203
            $url,
204
            $data
205
        );
206
207
        $this->results = null;
208
209
        $response = JSON::decode(
210
            $this->getBody()
211
        );
212
213
        if (JSON::errorNone())
214
        {
215
            $this->results = new Slice($response);
216
        }
217
218
        $code = $this->getCode();
219
220
        if ($allow404 && $code === 404)
221
        {
222
            return $this->getResults();
223
        }
224
225
        if ($code > 199 && $code < 300)
226
        {
227
            return $this->getResults();
228
        }
229
230
        throw new Invalid(
231
            'Error: ' . $this->response->getReasonPhrase(),
232
            $code
233
        );
234
    }
235
236
    /**
237
     * @param string $user
238
     * @param array  $params
239
     *
240
     * @return Slice
241
     *
242
     * @throws Invalid
243
     */
244
    protected function authorize(string $user, array $params = []): Slice
245
    {
246
        $grantType = $this->slice->getData('grant_type', 'password');
247
        $userData  = $this->slice->getSlice('users.' . $user);
248
        $defaults  = [
249
            'username'   => $userData->getRequired('username'),
250
            'password'   => $userData->getRequired('password'),
251
            'grant_type' => $grantType,
252
        ];
253
254
        $response = $this->apiRequest(new Slice([
255
            'params' => Arr::merge($defaults, $params)
256
        ]));
257
258
        if ($response)
259
        {
260
            $response->expires = Carbon::now()
261
                ->addSeconds($response->expires_in);
0 ignored issues
show
Bug introduced by
It seems like $response->expires_in can also be of type array; however, parameter $value of Carbon\Carbon::addSeconds() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

261
                ->addSeconds(/** @scrutinizer ignore-type */ $response->expires_in);
Loading history...
262
        }
263
264
        return $response;
265
    }
266
267
    /**
268
     * @param string $user
269
     * @param string $scope
270
     *
271
     * @return Slice
272
     *
273
     * @throws Invalid
274
     */
275
    protected function getToken(string $user, string $scope = ''): Slice
276
    {
277
        if (!isset($this->tokens[$user]))
278
        {
279
            $this->tokens[$user] = $this->authorize($user, [
280
                'scope' => $scope
281
            ]);
282
        }
283
284
        return $this->tokens[$user];
285
    }
286
287
    /**
288
     * @param string $user
289
     *
290
     * @return Slice
291
     *
292
     * @throws Invalid
293
     */
294
    protected function refreshToken(string $user): Slice
295
    {
296
        /**
297
         * @var $token Slice
298
         */
299
        $token = $this->tokens[$user];
300
301
        return $this->tokens[$user] = $this->authorize($user, [
302
            'grant_type'    => 'refresh_token',
303
            'refresh_token' => $token->getRequired('refresh_token'),
304
        ]);
305
    }
306
307
    /**
308
     * @param string $user
309
     * @param string $file
310
     * @param Slice  $options
311
     *
312
     * @return Slice
313
     *
314
     * @throws Invalid
315
     */
316
    public function upload(string $user, string $file, Slice $options = null): Slice
317
    {
318
        if (!File::isFile($file))
319
        {
320
            throw new \Bavix\Exceptions\NotFound\Path('File not found!');
321
        }
322
323
        if (!$options)
324
        {
325
            $options = $this->fake();
326
        }
327
328
        $token = $this->getToken(
329
            $user,
330
            $options->getData('scope', '')
0 ignored issues
show
Bug introduced by
It seems like $options->getData('scope', '') can also be of type array; however, parameter $scope of Bavix\SDK\Corundum::getToken() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

330
            /** @scrutinizer ignore-type */ $options->getData('scope', '')
Loading history...
331
        );
332
333
        return $this->apiSend(
334
            $this->uploadSlice($token, $options, $file),
335
            function () use ($user, $options, $file) {
336
                return $this->uploadSlice(
337
                    $this->tokenUpdate($user, $options),
338
                    $options,
339
                    $file
340
                );
341
            }
342
        );
343
    }
344
345
    /**
346
     * @param string     $user
347
     * @param string     $name
348
     * @param Slice|null $options
349
     *
350
     * @return Slice
351
     */
352
    public function update(string $user, string $name, Slice $options = null): Slice
353
    {
354
        if (!$options)
355
        {
356
            $options = $this->fake();
357
        }
358
359
        $token = $this->getToken(
360
            $user,
361
            $options->getData('scope', '')
0 ignored issues
show
Bug introduced by
It seems like $options->getData('scope', '') can also be of type array; however, parameter $scope of Bavix\SDK\Corundum::getToken() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

361
            /** @scrutinizer ignore-type */ $options->getData('scope', '')
Loading history...
362
        );
363
364
        $options->allow404 = true;
365
366
        $urlUpload    = $this->slice->getRequired('app.url.upload');
367
        $options->url = Path::slash($urlUpload) . $name;
0 ignored issues
show
Bug introduced by
It seems like $urlUpload can also be of type array; however, parameter $data of Bavix\SDK\Path::slash() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

367
        $options->url = Path::slash(/** @scrutinizer ignore-type */ $urlUpload) . $name;
Loading history...
368
369
        return $this->apiSend(
370
            $this->uploadSlice($token, $options),
371
            function () use ($user, $options) {
372
                return $this->uploadSlice(
373
                    $this->tokenUpdate($user, $options),
374
                    $options
375
                );
376
            }
377
        );
378
    }
379
380
    /**
381
     * @param string     $user
382
     * @param string     $name
383
     * @param Slice|null $options
384
     *
385
     * @return Slice|null
386
     */
387
    public function delete(string $user, string $name, Slice $options = null)
388
    {
389
        if (!$options)
390
        {
391
            $options = $this->fake();
392
        }
393
394
        $token = $this->getToken(
395
            $user,
396
            $options->getData('scope', '')
0 ignored issues
show
Bug introduced by
It seems like $options->getData('scope', '') can also be of type array; however, parameter $scope of Bavix\SDK\Corundum::getToken() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

396
            /** @scrutinizer ignore-type */ $options->getData('scope', '')
Loading history...
397
        );
398
399
        $options->allow404 = true;
400
401
        $urlUpload    = $this->slice->getRequired('app.url.upload');
402
        $options->url = Path::slash($urlUpload) . $name;
0 ignored issues
show
Bug introduced by
It seems like $urlUpload can also be of type array; however, parameter $data of Bavix\SDK\Path::slash() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

402
        $options->url = Path::slash(/** @scrutinizer ignore-type */ $urlUpload) . $name;
Loading history...
403
404
        $options->method = 'delete';
405
406
        return $this->apiSend(
407
            $this->uploadSlice($token, $options),
408
            function () use ($user, $options) {
409
                return $this->uploadSlice(
410
                    $this->tokenUpdate($user, $options),
411
                    $options
412
                );
413
            }
414
        );
415
    }
416
417
    /**
418
     * @param Slice    $slice
419
     * @param callable $callback
420
     *
421
     * @return Slice|null
422
     */
423
    protected function apiSend(Slice $slice, callable $callback)
424
    {
425
        try
426
        {
427
            return $this->apiRequest($slice);
428
        }
429
        catch (\Throwable $throwable)
430
        {
431
            return $this->apiRequest($callback());
432
        }
433
    }
434
435
    /**
436
     * @param Slice  $token
437
     * @param Slice  $options
438
     * @param string $file
439
     *
440
     * @return Slice
441
     */
442
    protected function uploadSlice(Slice $token, Slice $options, string $file = null): Slice
443
    {
444
        $params = $options->getData('params', []);
445
446
        if ($file)
447
        {
448
            $params = Arr::merge($params, [
449
                'file' => '@' . \ltrim($file, '@')
450
            ]);
451
        }
452
453
        return new Slice([
454
            'token_type'   => $token->getRequired('token_type'),
455
            'access_token' => $token->getRequired('access_token'),
456
            'method'       => $options->getData('method', 'post'),
457
            'url'          => $options->getData(
458
                'url',
459
                $this->slice->getRequired('app.url.upload')
460
            ),
461
462
            'allow404' => $options->getData('allow404', false),
463
464
            'headers' => Arr::merge($options->getData('headers', []), [
465
                'Accept' => 'application/json'
466
            ]),
467
468
            'params' => $params
469
        ]);
470
    }
471
472
    /**
473
     * @param Slice $token
474
     *
475
     * @return Slice
476
     *
477
     * @throws Invalid
478
     */
479
    protected function verify(Slice $token): Slice
480
    {
481
        return $this->apiRequest(new Slice([
482
            'token_type'   => $token->getRequired('token_type'),
483
            'access_token' => $token->getRequired('access_token'),
484
            'url'          => $this->slice->getRequired('app.url.verify'),
485
            'headers'      => [
486
                'Accept' => 'application/json'
487
            ]
488
        ]));
489
    }
490
491
    /**
492
     * @param string $user
493
     * @param Slice  $slice
494
     *
495
     * @return Slice
496
     *
497
     * @throws Invalid
498
     */
499
    protected function tokenUpdate(string $user, Slice $slice): Slice
500
    {
501
        try
502
        {
503
            $token  = $this->refreshToken($user);
504
            $verify = $this->verify($token);
505
506
            if (!$verify || !$verify->getRequired('verify'))
0 ignored issues
show
introduced by
$verify is of type Bavix\Slice\Slice, thus it always evaluated to true.
Loading history...
507
            {
508
                throw new Invalid('The token isn\'t verified');
509
            }
510
        }
511
        catch (Invalid $invalid)
512
        {
513
            $this->tokens[$user] = null;
514
515
            $token = $this->getToken(
516
                $user,
517
                $slice->getData('scope', '')
0 ignored issues
show
Bug introduced by
It seems like $slice->getData('scope', '') can also be of type array; however, parameter $scope of Bavix\SDK\Corundum::getToken() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

517
                /** @scrutinizer ignore-type */ $slice->getData('scope', '')
Loading history...
518
            );
519
        }
520
521
        return $token;
522
    }
523
524
}
525