Passed
Push — master ( 33f44d...dc25cc )
by Luo
03:45 queued 01:16
created

OssAdapter   D

Complexity

Total Complexity 59

Size/Duplication

Total Lines 654
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 59
eloc 198
dl 0
loc 654
ccs 0
cts 307
cp 0
rs 4.08
c 0
b 0
f 0

29 Methods

Rating   Name   Duplication   Size   Complexity  
A initClient() 0 4 2
A __construct() 0 10 1
A signatureConfig() 0 52 2
A signUrl() 0 11 2
A normalizeHost() 0 15 3
A writeStream() 0 5 1
A getSize() 0 3 1
A getTemporaryUrl() 0 3 1
A update() 0 3 1
A readStream() 0 9 2
A deleteDir() 0 3 1
A checkEndpoint() 0 8 3
A write() 0 13 2
A setVisibility() 0 12 3
A getMimetype() 0 7 2
A copy() 0 12 2
A read() 0 9 2
A rename() 0 7 2
A getUrl() 0 3 1
A listContents() 0 17 4
A getMetadata() 0 11 2
A has() 0 5 1
A createDir() 0 3 1
B listDirObjects() 0 63 10
A normalizeFileInfo() 0 15 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, SignatureTrait;
31
32
    /**
33
     * @var
34
     */
35
    protected $accessKeyId;
36
37
    /**
38
     * @var
39
     */
40
    protected $accessKeySecret;
41
42
    /**
43
     * @var
44
     */
45
    protected $endpoint;
46
47
    /**
48
     * @var
49
     */
50
    protected $bucket;
51
52
    /**
53
     * @var
54
     */
55
    protected $isCName;
56
57
    /**
58
     * @var OssClient
59
     */
60
    protected $client;
61
62
    /**
63
     * @var array|mixed[]
64
     */
65
    protected $params;
66
67
    /**
68
     * @var bool
69
     */
70
    protected $useSSL = false;
71
72
    /**
73
     * OssAdapter constructor.
74
     *
75
     * @param       $accessKeyId
76
     * @param       $accessKeySecret
77
     * @param       $endpoint
78
     * @param       $bucket
79
     * @param bool  $isCName
80
     * @param mixed ...$params
81
     *
82
     * @throws OssException
83
     */
84
    public function __construct($accessKeyId, $accessKeySecret, $endpoint, $bucket, $isCName = false, ...$params)
85
    {
86
        $this->accessKeyId = $accessKeyId;
87
        $this->accessKeySecret = $accessKeySecret;
88
        $this->endpoint = $endpoint;
89
        $this->bucket = $bucket;
90
        $this->isCName = $isCName;
91
        $this->params = $params;
92
        $this->initClient();
93
        $this->checkEndpoint();
94
    }
95
96
    /**
97
     * init oss client.
98
     *
99
     * @throws OssException
100
     */
101
    protected function initClient()
102
    {
103
        if (empty($this->client)) {
104
            $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

104
            $this->client = new OssClient($this->accessKeyId, $this->accessKeySecret, $this->endpoint, $this->isCName, /** @scrutinizer ignore-type */ ...$this->params);
Loading history...
105
        }
106
    }
107
108
    /**
109
     * oss 直传配置.
110
     *
111
     * @param string $prefix
112
     * @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...
113
     * @param int    $expire
114
     *
115
     * @return false|string
116
     *
117
     * @throws \Exception
118
     */
119
    public function signatureConfig($prefix = '', $callBackUrl = null, $expire = 30)
120
    {
121
        if (!empty($prefix)) {
122
            $prefix = ltrim($prefix, '/');
123
        }
124
125
        $callbackParam = [
126
            'callbackUrl' => $callBackUrl,
127
            'callbackBody' => 'filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}',
128
            'callbackBodyType' => 'application/x-www-form-urlencoded',
129
        ];
130
        $callbackString = json_encode($callbackParam);
131
        $base64_callback_body = base64_encode($callbackString);
132
133
        $now = time();
134
        $end = $now + $expire;
135
        $expiration = $this->gmt_iso8601($end);
136
137
        // 最大文件大小.用户可以自己设置
138
        $condition = [
139
            0 => 'content-length-range',
140
            1 => 0,
141
            2 => 1048576000,
142
        ];
143
        $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...
144
145
        $start = [
146
            0 => 'starts-with',
147
            1 => '$key',
148
            2 => $prefix,
149
        ];
150
        $conditions[] = $start;
151
152
        $arr = [
153
            'expiration' => $expiration,
154
            'conditions' => $conditions,
155
        ];
156
        $policy = json_encode($arr);
157
        $base64_policy = base64_encode($policy);
158
        $string_to_sign = $base64_policy;
159
        $signature = base64_encode(hash_hmac('sha1', $string_to_sign, $this->accessKeySecret, true));
160
161
        $response = [];
162
        $response['accessid'] = $this->accessKeyId;
163
        $response['host'] = $this->normalizeHost();
164
        $response['policy'] = $base64_policy;
165
        $response['signature'] = $signature;
166
        $response['expire'] = $end;
167
        $response['callback'] = $base64_callback_body;
168
        $response['dir'] = $prefix;  // 这个参数是设置用户上传文件时指定的前缀。
169
170
        return json_encode($response);
171
    }
