Passed
Push — master ( 9942bf...774758 )
by Luo
01:01 queued 10s
created

OssAdapter   F

Complexity

Total Complexity 70

Size/Duplication

Total Lines 750
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 27
Bugs 13 Features 0
Metric Value
eloc 247
c 27
b 13
f 0
dl 0
loc 750
ccs 0
cts 284
cp 0
rs 2.8
wmc 70

31 Methods

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

How to fix   Complexity   

Complex Class

Complex classes like OssAdapter often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use OssAdapter, and based on these observations, apply Extract Interface, too.

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

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