Passed
Push — master ( 53ff19...ea1022 )
by Luo
54s queued 11s
created

OssAdapter   F

Complexity

Total Complexity 61

Size/Duplication

Total Lines 693
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 19
Bugs 10 Features 0
Metric Value
eloc 217
c 19
b 10
f 0
dl 0
loc 693
ccs 0
cts 328
cp 0
rs 3.52
wmc 61

30 Methods

Rating   Name   Duplication   Size   Complexity  
A normalizeHost() 0 15 3
A writeStream() 0 5 1
A getSize() 0 3 1
A getTemporaryUrl() 0 3 1
A initClient() 0 4 2
A update() 0 3 1
A readStream() 0 9 2
A deleteDir() 0 8 2
A checkEndpoint() 0 8 3
A write() 0 13 2
A setVisibility() 0 12 3
A __construct() 0 12 1
A getMimetype() 0 3 1
A copy() 0 12 2
A signatureConfig() 0 52 2
A read() 0 9 2
A rename() 0 7 2
A getUrl() 0 5 1
A listContents() 0 17 4
A bucket() 0 17 2
A getMetadata() 0 11 2
A has() 0 5 1
A createDir() 0 5 1
B listDirObjects() 0 63 10
A normalizeFileInfo() 0 16 2
A signUrl() 0 11 2
A updateStream() 0 3 1
A delete() 0 11 2
A getTimestamp() 0 3 1
A getObject() 0 5 1

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
/*
4
 * This file is part of the iidestiny/flysystem-oss.
5
 *
6
 * (c) iidestiny <[email protected]>
7
 *
8
 * This source file is subject to the MIT license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace Iidestiny\Flysystem\Oss;
13
14
use Carbon\Carbon;
15
use Iidestiny\Flysystem\Oss\Traits\SignatureTrait;
16
use League\Flysystem\AdapterInterface;
17
use League\Flysystem\Adapter\AbstractAdapter;
18
use League\Flysystem\Adapter\Polyfill\NotSupportingVisibilityTrait;
19
use League\Flysystem\Config;
20
use OSS\Core\OssException;
21
use OSS\OssClient;
22
23
/**
24
 * Class OssAdapter.
25
 *
26
 * @author iidestiny <[email protected]>
27
 */