172
173
    /**
174
     * sign url.
175
     *
176
     * @param       $path
177
     * @param       $timeout
178
     * @param array $options
179
     *
180
     * @return bool|string
181
     */
182
    public function signUrl($path, $timeout, array $options = [])
183
    {
184
        $path = $this->applyPathPrefix($path);
185
186
        try {
187
            $path = $this->client->signUrl($this->bucket, $path, $timeout, OssClient::OSS_HTTP_GET, $options);
188
        } catch (OssException $exception) {
189
            return false;
190
        }
191
192
        return $path;
193
    }
194
195
    /**
196
     * temporaryUrl
197
     *
198
     * @param       $path
199
     * @param       $expiration
200
     * @param array $options
201
     */
202
    public function getTemporaryUrl($path, $expiration, array $options = [])
203
    {
204
        $this->signUrl($path, Carbon::now()->diffInSeconds($expiration), $options);
205
    }
206
    /**
207
     * write a file.
208
     *
209
     * @param string $path
210
     * @param string $contents
211
     * @param Config $config
212
     *
213
     * @return array|bool|false
214
     */
215
    public function write($path, $contents, Config $config)
216
    {
217
        $path = $this->applyPathPrefix($path);
218
219
        $options = [];
220
221
        if ($config->has('options')) {
222
            $options = $config->get('options');
223
        }
224
225
        $this->client->putObject($this->bucket, $path, $contents, $options);
226
227
        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...
228
    }
229
230
    /**
231
     * Write a new file using a stream.
232
     *
233
     * @param string   $path
234
     * @param resource $resource
235
     * @param Config   $config
236
     *
237
     * @return array|bool|false
238
     */
239
    public function writeStream($path, $resource, Config $config)
240
    {
241
        $contents = stream_get_contents($resource);
242
243
        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...
244
    }
245
246
    /**
247
     * Update a file.
248
     *
249
     * @param string $path
250
     * @param string $contents
251
     * @param Config $config
252
     *
253
     * @return array|bool|false
254
     */
255
    public function update($path, $contents, Config $config)
256
    {
257
        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...
258
    }
259
260
    /**
261
     * Update a file using a stream.
262
     *
263
     * @param string   $path
264
     * @param resource $resource
265
     * @param Config   $config
266
     *
267
     * @return array|bool|false
268
     */
269
    public function updateStream($path, $resource, Config $config)
270
    {
271
        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...
272
    }
273
274
    /**
275
     * rename a file.
276
     *
277
     * @param string $path
278
     * @param string $newpath
279
     *
280
     * @return bool
281
     *
282
     * @throws OssException
283
     */
284
    public function rename($path, $newpath)
285
    {
286
        if (!$this->copy($path, $newpath)) {
287
            return false;
288
        }
289
290
        return $this->delete($path);
291
    }
292
293
    /**
294
     * copy a file.
295
     *
296
     * @param string $path
297
     * @param string $newpath
298
     *
299
     * @return bool
300
     */
301
    public function copy($path, $newpath)
302
    {
303
        $path = $this->applyPathPrefix($path);
304
        $newpath = $this->applyPathPrefix($newpath);
305
306
        try {
307
            $this->client->copyObject($this->bucket, $path, $this->bucket, $newpath);
308
        } catch (OssException $exception) {
309
            return false;
310
        }
311
312
        return true;
313
    }
314
315
    /**
316
     * delete a file.
317
     *
318
     * @param string $path
319
     *
320
     * @return bool
321
     *
322
     * @throws OssException
323
     */
324
    public function delete($path)
325
    {
326
        $path = $this->applyPathPrefix($path);
327
328
        try {
329
            $this->client->deleteObject($this->bucket, $path);
330
        } catch (OssException $ossException) {
331
            return false;
332
        }
333
334
        return !$this->has($path);
335
    }
