Completed
Branch master (b31114)
by frey
04:16
created

Adapter   C

Complexity

Total Complexity 54

Size/Duplication

Total Lines 490
Duplicated Lines 0 %

Test Coverage

Coverage 83.98%

Importance

Changes 0
Metric Value
eloc 154
dl 0
loc 490
ccs 152
cts 181
cp 0.8398
rs 6.4799
c 0
b 0
f 0
wmc 54

29 Methods

Rating   Name   Duplication   Size   Complexity  
A getMetadata() 0 6 1
A write() 0 5 1
A rename() 0 7 1
A getVisibility() 0 17 5
A copy() 0 5 1
A getTimestamp() 0 6 2
A updateStream() 0 3 1
A listObjects() 0 6 3
A getMimetype() 0 6 2
A getAppId() 0 3 1
A getHttpClient() 0 6 1
A delete() 0 5 1
A normalizeFileInfo() 0 13 2
A getBucket() 0 3 1
A readStream() 0 15 3
A has() 0 6 2
A read() 0 20 5
A deleteDir() 0 15 2
A getTemporaryUrl() 0 16 3
A listContents() 0 11 2
A getSourcePath() 0 4 1
A update() 0 3 1
A createDir() 0 6 1
A setVisibility() 0 14 2
A __construct() 0 6 1
A getSize() 0 6 2
A writeStream() 0 5 1
A getRegion() 0 4 2
A getUrl() 0 14 3

How to fix   Complexity   

Complex Class

Complex classes like Adapter 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 Adapter, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Freyo\Flysystem\QcloudCOSv5;
4
5
use Carbon\Carbon;
6
use DateTimeInterface;
7
use League\Flysystem\Adapter\AbstractAdapter;
8
use League\Flysystem\Adapter\CanOverwriteFiles;
9
use League\Flysystem\AdapterInterface;
10
use League\Flysystem\Config;
11
use Qcloud\Cos\Client;
12
use Qcloud\Cos\Exception\NoSuchKeyException;
13
14
/**
15
 * Class Adapter.
16
 */