28
class OssAdapter extends AbstractAdapter
29
{
30
    use NotSupportingVisibilityTrait;
31
    use SignatureTrait;
32
33
    /**
34
     * @var
35
     */
36
    protected $accessKeyId;
37
38
    /**
39
     * @var
40
     */
41
    protected $accessKeySecret;
42
43
    /**
44
     * @var
45
     */
46
    protected $endpoint;
47
48
    /**
49
     * @var
50
     */
51
    protected $bucket;
52
53
    /**
54
     * @var
55
     */
56
    protected $isCName;
57
58
    /**
59
     * @var array
60
     */
61
    protected $buckets;
62
    /**
63
     * @var OssClient
64
     */
65
    protected $client;
66
67
    /**
68
     * @var array|mixed[]
69
     */
70
    protected $params;
71
72
    /**
73
     * @var bool
74
     */
75
    protected $useSSL = false;
76
77
    /**
78
     * OssAdapter constructor.
79
     *
80
     * @param       $accessKeyId
81
     * @param       $accessKeySecret
82
     * @param       $endpoint
83
     * @param       $bucket
84
     * @param bool  $isCName
85
     * @param       $prefix
86
     * @param array $buckets
87
     * @param mixed ...$params
88
     *
89
     * @throws OssException
90
     */
91
    public function __construct($accessKeyId, $accessKeySecret, $endpoint, $bucket, $isCName = false, $prefix = '', $buckets = [], ...$params)
92
    {
93
        $this->accessKeyId = $accessKeyId;
94
        $this->accessKeySecret = $accessKeySecret;
95
        $this->endpoint = $endpoint;
96
        $this->bucket = $bucket;
97
        $this->isCName = $isCName;
98
        $this->setPathPrefix($prefix);
99
        $this->buckets = $buckets;
100
        $this->params = $params;
101
        $this->initClient();
102
        $this->checkEndpoint();
103
    }
104
105
    /**
106
     * 调用不同的桶配置
107
     */
108
    public function bucket($bucket)
109
    {
110
        if (!isset($this->buckets[$bucket])) {
111
            throw new \Exception("bucket is not exist.");
112
        }
113
        $bucketConfig = $this->buckets[$bucket];
114
115
        $this->accessKeyId = $bucketConfig['access_key'];
116
        $this->accessKeySecret = $bucketConfig['secret_key'];
117
        $this->endpoint = $bucketConfig['endpoint'];
118
        $this->bucket = $bucketConfig['bucket'];
119
        $this->isCName = $bucketConfig['isCName'];
120
121
        $this->initClient();
122
        $this->checkEndpoint();
123
124
        return $this;
125
    }
126
127
    /**
128
     * init oss client.
129
     *
130
     * @throws OssException
131
     */
132
    protected function initClient()
133
    {
134
        if (empty($this->client)) {
135
            $this->client = new OssClient($this->accessKeyId, $this->accessKeySecret, $this->endpoint, $this->isCName, ...$this->params);
0 ignored issues
show
Bug introduced by
$this->params is expanded, but the parameter $securityToken of OSS\OssClient::__construct() does not expect variable arguments. ( Ignorable by Annotation )

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

135
            $this->client = new OssClient($this->accessKeyId, $this->accessKeySecret, $this->endpoint, $this->isCName, /** @scrutinizer ignore-type */ ...$this->params);
Loading history...
136
        }
137
    }
138
139
    /**
140
     * oss 直传配置.
141
     *
142
     * @param string $prefix
143
     * @param null   $callBackUrl
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $callBackUrl is correct as it would always require null to be passed?
Loading history...
144
     * @param int    $expire
145
     * @param int    $contentLengthRangeValue 最大文件大小
146
     *
147
     * @return false|string
148
     *
149
     * @throws \Exception
150
     */
151
    public function signatureConfig($prefix = '', $callBackUrl = null, $expire = 30, $contentLengthRangeValue = 1048576000)
152
    {
153
        if (!empty($prefix)) {
154
            $prefix = ltrim($prefix, '/');
155
        }
156
157
        $callbackParam = [
158
            'callbackUrl' => $callBackUrl,
159
            'callbackBody' => 'filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}',
160
            'callbackBodyType' => 'application/x-www-form-urlencoded',
161
        ];
162
        $callbackString = json_encode($callbackParam);
163
        $base64_callback_body = base64_encode($callbackString);
164
165
        $now = time();
166
        $end = $now + $expire;
167
        $expiration = $this->gmt_iso8601($end);
168
169
        // 最大文件大小.用户可以自己设置
170
        $condition = [
171
            0 => 'content-length-range',
172
            1 => 0,
173
            2 => $contentLengthRangeValue,
174
        ];
175
        $conditions[] = $condition;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$conditions was never initialized. Although not strictly required by PHP, it is generally a good practice to add $conditions = array(); before regardless.
Loading history...
176
177
        $start = [
178
            0 => 'starts-with',
179
            1 => '$key',
180
            2 => $prefix,
181
        ];
182
        $conditions[] = $start;
183
184
        $arr = [
185
            'expiration' => $expiration,
186
            'conditions' => $conditions,
187
        ];
188
        $policy = json_encode($arr);
189
        $base64_policy = base64_encode($policy);
190
        $string_to_sign = $base64_policy;
191
        $signature = base64_encode(hash_hmac('sha1', $string_to_sign, $this->accessKeySecret, true));
192
193
        $response = [];
194
        $response['accessid'] = $this->accessKeyId;
195
        $response['host'] = $this->normalizeHost();
196
        $response['policy'] = $base64_policy;
197
        $response['signature'] = $signature;
198
        $response['expire'] = $end;
199
        $response['callback'] = $base64_callback_body;
200
        $response['dir'] = $prefix;  // 这个参数是设置用户上传文件时指定的前缀。
201
202
        return json_encode($response);
203
    }
204
205
    /**
206
     * sign url.
207
     *
208
     * @param       $path
209
     * @param       $timeout
210
     * @param array $options
211
     *
212
     * @return bool|string
213
     */
214
    public function signUrl($path, $timeout, array $options = [])
215
    {
216
        $path = $this->applyPathPrefix($path);
217
218
        try {
219
            $path = $this->client->signUrl($this->bucket, $path, $timeout, OssClient::OSS_HTTP_GET, $options);
220
        } catch (OssException $exception) {
221
            return false;
222
        }
223
224
        return $path;
225
    }
226
227
    /**
228
     * temporaryUrl.
229
     *
230
     * @param       $path
231
     * @param       $expiration
232
     * @param array $options
233
     */
234
    public function getTemporaryUrl($path, $expiration, array $options = [])
235
    {
236
        return $this->signUrl($path, Carbon::now()->diffInSeconds($expiration), $options);
237
    }
238
239
    /**
240
     * write a file.
241
     *
242
     * @param string $path
243
     * @param string $contents
244
     * @param Config $config
245
     *
246
     * @return array|bool|false
247
     */
248
    public function write($path, $contents, Config $config)
249
    {
250
        $path = $this->applyPathPrefix($path);
251
252
        $options = [];
253
254
        if ($config->has('options')) {
255
            $options = $config->get('options');
256
        }
257
258
        $this->client->putObject($this->bucket, $path, $contents, $options);
259
260
        return true;
0 ignored issues
show
Bug Best Practice introduced by
The expression return true returns the type true which is incompatible with the return type mandated by League\Flysystem\AdapterInterface::write() of array|false.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
261
    }
262
263
    /**
264
     * Write a new file using a stream.
265
     *
266
     * @param string   $path
267
     * @param resource $resource
268
     * @param Config   $config
269
     *
270
     * @return array|bool|false
271
     */
272
    public function writeStream($path, $resource, Config $config)
273
    {
274
        $contents = stream_get_contents($resource);
275
276
        return $this->write($path, $contents, $config);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->write($path, $contents, $config) returns the type true which is incompatible with the return type mandated by League\Flysystem\AdapterInterface::writeStream() of array|false.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
277
    }
278
279
    /**
280
     * Update a file.
281
     *
282
     * @param string $path
283
     * @param string $contents
284
     * @param Config $config
285
     *
286
     * @return array|bool|false
287
     */
288
    public function update($path, $contents, Config $config)
289
    {
290
        return $this->write($path, $contents, $config);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->write($path, $contents, $config) returns the type true which is incompatible with the return type mandated by League\Flysystem\AdapterInterface::update() of array|false.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
291
    }
292
293
    /**
294
     * Update a file using a stream.
295
     *
296
     * @param string   $path
297
     * @param resource $resource
298
     * @param Config   $config
299
     *
300
     * @return array|bool|false
301
     */
302
    public function updateStream($path, $resource, Config $config)
303
    {
304
        return $this->writeStream($path, $resource, $config);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->writeStrea...th, $resource, $config) returns the type true which is incompatible with the return type mandated by League\Flysystem\AdapterInterface::updateStream() of array|false.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
305
    }