336
337
    /**
338
     * Delete a directory.
339
     *
340
     * @param string $dirname
341
     *
342
     * @return bool
343
     */
344
    public function deleteDir($dirname)
345
    {
346
        return true;
347
    }
348
349
    /**
350
     * create a directory.
351
     *
352
     * @param string $dirname
353
     * @param Config $config
354
     *
355
     * @return array|false
356
     */
357
    public function createDir($dirname, Config $config)
358
    {
359
        return ['path' => $dirname, 'type' => 'dir'];
360
    }
361
362
    /**
363
     * visibility.
364
     *
365
     * @param string $path
366
     * @param string $visibility
367
     *
368
     * @return array|bool|false
369
     */
370
    public function setVisibility($path, $visibility)
371
    {
372
        $object = $this->applyPathPrefix($path);
373
        $acl = (AdapterInterface::VISIBILITY_PUBLIC === $visibility) ? OssClient::OSS_ACL_TYPE_PUBLIC_READ : OssClient::OSS_ACL_TYPE_PRIVATE;
374
375
        try {
376
            $this->client->putObjectAcl($this->bucket, $object, $acl);
377
        } catch (OssException $exception) {
378
            return false;
379
        }
380
381
        return compact('visibility');
382
    }
383
384
    /**
385
     * Check whether a file exists.
386
     *
387
     * @param string $path
388
     *
389
     * @return array|bool|null
390
     */
391
    public function has($path)
392
    {
393
        $path = $this->applyPathPrefix($path);
394
395
        return $this->client->doesObjectExist($this->bucket, $path);
396
    }
397
398
    /**
399
     * Get resource url.
400
     *
401
     * @param string $path
402
     *
403
     * @return string
404
     */
405
    public function getUrl($path)
406
    {
407
        return $this->normalizeHost().ltrim($path, '/');
408
    }
409
410
    /**
411
     * read a file.
412
     *
413
     * @param string $path
414
     *
415
     * @return array|bool|false
416
     */
417
    public function read($path)
418
    {
419
        try {
420
            $contents = $this->getObject($path);
421
        } catch (OssException $exception) {
422
            return false;
423
        }
424
425
        return compact('contents', 'path');
426
    }
427
428
    /**
429
     * read a file stream.
430
     *
431
     * @param string $path
432
     *
433
     * @return array|bool|false
434
     */
435
    public function readStream($path)
436
    {
437
        try {
438
            $stream = $this->getObject($path);
439
        } catch (OssException $exception) {
440
            return false;
441
        }
442
443
        return compact('stream', 'path');
444
    }
445
446
    /**
447
     * Lists all files in the directory.
448
     *
449
     * @param string $directory
450
     * @param bool   $recursive
451
     *
452
     * @return array
453
     *
454
     * @throws OssException
455
     */
456
    public function listContents($directory = '', $recursive = false)
457
    {
458
        $list = [];
459
460
        $result = $this->listDirObjects($directory, true);
461
462
        if (!empty($result['objects'])) {
463
            foreach ($result['objects'] as $files) {
464
                if (!$fileInfo = $this->normalizeFileInfo($files)) {
465
                    continue;
466
                }
467
468
                $list[] = $fileInfo;
469
            }
470
        }
471
472
        return $list;
473
    }
474
475
    /**
476
     * get meta data.
477
     *
478
     * @param string $path
479
     *
480
     * @return array|bool|false
481
     */
482
    public function getMetadata($path)
483
    {
484
        $path = $this->applyPathPrefix($path);
485
486
        try {
487
            $metadata = $this->client->getObjectMeta($this->bucket, $path);
488
        } catch (OssException $exception) {
489
            return false;
490
        }
491
492
        return $metadata;
493
    }
494
495
    /**
496
     * get the size of file.
497
     *
498
     * @param string $path
499
     *
500
     * @return array|false
501
     */
502
    public function getSize($path)
503
    {
504
        return $this->normalizeFileInfo(['Key' => $path]);
505
    }
506
507
    /**
508
     * get mime type.
509
     *
510
     * @param string $path
511
     *
512
     * @return array|bool|false
513
     */
514
    public function getMimetype($path)
515
    {
516
        if (!$fileInfo = $this->normalizeFileInfo(['Key' => $path])) {
517
            return false;
518
        }
519
520
        return ['mimetype' => $fileInfo['type']];
521
    }
522
523
    /**
524
     * get timestamp.
525
     *
526
     * @param string $path
527
     *
528
     * @return array|false
529
     */