17
class Adapter extends AbstractAdapter implements CanOverwriteFiles
18
{
19
    /**
20
     * @var Client
21
     */
22
    protected $client;
23
24
    /**
25
     * @var array
26
     */
27
    protected $config = [];
28
29
    /**
30
     * @var array
31
     */
32
    protected $regionMap = [
33
        'cn-east'      => 'ap-shanghai',
34
        'cn-sorth'     => 'ap-guangzhou',
35
        'cn-north'     => 'ap-beijing-1',
36
        'cn-south-2'   => 'ap-guangzhou-2',
37
        'cn-southwest' => 'ap-chengdu',
38
        'sg'           => 'ap-singapore',
39
        'tj'           => 'ap-beijing-1',
40
        'bj'           => 'ap-beijing',
41
        'sh'           => 'ap-shanghai',
42
        'gz'           => 'ap-guangzhou',
43
        'cd'           => 'ap-chengdu',
44
        'sgp'          => 'ap-singapore',
45
    ];
46
47
    /**
48
     * Adapter constructor.
49
     *
50
     * @param Client $client
51
     * @param array  $config
52
     */
53
    public function __construct(Client $client, array $config)
54
    {
55
        $this->client = $client;
56
        $this->config = $config;
57
58
        $this->setPathPrefix($config['cdn']);
59
    }
60
61
    /**
62
     * @return string
63
     */
64 19
    public function getBucket()
65 1
    {
66 19
        return $this->config['bucket'];
67
    }
68
69
    /**
70
     * @return string
71
     */
72 2
    public function getAppId()
73
    {
74 2
        return $this->config['credentials']['appId'];
75
    }
76
77
    /**
78
     * @return string
79
     */
80 2
    public function getRegion()
81
    {
82 2
        return array_key_exists($this->config['region'], $this->regionMap)
83 2
            ? $this->regionMap[$this->config['region']] : $this->config['region'];
84
    }
85
86
    /**
87
     * @param $path
88
     *
89
     * @return string
90
     */
91 2
    public function getSourcePath($path)
92
    {
93 2
        return sprintf('%s-%s.cos.%s.myqcloud.com/%s',
94 2
            $this->getBucket(), $this->getAppId(), $this->getRegion(), $path
95 2
        );
96
    }
97
98
    /**
99
     * @param string $path
100
     *
101
     * @return string
102
     */
103 4
    public function getUrl($path)
104 4
    {
105 3
        if ($this->config['cdn']) {
106 3
            return $this->applyPathPrefix($path);
107
        }
108
109
        $objectUrl = $this->client->getObjectUrl(
110 4
            $this->getBucket(), $path, null,
111
                ['Scheme' => isset($this->config['scheme']) ? $this->config['scheme'] : 'http']
112 4
        );
113
114
        $url = parse_url($objectUrl);
115
116
        return sprintf('%s://%s%s', $url['scheme'], $url['host'], urldecode($url['path']));
117
    }
118
119
    /**
120
     * @param string             $path
121
     * @param \DateTimeInterface $expiration
122
     * @param array              $options
123
     *
124
     * @return string
125
     */
126 1
    public function getTemporaryUrl($path, DateTimeInterface $expiration, array $options = [])
127
    {
128 1
        $options = array_merge($options,
129 1
            ['Scheme' => isset($this->config['scheme']) ? $this->config['scheme'] : 'http']);
130
131 1
        $objectUrl = $this->client->getObjectUrl(
132 1
            $this->getBucket(), $path, $expiration->format('c'), $options
133 1
        );
134
135 1
        $url = parse_url($objectUrl);
136
137 1
        if ($this->config['cdn']) {
138 1
            return $this->config['cdn'].urldecode($url['path']).'?'.$url['query'];
139
        }
140
141
        return sprintf('%s://%s%s?%s', $url['scheme'], $url['host'], urldecode($url['path']), $url['query']);
142
    }
143
144
    /**
145
     * @param string $path
146
     * @param string $contents
147
     * @param Config $config
148
     *
149
     * @return array|bool
150
     */
151 2
    public function write($path, $contents, Config $config)
152
    {
153 2
        return $this->client->upload($this->getBucket(), $path, $contents, [
154
            'params' => [
155 2
                'ACL' => $config->get('visibility', 'public-read'),
156 2
            ],
157 2
        ]);
158
    }
159
160
    /**
161
     * @param string   $path
162
     * @param resource $resource
163
     * @param Config   $config
164
     *
165
     * @return array|bool
166
     */
167 4
    public function writeStream($path, $resource, Config $config)
168
    {
169 4
        return $this->client->upload($this->getBucket(), $path, stream_get_contents($resource, -1, 0), [
170
            'params' => [
171 4
                'ACL' => $config->get('visibility', 'public-read'),
172 2
            ],
173 2
        ]);
174
    }
175
176
    /**
177
     * @param string $path
178
     * @param string $contents
179
     * @param Config $config
180
     *
181
     * @return array|bool
182
     */
183 1
    public function update($path, $contents, Config $config)
184
    {
185 1
        return $this->write($path, $contents, $config);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->write($path, $contents, $config) also could return the type boolean which is incompatible with the return type mandated by League\Flysystem\AdapterInterface::update() of false|array.
Loading history...
186
    }
187
188
    /**
189
     * @param string   $path
190
     * @param resource $resource
191
     * @param Config   $config
192
     *
193
     * @return array|bool
194
     */
195 1
    public function updateStream($path, $resource, Config $config)
196
    {
197 1
        return $this->writeStream($path, $resource, $config);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->writeStrea...th, $resource, $config) also could return the type boolean which is incompatible with the return type mandated by League\Flysystem\AdapterInterface::updateStream() of false|array.
Loading history...
198
    }
199
200
    /**
201
     * @param string $path
202
     * @param string $newpath
203
     *
204
     * @return bool
205
     */
206 1
    public function rename($path, $newpath)
207
    {
208 1
        $result = $this->copy($path, $newpath);
209
210 1
        $this->delete($path);
211
212 1
        return $result;
213
    }
214
215
    /**
216
     * @param string $path
217
     * @param string $newpath
218
     *
219
     * @return bool
220
     */
221 2
    public function copy($path, $newpath)
222
    {
223 2
        $source = $this->getSourcePath($path);
224
225 2
        return (bool) $this->client->copy($this->getBucket(), $newpath, $source);
226
    }
227
228
    /**
229
     * @param string $path
230
     *
231
     * @return bool
232
     */
233 2
    public function delete($path)
234
    {
235 2
        return (bool) $this->client->deleteObject([
236 2
            'Bucket' => $this->getBucket(),
237 2
            'Key'    => $path,
238 2
        ]);
239
    }
240
241
    /**
242
     * @param string $dirname
243
     *
244
     * @return bool
245
     */