306
307
    /**
308
     * rename a file.
309
     *
310
     * @param string $path
311
     * @param string $newpath
312
     *
313
     * @return bool
314
     *
315
     * @throws OssException
316
     */
317
    public function rename($path, $newpath)
318
    {
319
        if (!$this->copy($path, $newpath)) {
320
            return false;
321
        }
322
323
        return $this->delete($path);
324
    }
325
326
    /**
327
     * copy a file.
328
     *
329
     * @param string $path
330
     * @param string $newpath
331
     *
332
     * @return bool
333
     */
334
    public function copy($path, $newpath)
335
    {
336
        $path = $this->applyPathPrefix($path);
337
        $newpath = $this->applyPathPrefix($newpath);
338
339
        try {
340
            $this->client->copyObject($this->bucket, $path, $this->bucket, $newpath);
341
        } catch (OssException $exception) {
342
            return false;
343
        }
344
345
        return true;
346
    }
347
348
    /**
349
     * delete a file.
350
     *
351
     * @param string $path
352
     *
353
     * @return bool
354
     *
355
     * @throws OssException
356
     */
357
    public function delete($path)
358
    {
359
        $path = $this->applyPathPrefix($path);
360
361
        try {
362
            $this->client->deleteObject($this->bucket, $path);
363
        } catch (OssException $ossException) {
364
            return false;
365
        }
366
367
        return !$this->has($path);
368
    }
369
370
    /**
371
     * Delete a directory.
372
     *
373
     * @param string $dirname
374
     *
375
     * @return bool
376
     */
377
    public function deleteDir($dirname)
378
    {
379
        $fileList = $this->listContents($dirname, true);
380
        foreach ($fileList as $file) {
381
            $this->delete($file['path']);
382
        }
383
384
        return !$this->has($dirname);
385
    }
386
387
    /**
388
     * create a directory.
389
     *
390
     * @param string $dirname
391
     * @param Config $config
392
     *
393
     * @return bool
394
     */