530
    public function getTimestamp($path)
531
    {
532
        return $this->normalizeFileInfo(['Key' => $path]);
533
    }
534
535
    /**
536
     * normalize Host.
537
     *
538
     * @return string
539
     */
540
    protected function normalizeHost()
541
    {
542
        if ($this->isCName) {
543
            $domain = $this->endpoint;
544
        } else {
545
            $domain = $this->bucket.'.'.$this->endpoint;
546
        }
547
548
        if ($this->useSSL) {
549
            $domain = "https://{$domain}";
550
        } else {
551
            $domain = "http://{$domain}";
552
        }
553
554
        return rtrim($domain, '/').'/';
555
    }
556
557
    /**
558
     * Check the endpoint to see if SSL can be used.
559
     */
560
    protected function checkEndpoint()
561
    {
562
        if (0 === strpos($this->endpoint, 'http://')) {
563
            $this->endpoint = substr($this->endpoint, strlen('http://'));
564
            $this->useSSL = false;
565
        } elseif (0 === strpos($this->endpoint, 'https://')) {
566
            $this->endpoint = substr($this->endpoint, strlen('https://'));
567
            $this->useSSL = true;
568
        }
569
    }
570
571
    /**
572
     * Read an object from the OssClient.
573
     *
574
     * @param $path
575
     *
576
     * @return string
577
     */
578
    protected function getObject($path)
579
    {
580
        $path = $this->applyPathPrefix($path);
581
582
        return $this->client->getObject($this->bucket, $path);
583
    }
584
585
    /**
586
     * File list core method.
587
     *
588
     * @param string $dirname
589
     * @param bool   $recursive
590
     *
591
     * @return array
592
     *
593
     * @throws OssException
594
     */
595
    public function listDirObjects($dirname = '', $recursive = false)
596
    {
597
        $delimiter = '/';
598
        $nextMarker = '';
599
        $maxkeys = 1000;
600
601
        $result = [];
602
603
        while (true) {
604
            $options = [
605
                'delimiter' => $delimiter,
606
                'prefix' => $dirname,
607
                'max-keys' => $maxkeys,
608
                'marker' => $nextMarker,
609
            ];
610
611
            try {
612
                $listObjectInfo = $this->client->listObjects($this->bucket, $options);
613
            } catch (OssException $exception) {
614
                throw $exception;
615
            }
616
617
            $nextMarker = $listObjectInfo->getNextMarker();
618
            $objectList = $listObjectInfo->getObjectList();
619
            $prefixList = $listObjectInfo->getPrefixList();
620
621
            if (!empty($objectList)) {
622
                foreach ($objectList as $objectInfo) {
623
                    $object['Prefix'] = $dirname;
624
                    $object['Key'] = $objectInfo->getKey();
625
                    $object['LastModified'] = $objectInfo->getLastModified();
626
                    $object['eTag'] = $objectInfo->getETag();
627
                    $object['Type'] = $objectInfo->getType();
628
                    $object['Size'] = $objectInfo->getSize();
629
                    $object['StorageClass'] = $objectInfo->getStorageClass();
630
                    $result['objects'][] = $object;
631
                }
632
            } else {
633
                $result['objects'] = [];
634
            }
635
636
            if (!empty($prefixList)) {
637
                foreach ($prefixList as $prefixInfo) {
638
                    $result['prefix'][] = $prefixInfo->getPrefix();
639
                }
640
            } else {
641
                $result['prefix'] = [];
642
            }
643
644
            // Recursive directory
645
            if ($recursive) {
646
                foreach ($result['prefix'] as $prefix) {
647
                    $next = $this->listDirObjects($prefix, $recursive);
648
                    $result['objects'] = array_merge($result['objects'], $next['objects']);
649
                }
650
            }
651
652
            if ('' === $nextMarker) {
653
                break;
654
            }
655
        }
656
657
        return $result;
658
    }
659
660
    /**
661
     * normalize file info.
662
     *
663
     * @param array $stats
664
     *
665
     * @return array
666
     */
667
    protected function normalizeFileInfo(array $stats)
668
    {
669
        $filePath = ltrim($stats['Key'], '/');
670
671
        $meta = $this->getMetadata($filePath) ?? [];
672
673
        if (empty($meta)) {
674
            return [];
675
        }
676
677
        return [
678
            'type' => $meta['content-type'],
679
            'path' => $filePath,
680
            'timestamp' => $meta['info']['filetime'],
681
            'size' => $meta['content-length'],
682
        ];
683
    }
684
}
685