246 1
    public function deleteDir($dirname)
247
    {
248 1
        $response = $this->listObjects($dirname);
249
250 1
        if (!isset($response['Contents'])) {
251
            return true;
252
        }
253
254 1
        $keys = array_map(function ($item) {
255 1
            return ['Key' => $item['Key']];
256 1
        }, (array) $response['Contents']);
257
258 1
        return (bool) $this->client->deleteObjects([
259 1
            'Bucket'  => $this->getBucket(),
260 1
            'Objects' => $keys,
261 1
        ]);
262
    }
263
264
    /**
265
     * @param string $dirname
266
     * @param Config $config
267
     *
268
     * @return array|bool
269
     */
270 1
    public function createDir($dirname, Config $config)
271
    {
272 1
        return $this->client->putObject([
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->client->pu..._blank', 'Body' => '')) returns the type Guzzle\Http\Message\Response which is incompatible with the documented return type boolean|array.
Loading history...
273 1
            'Bucket' => $this->getBucket(),
274 1
            'Key'    => $dirname.'/_blank',
275 1
            'Body'   => '',
276 1
        ]);
277
    }
278
279
    /**
280
     * @param string $path
281
     * @param string $visibility
282
     *
283
     * @return bool
284
     */
285 1
    public function setVisibility($path, $visibility)
286
    {
287
        switch ($visibility) {
288 1
            case AdapterInterface::VISIBILITY_PUBLIC:
289
                $visibility = 'public-read';
290
                break;
291 1
            default:
292
                //
293 1
        }
294
295 1
        return (bool) $this->client->PutObjectAcl([
0 ignored issues
show
Bug Best Practice introduced by
The expression return (bool)$this->clie... 'ACL' => $visibility)) returns the type boolean which is incompatible with the return type mandated by League\Flysystem\AdapterInterface::setVisibility() of false|array.

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...
296 1
            'Bucket' => $this->getBucket(),
297 1
            'Key'    => $path,
298 1
            'ACL'    => $visibility,
299 1
        ]);
300
    }
301
302
    /**
303
     * @param string $path
304
     *
305
     * @return bool
306
     */
307 1
    public function has($path)
308
    {
309
        try {
310 1
            return (bool) $this->getMetadata($path);
311
        } catch (NoSuchKeyException $e) {
312
            return false;
313
        }
314
    }
315
316
    /**
317
     * @param string $path
318
     *
319
     * @return array|bool
320
     */
321 1
    public function read($path)
322
    {
323
        try {
324 1
            if (isset($this->config['read_from_cdn']) && $this->config['read_from_cdn']) {
325
                $response = $this->getHttpClient()
326
                                 ->get($this->getTemporaryUrl($path, Carbon::now()->addMinutes(5)))
327
                                 ->getBody()
328
                                 ->getContents();
329
            } else {
330 1
                $response = $this->client->getObject([
331 1
                    'Bucket' => $this->getBucket(),
332 1
                    'Key'    => $path,
333 1
                ])->get('Body');
0 ignored issues
show
Bug introduced by
The method get() does not exist on Guzzle\Http\Message\Response. ( Ignorable by Annotation )

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

333
                ])->/** @scrutinizer ignore-call */ get('Body');

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
334
            }
335
336 1
            return ['contents' => (string)$response];
337
        } catch (NoSuchKeyException $e) {
338
            return false;
339
        } catch (\GuzzleHttp\Exception\ClientException $e) {
340
            return false;
341
        }
342
    }
343
344
    /**
345
     * @return \GuzzleHttp\Client
346
     */
347 1
    public function getHttpClient()
348
    {
349 1
        return $client = (new \GuzzleHttp\Client([
0 ignored issues
show
Unused Code introduced by
The assignment to $client is dead and can be removed.
Loading history...
350 1
            'verify'          => false,
351 1
            'timeout'         => $this->config['timeout'],
352 1
            'connect_timeout' => $this->config['connect_timeout'],
353 1
        ]));
354
    }
355
356
    /**
357
     * @param string $path
358
     *
359
     * @return array|bool
360
     */
361 1
    public function readStream($path)
362
    {
363
        try {
364 1
            $temporaryUrl = $this->getTemporaryUrl($path, Carbon::now()->addMinutes(5));
365
366 1
            $stream = $this->getHttpClient()
367 1
                           ->get($temporaryUrl, ['stream' => true])
368 1
                           ->getBody()
369 1
                           ->detach();
370
371 1
            return ['stream' => $stream];
372
        } catch (NoSuchKeyException $e) {
373
            return false;
374
        } catch (\GuzzleHttp\Exception\ClientException $e) {
375
            return false;
376
        }
377
    }
