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

OssAdapter::has()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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