Oss::getTimestamp()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 4
c 1
b 0
f 0
nc 4
nop 1
dl 0
loc 5
rs 10
1
<?php
2
3
// +----------------------------------------------------------------------
4
// | date: 2015-09-09
5
// +----------------------------------------------------------------------
6
// | OssAdapter.php: oss上传
7
// +----------------------------------------------------------------------
8
// | Author: Tinymeng <[email protected]>
9
// +----------------------------------------------------------------------
10
11
12
namespace tinymeng\uploads\Gateways;
13
14
use Exception;
15
use OSS\OssClient;
16
use OSS\Core\OssException;
17
use tinymeng\uploads\exception\TinymengException;
18
use tinymeng\uploads\Connector\Gateway;
19
use tinymeng\uploads\Helper\PathLibrary;
20
use tinymeng\uploads\Helper\FileFunction;
21
22
class Oss extends  Gateway
23
{
24
25
    const FILE_TYPE_FILE    = 'file';//类型是文件
26
    const FILE_TYPE_DIR     = 'dir';//类型是文件夹
27
28
29
    /**
30
     * oss client 上传对象
31
     *
32
     * @var OssClient
33
     */
34
    protected $upload;
35
36
    /**
37
     * bucket
38
     *
39
     * @var string
40
     */
41
    protected $bucket;
42
43
    /**
44
     * 构造方法
45
     *
46
     * @param array $config   配置信息
47
     * @author Tinymeng <[email protected]>
48
     */
49
    public function __construct($config)
50
    {
51
        $baseConfig = [
52
            'accessKeyId'		=> '',
53
            'accessKeySecret' 	=> '',
54
            'endpoint'			=> '',
55
            'isCName'			=> false,
56
            'securityToken'		=> null,
57
            'bucket'            => '',
58
            'timeout'           => '5184000',
59
            'connectTimeout'    => '10',
60
            'transport'     	=> 'http',//如果支持https,请填写https,如果不支持请填写http
61
            'max_keys'          => 1000,//max-keys用于限定此次返回object的最大数,如果不设定,默认为100,max-keys取值不能大于1000
62
        ];
63
        $this->config   = array_replace_recursive($baseConfig,$config);
64
        $this->bucket   = $this->config['bucket'];
65
        //设置路径前缀
66
        $this->setPathPrefix($this->config['transport'] . '://' . $this->config['bucket'] . '.' .  $this->config['endpoint']);
67
    }
68
69
    /**
70
     * 格式化路径
71
     *
72
     * @param $path
73
     * @return string
74
     */
75
    protected static function normalizerPath($path, $is_dir = false)
76
    {
77
        $path = ltrim(PathLibrary::normalizerPath($path, $is_dir), '/');
78
79
        return $path == '/' ? '' : $path;
80
    }
81
82
    /**
83
     * 获得OSS client上传对象
84
     *
85
     * @return \OSS\OssClient
86
     * @author Tinymeng <[email protected]>
87
     */
88
    protected function getClient()
89
    {
90
        if (!$this->client) {
91
            $this->client = new OssClient(
92
                $this->config['accessKeyId'],
93
                $this->config['accessKeySecret'],
94
                $this->config['endpoint'],
95
                $this->config['isCName'],
96
                $this->config['securityToken']
97
            );
98
99
            //设置请求超时时间
100
            $this->client->setTimeout($this->config['timeout']);
101
102
            //设置连接超时时间
103
            $this->client->setConnectTimeout($this->config['connectTimeout']);
104
        }
105
106
        return $this->client;
107
    }
108
109
    /**
110
     * 获得 Oss 实例
111
     * @return OssClient
112
     */
113
    public function getInstance()
114
    {
115
        return $this->getClient();
116
    }
117
118
    /**
119
     * 判断文件是否存在
120
     * @param string $path
121
     * @return bool
122
     * @throws TinymengException
123
     * @author Tinymeng <[email protected]>
124
     */
125
    public function has($path)
126
    {
127
        try {
128
            return $this->getClient()->doesObjectExist($this->bucket, $path) != false ? true : false;
129
        }catch (OssException $e){
130
            throw new TinymengException($e->getMessage());
131
        }
132
    }
133
134
    /**
135
     * 读取文件
136
     * @param $path
137
     * @return array
138
     * @throws TinymengException
139
     * @internal param $file_name
140
     * @author Tinymeng <[email protected]>
141
     */
142
    public function read($path)
143
    {
144
        try {
145
            return ['contents' => $this->getClient()->getObject($this->bucket, static::normalizerPath($path)) ];
146
        }catch (OssException $e){
147
            throw new TinymengException($e->getMessage());
148
        }
149
    }
150
151
    /**
152
     * 获得文件流
153
     * @param string $path
154
     * @return array|bool
155
     * @throws TinymengException
156
     * @author Tinymeng <[email protected]>
157
     */
158
    public function readStream($path)
159
    {
160
        try {
161
            //获得一个临时文件
162
            $tmpfname       = FileFunction::getTmpFile();
163
164
            file_put_contents($tmpfname, $this->read($path)['contents'] );
165
166
            $handle         = fopen($tmpfname, 'r');
167
168
            //删除临时文件
169
            FileFunction::deleteTmpFile($tmpfname);
170
171
            return ['stream' => $handle];
172
        }catch (OssException $e){
173
            throw new TinymengException($e->getMessage());
174
        }
175
    }
176
177
    /**
178
     * 写入文件
179
     *
180
     * @param $path
181
     * @param $contents
182
     * @param array $option
183
     * @return mixed
184
     * @throws TinymengException
185
     * @author Tinymeng <[email protected]>
186
     */
187
    public function write($path, $contents,$option=[])
188
    {
189
        try {
190
            $path = static::normalizerPath($path);
191
            return $this->getClient()->putObject($this->bucket, $path, $contents, $option);
192
        }catch (OssException $e){
193
            throw new TinymengException($e->getMessage());
194
        }
195
    }
196
197
    /**
198
     * 写入文件流
199
     * @param string $path
200
     * @param resource $resource
201
     * @param array $option
202
     * @return array|bool|false
203
     * @throws TinymengException
204
     */
205
    public function writeStream($path, $resource, $option=[])
206
    {
207
        try{
208
            $path = static::normalizerPath($path);
209
            //获得一个临时文件
210
            $tmpfname = FileFunction::getTmpFile();
211
212
            // 将资源流写入临时文件
213
            $contents = stream_get_contents($resource);
214
            file_put_contents($tmpfname, $contents);
215
216
            $this->getClient()->uploadFile($this->bucket, $path, $tmpfname, $option);
217
218
            //删除临时文件
219
            FileFunction::deleteTmpFile($tmpfname);
220
            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 tinymeng\uploads\Connect...nterface::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...
221
        } catch (OssException $e){
222
            throw new TinymengException($e->getMessage());
223
        }
224
    }
225
226
    /**
227
     * Name: 上传文件
228
     * Author: Tinymeng <[email protected]>
229
     * @param $path
230
     * @param $tmpfname
231
     * @param array $option
232
     * @return array 返回文件信息,包含 url, etag, size 等
233
     * @throws TinymengException
234
     */
235
    public function uploadFile($path, $tmpfname, $option = []){
236
        try{
237
            $path = static::normalizerPath($path);
238
            if (!file_exists($tmpfname)) {
239
                throw new TinymengException("文件不存在: {$tmpfname}");
240
            }
241
            
242
            $result = $this->getClient()->uploadFile($this->bucket, $path, $tmpfname, $option);
243
            
244
            if ($result === null || $result === false) {
245
                throw new TinymengException("文件上传失败");
246
            }
247
            
248
            // 构建返回信息
249
            $fileInfo = [
250
                'success' => true,
251
                'path' => $path,
252
                'key' => $path,
253
                'etag' => isset($result['etag']) ? trim($result['etag'], '"') : '',
254
                'size' => filesize($tmpfname),
255
            ];
256
            
257
            // 获取文件 URL
258
            try {
259
                $fileInfo['url'] = $this->getUrl($path, 0); // 0 表示永久 URL
260
            } catch (Exception $e) {
261
                // 如果获取 URL 失败,使用路径前缀构建 URL
262
                $fileInfo['url'] = $this->applyPathPrefix($path);
263
            }
264
            
265
            // 添加其他可能的响应信息
266
            if (isset($result['x-oss-request-id'])) {
267
                $fileInfo['request_id'] = $result['x-oss-request-id'];
268
            }
269
            
270
            return $fileInfo;
271
        } catch (OssException $e){
272
            throw new TinymengException($e->getMessage());
273
        }
274
    }
275
276
    /**
277
     * 更新文件
278
     * @param string $path
279
     * @param string $contents
280
     * @return array|bool|false
281
     */
282
    public function update($path, $contents)
283
    {
284
        return $this->write($path, $contents);
285
    }
286
287
    /**
288
     * 更新文件流
289
     * @param string $path
290
     * @param resource $resource
291
     * @return array|bool|false
292
     */
293
    public function updateStream($path, $resource){
294
        return $this->writeStream($path, $resource);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->writeStream($path, $resource) returns the type true which is incompatible with the return type mandated by tinymeng\uploads\Connect...terface::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...
295
    }
296
297
    /**
298
     * 列出目录文件
299
     * @param string $directory
300
     * @param bool|false $recursive
301
     * @return array
302
     * @throws TinymengException
303
     * @author Tinymeng <[email protected]>
304
     */
305
    public function listContents($directory = '', $recursive = false){
0 ignored issues
show
Unused Code introduced by
The parameter $recursive is not used and could be removed. ( Ignorable by Annotation )

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

305
    public function listContents($directory = '', /** @scrutinizer ignore-unused */ $recursive = false){

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
306
        try{
307
            $directory = static::normalizerPath($directory, true);
308
309
            $options = [
310
                'delimiter' => '/' ,
311
                'prefix'    => $directory,
312
                'max-keys'  => $this->config['max_keys'],
313
                'marker'    => '',
314
            ];
315
316
            $result_obj = $this->getClient()->listObjects($this->bucket, $options);
317
318
            $file_list  = $result_obj->getObjectList();//文件列表
319
            $dir_list   = $result_obj->getPrefixList();//文件夹列表
320
            $data       = [];
321
322
            if (is_array($dir_list) && count($dir_list) > 0 ) {
323
                foreach ($dir_list as $key => $dir) {
324
                    $data[] = [
325
                        'path'      => $dir->getPrefix(),
326
                        'prefix'    => $options['prefix'],
327
                        'marker'    => $options['marker'],
328
                        'file_type' => self::FILE_TYPE_DIR
329
                    ];
330
                }
331
            }
332
333
            if (is_array($file_list) && count($file_list) > 0 ) {
334
                foreach ($file_list as $key => $file) {
335
                    if ($key == 0 ) {
336
                        $data[] = [
337
                            'path'      => $file->getKey(),
338
                            'prefix'    => $options['prefix'],
339
                            'marker'    => $options['marker'],
340
                            'file_type' => self::FILE_TYPE_DIR
341
                        ];
342
                    } else {
343
                        $data[] = [
344
                            'path'              => $file->getKey(),
345
                            'last_modified'     => $file->getLastModified(),
346
                            'e_tag'             => $file->getETag(),
347
                            'file_size'         => $file->getSize(),
348
                            'prefix'            => $options['prefix'],
349
                            'marker'            => $options['marker'],
350
                            'file_type'         => self::FILE_TYPE_FILE,
351
                        ];
352
                    }
353
                }
354
            }
355
356
            return $data;
357
        }catch (Exception $e){
358
            throw new TinymengException($e->getMessage());
359
        }
360
    }
361
362
    /**
363
     * 获取资源的元信息,但不返回文件内容
364
     * @param $path
365
     * @return array|bool
366
     * @throws TinymengException
367
     * @author Tinymeng <[email protected]>
368
     */
369
    public function getMetadata($path)
370
    {
371
        try {
372
            $path = static::normalizerPath($path);
373
            $file_info = $this->getClient()->getObjectMeta($this->bucket, $path);
374
            if ( !empty($file_info) ) {
375
                return $file_info;
376
            }
377
        }catch (OssException $e) {
378
            throw new TinymengException($e->getMessage());
379
        }
380
        return false;
381
    }
382
383
    /**
384
     * 获得文件大小
385
     * @param string $path
386
     * @return array
387
     * @author Tinymeng <[email protected]>
388
     */
389
    public function getSize($path)
390
    {
391
        $file_info = $this->getMetadata($path);
392
        return $file_info != false && $file_info['content-length'] > 0 ? [ 'size' => $file_info['content-length'] ] : ['size' => 0];
393
    }
394
395
    /**
396
     * 获得文件Mime类型
397
     * @param string $path
398
     * @return mixed string|null
399
     * @author Tinymeng <[email protected]>
400
     */
401
    public function getMimetype($path)
402
    {
403
        $file_info = $this->getMetadata($path);
404
        return $file_info != false && !empty($file_info['content-type']) ? [ 'mimetype' => $file_info['content-type'] ] : false;
405
    }
406
407
    /**
408
     * 获得文件最后修改时间
409
     * @param string $path
410
     * @return array 时间戳
411
     * @author Tinymeng <[email protected]>
412
     */
413
    public function getTimestamp($path){
414
        $file_info = $this->getMetadata($path);
415
        return $file_info != false && !empty($file_info['last-modified'])
416
            ? ['timestamp' => strtotime($file_info['last-modified']) ]
417
            : ['timestamp' => 0 ];
418
    }
419
420
    /**
421
     * 获得文件模式 (未实现)
422
     * @param string $path
423
     * @author Tinymeng <[email protected]>
424
     * @return string
425
     */
426
    public function getVisibility($path){
0 ignored issues
show
Unused Code introduced by
The parameter $path is not used and could be removed. ( Ignorable by Annotation )

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

426
    public function getVisibility(/** @scrutinizer ignore-unused */ $path){

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
427
        return self::VISIBILITY_PUBLIC;
428
    }
429
430
    /**
431
     * 重命名文件
432
     * @param string $path
433
     * @param string $newpath
434
     * @return bool
435
     * @throws TinymengException
436
     * @internal param $oldname
437
     * @internal param $newname
438
     * @author Tinymeng <[email protected]>
439
     */
440
    public function rename($path, $newpath)
441
    {
442
        try {
443
            /**
444
             * 如果是一个资源,请保持最后不是以“/”结尾!
445
             *
446
             */
447
            $path = static::normalizerPath($path);
448
449
            $this->getClient()->copyObject($this->bucket, $path, $this->bucket, static::normalizerPath($newpath), []);
450
            return $this->delete($path);
451
        }catch (OssException $e){
452
            throw new TinymengException($e->getMessage());
453
        }
454
    }
455
456
    /**
457
     * 复制文件
458
     *
459
     * @param string $path
460
     * @param string $newpath
461
     * @return bool
462
     * @throws TinymengException
463
     * @author Tinymeng <[email protected]>
464
     */
465
    public function copy($path, $newpath)
466
    {
467
        try {
468
            $path = static::normalizerPath($path);
469
            $newpath = static::normalizerPath($newpath);
470
            $this->getClient()->copyObject($this->bucket, $path, $this->bucket, $newpath, []);
471
            return true;
472
        }catch (OssException $e){
473
            throw new TinymengException($e->getMessage());
474
        }
475
    }
476
477
    /**
478
     * 删除文件或者文件夹
479
     * @param string $path
480
     * @return bool
481
     * @throws TinymengException
482
     * @author Tinymeng <[email protected]>
483
     */
484
    public function delete($path)
485
    {
486
        try{
487
            $path = static::normalizerPath($path);
488
            return $this->getClient()->deleteObject($this->bucket, $path);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getClient(...t($this->bucket, $path) also could return the type array which is incompatible with the documented return type boolean.
Loading history...
489
        }catch (OssException $e){
490
            throw new TinymengException($e->getMessage());
491
        }
492
    }
493
494
    /**
495
     * 删除文件夹
496
     * @param string $path
497
     * @return mixed
498
     * @throws TinymengException
499
     * @author Tinymeng <[email protected]>
500
     */
501
    public function deleteDir($path)
502
    {
503
        try{
504
            //递归去删除全部文件
505
            return $this->recursiveDelete($path);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->recursiveDelete($path) targeting tinymeng\uploads\Gateways\Oss::recursiveDelete() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
506
        }catch (OssException $e){
507
            throw new TinymengException($e->getMessage());
508
        }
509
    }
510
511
    /**
512
     * 递归删除全部文件
513
     * @param $path
514
     * @author Tinymeng <[email protected]>
515
     */
516
    protected function recursiveDelete($path)
517
    {
518
        $file_list = $this->listContents($path);
519
520
        // 如果当前文件夹文件不为空,则直接去删除文件夹
521
        if ( is_array($file_list) && count($file_list) > 0 ) {
522
            foreach ($file_list as $file) {
523
                if ($file['path'] == $path) {
524
                    continue;
525
                }
526
                if ($file['file_type'] == self::FILE_TYPE_FILE) {
527
                    $this->delete($file['path']);
528
                } else {
529
                    $this->recursiveDelete($file['path']);
530
                }
531
            }
532
        }
533
534
        $this->getClient()->deleteObject($this->bucket, $path);
535
    }
536
537
    /**
538
     * 创建文件夹
539
     * @param string $dirname
540
     * @return array|false
541
     * @throws TinymengException
542
     * @author Tinymeng <[email protected]>
543
     */
544
    public function createDir($dirname)
545
    {
546
        try{
547
            return $this->getClient()->createObjectDir($this->bucket, static::normalizerPath($dirname, true));
548
        }catch (OssException $e){
549
            throw new TinymengException($e->getMessage());
550
        }
551
    }
552
553
    /**
554
     * 设置文件模式 (未实现)
555
     * @param string $path
556
     * @param string $visibility
557
     * @return bool
558
     * @author Tinymeng <[email protected]>
559
     */
560
    public function setVisibility($path, $visibility)
561
    {
562
        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 tinymeng\uploads\Connect...erface::setVisibility() 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...
563
    }
564
565
    /**
566
     * 获取当前文件的URL访问路径
567
     * @param  string $file 文件名
568
     * @param  integer $expire_at 有效期,单位:秒(0 表示永久有效,使用路径前缀)
569
     * @return string
570
     * @throws TinymengException
571
     * @author Tinymeng <[email protected]>
572
     */
573
    public function getUrl($file, $expire_at = 3600)
574
    {
575
        try {
576
            $file = static::normalizerPath($file);
577
578
            // 处理 endpoint,去除可能包含的协议
579
            $endpoint = $this->config['endpoint'];
580
            $endpoint = preg_replace('#^https?://#', '', $endpoint);
581
            
582
            // 构建完整的基础 URL
583
            // 如果使用自定义域名(isCName),直接使用 endpoint,否则使用 bucket.endpoint 格式
584
            if (!empty($this->config['isCName'])) {
585
                $baseUrl = rtrim($this->config['transport'] . '://' . $endpoint, '/');
586
            } else {
587
                $baseUrl = rtrim($this->config['transport'] . '://' . $this->config['bucket'] . '.' . $endpoint, '/');
588
            }
589
            $filePath = '/' . ltrim($file, '/');
590
            $fullUrl = $baseUrl . $filePath;
591
592
            // 如果不需要签名 URL,直接返回完整 URL
593
            if ($expire_at == 0) {
594
                return $fullUrl;
595
            }
596
597
            // 生成签名 URL
598
            $accessUrl = $this->getClient()->signUrl($this->bucket, $file, $expire_at);
599
            return $accessUrl;
600
        } catch (OssException $e) {
601
            throw new TinymengException($e->getMessage());
602
        }
603
    }
604
605
}