378
379
    /**
380
     * @param string $directory
381
     * @param bool   $recursive
382
     *
383
     * @return array|bool
384
     */
385 1
    public function listContents($directory = '', $recursive = false)
386
    {
387 1
        $list = [];
388
389 1
        $response = $this->listObjects($directory, $recursive);
390
391 1
        foreach ((array) $response->get('Contents') as $content) {
392 1
            $list[] = $this->normalizeFileInfo($content);
393 1
        }
394
395 1
        return $list;
396
    }
397
398
    /**
399
     * @param string $path
400
     *
401
     * @return array|bool
402
     */
403 5
    public function getMetadata($path)
404
    {
405 5
        return $this->client->headObject([
406 5
            'Bucket' => $this->getBucket(),
407 5
            'Key'    => $path,
408 5
        ])->toArray();
0 ignored issues
show
Bug introduced by
The method toArray() does not exist on Guzzle\Http\Message\Response. ( Ignorable by Annotation )

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

408
        ])->/** @scrutinizer ignore-call */ toArray();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
409
    }
410
411
    /**
412
     * @param string $path
413
     *
414
     * @return array|bool
415
     */
416 1
    public function getSize($path)
417
    {
418 1
        $meta = $this->getMetadata($path);
419
420 1
        return isset($meta['ContentLength'])
421 1
            ? ['size' => $meta['ContentLength']] : false;
422
    }
423
424
    /**
425
     * @param string $path
426
     *
427
     * @return array|bool
428
     */
429 1
    public function getMimetype($path)
430
    {
431 1
        $meta = $this->getMetadata($path);
432
433 1
        return isset($meta['ContentType'])
434 1
            ? ['mimetype' => $meta['ContentType']] : false;
435
    }
436
437
    /**
438
     * @param string $path
439
     *
440
     * @return array|bool
441
     */
442 1
    public function getTimestamp($path)
443
    {
444 1
        $meta = $this->getMetadata($path);
445
446 1
        return isset($meta['LastModified'])
447 1
            ? ['timestamp' => strtotime($meta['LastModified'])] : false;
448
    }
449
450
    /**
451
     * @param string $path
452
     *
453
     * @return array|bool
454
     */
455 1
    public function getVisibility($path)
456
    {
457 1
        $meta = $this->client->getObjectAcl([
458 1
            'Bucket' => $this->getBucket(),
459 1
            'Key'    => $path,
460 1
        ]);
461
462 1
        foreach ($meta->get('Grants') as $grant) {
463 1
            if (isset($grant['Grantee']['URI'])
464 1
                && $grant['Permission'] === 'READ'
465 1
                && strpos($grant['Grantee']['URI'], 'global/AllUsers') !== false
466 1
            ) {
467
                return ['visibility' => AdapterInterface::VISIBILITY_PUBLIC];
468
            }
469 1
        }
470
471 1
        return ['visibility' => AdapterInterface::VISIBILITY_PRIVATE];
472
    }
473
474
    /**
475
     * @param array $content
476
     *
477
     * @return array
478
     */
479 1
    private function normalizeFileInfo(array $content)
480
    {
481 1
        $path = pathinfo($content['Key']);
482
483
        return [
484 1
            'type'      => 'file',
485 1
            'path'      => $content['Key'],
486 1
            'timestamp' => Carbon::parse($content['LastModified'])->getTimestamp(),
487 1
            'size'      => (int) $content['Size'],
488 1
            'dirname'   => (string) $path['dirname'],
489 1
            'basename'  => (string) $path['basename'],
490 1
            'extension' => isset($path['extension']) ? $path['extension'] : '',
491 1
            'filename'  => (string) $path['filename'],
492 1
        ];
493
    }
494
495
    /**
496
     * @param string $directory
497
     * @param bool   $recursive
498
     *
499
     * @return mixed
500
     */
501 2
    private function listObjects($directory = '', $recursive = false)
502
    {
503 2
        return $this->client->listObjects([
504 2
            'Bucket'    => $this->getBucket(),
505 2
            'Prefix'    => ((string) $directory === '') ? '' : ($directory.'/'),
506 2
            'Delimiter' => $recursive ? '' : '/',
507 2
        ]);
508
    }
509
}
510