395
    public function createDir($dirname, Config $config)
396
    {
397
        $defaultFile = trim($dirname, '/').'/oss.txt';
398
399
        return $this->write($defaultFile, '当虚拟目录下有其他文件时,可删除此文件~', $config);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->write($def... returns the type true which is incompatible with the return type mandated by League\Flysystem\AdapterInterface::createDir() of array|false.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
400
    }
401
402
    /**
403
     * visibility.
404
     *
405
     * @param string $path
406
     * @param string $visibility
407
     *
408
     * @return array|bool|false
409
     */
410
    public function setVisibility($path, $visibility)
411
    {
412
        $object = $this->applyPathPrefix($path);
413
        $acl = (AdapterInterface::VISIBILITY_PUBLIC === $visibility) ? OssClient::OSS_ACL_TYPE_PUBLIC_READ : OssClient::OSS_ACL_TYPE_PRIVATE;
414
415
        try {
416
            $this->client->putObjectAcl($this->bucket, $object, $acl);
417
        } catch (OssException $exception) {
418
            return false;
419
        }
420
421
        return compact('visibility');
422
    }
423
424
    /**
425
     * Check whether a file exists.
426
     *
427
     * @param string $path
428
     *
429
     * @return array|bool|null
430
     */
431
    public function has($path)
432
    {
433
        $path = $this->applyPathPrefix($path);
434
435
        return $this->client->doesObjectExist($this->bucket, $path);
436
    }
437
438
    /**
439
     * Get resource url.
440
     *
441
     * @param string $path
442
     *
443
     * @return string
444
     */
445
    public function getUrl($path)
446
    {
447
        $path = $this->applyPathPrefix($path);
448
449
        return $this->normalizeHost().ltrim($path, '/');
450
    }
451
452
    /**
453
     * read a file.
454
     *
455
     * @param string $path
456
     *
457
     * @return array|bool|false
458
     */
459
    public function read($path)
460
    {
461
        try {
462
            $contents = $this->getObject($path);
463
        } catch (OssException $exception) {
464
            return false;
465
        }
466
467
        return compact('contents', 'path');
468
    }
469
470
    /**
471
     * read a file stream.
472
     *
473
     * @param string $path
474
     *
475
     * @return array|bool|false
476
     */
477
    public function readStream($path)
478
    {
479
        try {
480
            $stream = $this->getObject($path);
481
        } catch (OssException $exception) {
482
            return false;
483
        }
484
485
        return compact('stream', 'path');
486
    }
487
488
    /**
489
     * Lists all files in the directory.
490
     *
491
     * @param string $directory
492
     * @param bool   $recursive
493
     *
494
     * @return array
495
     *
496
     * @throws OssException
497
     */
498
    public function listContents($directory = '', $recursive = false)
499
    {
500
        $list = [];
501
502
        $result = $this->listDirObjects($directory, true);
503
504
        if (!empty($result['objects'])) {
505
            foreach ($result['objects'] as $files) {
506
                if (!$fileInfo = $this->normalizeFileInfo($files)) {
507
                    continue;
508
                }
509
510
                $list[] = $fileInfo;
511
            }
512
        }
513
514
        return $list;
515
    }
516
517
    /**
518
     * get meta data.
519
     *
520
     * @param string $path
521
     *
522
     * @return array|bool|false
523
     */
524
    public function getMetadata($path)
525
    {
526
        $path = $this->applyPathPrefix($path);
527
528
        try {
529
            $metadata = $this->client->getObjectMeta($this->bucket, $path);
530
        } catch (OssException $exception) {
531
            return false;
532
        }
533
534
        return $metadata;
535
    }
536
537
    /**
538
     * get the size of file.
539
     *
540
     * @param string $path
541
     *
542
     * @return array|false
543
     */
544
    public function getSize($path)
545
    {
546
        return $this->normalizeFileInfo(['Key' => $path]);
547
    }
548
549
    /**
550
     * get mime type.
551
     *
552
     * @param string $path
553
     *
554
     * @return array|false
555
     */
556
    public function getMimetype($path)
557
    {
558
        return $this->normalizeFileInfo(['Key' => $path]);
559
    }
560
561
    /**
562
     * get timestamp.
563
     *
564
     * @param string $path
565
     *
566
     * @return array|false
567
     */
568
    public function getTimestamp($path)
569
    {
570
        return $this->normalizeFileInfo(['Key' => $path]);
571
    }
572
573
    /**
574
     * normalize Host.
575
     *
576
     * @return string
577
     */
578
    protected function normalizeHost()
579
    {
580
        if ($this->isCName) {
581
            $domain = $this->endpoint;
582
        } else {
583
            $domain = $this->bucket.'.'.$this->endpoint;
584
        }
585
586
        if ($this->useSSL) {
587
            $domain = "https://{$domain}";
588
        } else {
589
            $domain = "http://{$domain}";
590
        }
591
592
        return rtrim($domain, '/').'/';
593
    }
594
595
    /**
596
     * Check the endpoint to see if SSL can be used.
597
     */
598
    protected function checkEndpoint()
599
    {
600
        if (0 === strpos($this->endpoint, 'http://')) {
601
            $this->endpoint = substr($this->endpoint, strlen('http://'));
602
            $this->useSSL = false;
603
        } elseif (0 === strpos($this->endpoint, 'https://')) {
604
            $this->endpoint = substr($this->endpoint, strlen('https://'));
605
            $this->useSSL = true;
606
        }
607
    }
608
609
    /**
610
     * Read an object from the OssClient.
611
     *
612
     * @param $path
613
     *
614
     * @return string
615
     */
616
    protected function getObject($path)
617
    {
618
        $path = $this->applyPathPrefix($path);
619
620
        return $this->client->getObject($this->bucket, $path);
621
    }
622
623
    /**
624
     * File list core method.
625
     *
626
     * @param string $dirname
627
     * @param bool   $recursive
628
     *
629
     * @return array
630
     *
631
     * @throws OssException
632
     */
633
    public function listDirObjects($dirname = '', $recursive = false)
634
    {
635
        $delimiter = '/';
636
        $nextMarker = '';
637
        $maxkeys = 1000;
638
639
        $result = [];
640
641
        while (true) {
642
            $options = [
643
                'delimiter' => $delimiter,
644
                'prefix' => $dirname,
645
                'max-keys' => $maxkeys,
646
                'marker' => $nextMarker,
647
            ];
648
649
            try {
650
                $listObjectInfo = $this->client->listObjects($this->bucket, $options);
651
            } catch (OssException $exception) {
652
                throw $exception;
653
            }
654
655
            $nextMarker = $listObjectInfo->getNextMarker();
656
            $objectList = $listObjectInfo->getObjectList();
657
            $prefixList = $listObjectInfo->getPrefixList();
658
659
            if (!empty($objectList)) {
660
                foreach ($objectList as $objectInfo) {
661
                    $object['Prefix'] = $dirname;
662
                    $object['Key'] = $objectInfo->getKey();
663
                    $object['LastModified'] = $objectInfo->getLastModified();
664
                    $object['eTag'] = $objectInfo->getETag();
665
                    $object['Type'] = $objectInfo->getType();
666
                    $object['Size'] = $objectInfo->getSize();
667
                    $object['StorageClass'] = $objectInfo->getStorageClass();
668
                    $result['objects'][] = $object;
669
                }
670
            } else {
671
                $result['objects'] = [];
672
            }
673
674
            if (!empty($prefixList)) {
675
                foreach ($prefixList as $prefixInfo) {
676
                    $result['prefix'][] = $prefixInfo->getPrefix();
677
                }
678
            } else {
679
                $result['prefix'] = [];
680
            }
681
682
            // Recursive directory
683
            if ($recursive) {
684
                foreach ($result['prefix'] as $prefix) {
685
                    $next = $this->listDirObjects($prefix, $recursive);
686
                    $result['objects'] = array_merge($result['objects'], $next['objects']);
687
                }
688
            }
689
690
            if ('' === $nextMarker) {
691
                break;
692
            }
693
        }
694
695
        return $result;
696
    }
697
698
    /**
699
     * normalize file info.
700
     *
701
     * @param array $stats
702
     *
703
     * @return array
704
     */
705
    protected function normalizeFileInfo(array $stats)
706
    {
707
        $filePath = ltrim($stats['Key'], '/');
708
709
        $meta = $this->getMetadata($filePath) ?? [];
710
711
        if (empty($meta)) {
712
            return [];
713
        }
714
715
        return [
716
            'type' => 'file',
717
            'mimetype' => $meta['content-type'],
718
            'path' => $filePath,
719
            'timestamp' => $meta['info']['filetime'],
720
            'size' => $meta['content-length'],
721
        ];
722
    }
723
}
724