Passed
Push — master ( b4be77...04a421 )
by Бабичев
01:34
created

Corundum::update()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 27
Code Lines 15

Duplication

Lines 27
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 27
loc 27
ccs 0
cts 16
cp 0
rs 8.8571
c 0
b 0
f 0
cc 2
eloc 15
nc 2
nop 3
crap 6
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);
65
        $this->urlToken = $slice->getRequired('app.url.token');
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;
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),
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);
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', '')
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 View Code Duplication
    public function update(string $user, string $name, Slice $options = null): Slice
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...
353
    {
354
        if (!$options)
355
        {
356
            $options = $this->fake();
357
        }
358
359
        $token = $this->getToken(
360
            $user,
361
            $options->getData('scope', '')
362
        );
363
364
        $options->allow404 = true;
365
366
        $urlUpload    = $this->slice->getRequired('app.url.upload');
367
        $options->url = Path::slash($urlUpload) . $name;
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 View Code Duplication
    public function delete(string $user, string $name, Slice $options = null)
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...
388
    {
389
        if (!$options)
390
        {
391
            $options = $this->fake();
392
        }
393
394
        $token = $this->getToken(
395
            $user,
396
            $options->getData('scope', '')
397
        );
398
399
        $options->allow404 = true;
400
401
        $urlUpload    = $this->slice->getRequired('app.url.upload');
402
        $options->url = Path::slash($urlUpload) . $name;
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)
0 ignored issues
show
Bug introduced by
The class Throwable does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
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)
0 ignored issues
show
Bug Best Practice introduced by
The expression $file of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
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
505
            if (!$this->verify($token))
506
            {
507
                throw new Invalid('The token isn\'t verified');
508
            }
509
        }
510
        catch (Invalid $invalid)
511
        {
512
            $this->tokens[$user] = null;
513
514
            $token = $this->getToken(
515
                $user,
516
                $slice->getData('scope', '')
517
            );
518
        }
519
520
        return $token;
521
    }
522
523
}
524