Passed
Pull Request — master (#54)
by
unknown
02:26
created

OssAdapter::normalizeHost()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 12
nc 6
nop 0
dl 0
loc 19
rs 9.8666
c 0
b 0
f 0
1
<?php
2
3
namespace Jason\Flysystem\Oss;
4
5
use Carbon\Carbon;
6
use Iidestiny\Flysystem\Oss\Traits\SignatureTrait;
0 ignored issues
show
Bug introduced by
The type Iidestiny\Flysystem\Oss\Traits\SignatureTrait was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

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