Passed
Push — master ( cd1b64...6432aa )
by frey
47s
created

Adapter   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 424
Duplicated Lines 15.57 %

Coupling/Cohesion

Components 2
Dependencies 5

Test Coverage

Coverage 49.23%

Importance

Changes 0
Metric Value
wmc 45
lcom 2
cbo 5
dl 66
loc 424
ccs 64
cts 130
cp 0.4923
rs 8.3673
c 0
b 0
f 0

26 Methods

Rating   Name   Duplication   Size   Complexity  
A getBucket() 0 4 1
A getUrl() 0 4 1
A writeStream() 15 15 2
A updateStream() 15 15 2
A rename() 0 6 1
A copy() 0 6 1
A delete() 0 6 1
A deleteDir() 0 6 1
A createDir() 0 6 1
A setVisibility() 0 9 2
A has() 0 8 2
A read() 0 6 2
A readStream() 0 6 2
A listContents() 0 6 1
A getMetadata() 0 6 1
A getTimestamp() 0 6 2
A getTemporaryPath() 0 4 1
A setContentType() 0 10 1
A normalizeResponse() 0 12 4
A write() 15 15 2
A update() 15 15 2
A getSize() 0 6 2
A getMimetype() 0 7 2
B getVisibility() 6 14 5
A createTemporaryFile() 0 17 2
A __construct() 0 9 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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\QcloudCOSv4;
4
5
use Freyo\Flysystem\QcloudCOSv4\Exceptions\RuntimeException;
6
use League\Flysystem\Adapter\AbstractAdapter;
7
use League\Flysystem\AdapterInterface;
8
use League\Flysystem\Config;
9
use League\Flysystem\Util;
10
use QCloud\Cos\Api;
11
12
/**
13
 * Class Adapter.
14
 */
15
class Adapter extends AbstractAdapter
16
{
17
    /**
18
     * @var Api
19
     */
20
    protected $cosApi;
21
22
    /**
23
     * @var string
24
     */
25
    protected $bucket;
26
27
    /**
28
     * @var bool
29
     */
30
    protected $debug;
31
32
    /**
33
     * Adapter constructor.
34
     *
35
     * @param Api   $cosApi
36
     * @param array $config
37
     */
38
    public function __construct(Api $cosApi, array $config)
39
    {
40
        $this->cosApi = $cosApi;
41
42
        $this->bucket = $config['bucket'];
43
        $this->debug = $config['debug'];
44
45
        $this->setPathPrefix($config['protocol'].'://'.$config['domain'].'/');
46
    }
47
48
    /**
49
     * @return string
50
     */
51 15
    public function getBucket()
52
    {
53 15
        return $this->bucket;
54
    }
55
56
    /**
57
     * @param string $path
58
     *
59
     * @return string
60
     */
61 1
    public function getUrl($path)
62
    {
63 1
        return $this->applyPathPrefix($path);
64
    }
65
66
    /**
67
     * @param string $path
68
     * @param string $contents
69
     * @param Config $config
70
     *
71
     * @throws RuntimeException
72
     *
73
     * @return array|bool
74
     */
75 View Code Duplication
    public function write($path, $contents, Config $config)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
76
    {
77
        $temporaryPath = $this->createTemporaryFile($contents);
78
79
        $response = $this->cosApi->upload($this->getBucket(), $temporaryPath, $path,
0 ignored issues
show
Bug introduced by
It seems like $temporaryPath defined by $this->createTemporaryFile($contents) on line 77 can also be of type boolean; however, QCloud\Cos\Api::upload() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
80
            null, null, $config->get('insertOnly', 1));
81
82
        $response = $this->normalizeResponse($response);
83
84
        if (false !== $response) {
85
            $this->setContentType($path, $contents);
86
        }
87
88
        return $response;
89
    }
90
91
    /**
92
     * @param string   $path
93
     * @param resource $resource
94
     * @param Config   $config
95
     *
96
     * @throws RuntimeException
97
     *
98
     * @return array|bool
99
     */
100 1 View Code Duplication
    public function writeStream($path, $resource, Config $config)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
101
    {
102 1
        $uri = stream_get_meta_data($resource)['uri'];
103
104 1
        $response = $this->cosApi->upload($this->getBucket(), $uri, $path,
105 1
            null, null, $config->get('insertOnly', 1));
106
107 1
        $response = $this->normalizeResponse($response);
108
109
        if (false !== $response) {
110
            $this->setContentType($path, stream_get_contents($resource));
111
        }
112
113
        return $response;
114
    }
115
116
    /**
117
     * @param string $path
118
     * @param string $contents
119
     * @param Config $config
120
     *
121
     * @throws RuntimeException
122
     *
123
     * @return array|bool
124
     */
125 View Code Duplication
    public function update($path, $contents, Config $config)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
126
    {
127
        $temporaryPath = $this->createTemporaryFile($contents);
128
129
        $response = $this->cosApi->upload($this->getBucket(), $temporaryPath, $path,
0 ignored issues
show
Bug introduced by
It seems like $temporaryPath defined by $this->createTemporaryFile($contents) on line 127 can also be of type boolean; however, QCloud\Cos\Api::upload() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
130
            null, null, $config->get('insertOnly', 0));
131
132
        $response = $this->normalizeResponse($response);
133
134
        if (false !== $response) {
135
            $this->setContentType($path, $contents);
136
        }
137
138
        return $response;
139
    }
140
141
    /**
142
     * @param string   $path
143
     * @param resource $resource
144
     * @param Config   $config
145
     *
146
     * @throws RuntimeException
147
     *
148
     * @return array|bool
149
     */
150 1 View Code Duplication
    public function updateStream($path, $resource, Config $config)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
151
    {
152 1
        $uri = stream_get_meta_data($resource)['uri'];
153
154 1
        $response = $this->cosApi->upload($this->getBucket(), $uri, $path,
155 1
            null, null, $config->get('insertOnly', 0));
156
157 1
        $response = $this->normalizeResponse($response);
158
159
        if (false !== $response) {
160
            $this->setContentType($path, stream_get_contents($resource));
161
        }
162
163
        return $response;
164
    }
165
166
    /**
167
     * @param string $path
168
     * @param string $newpath
169
     *
170
     * @return bool
171
     */
172 1
    public function rename($path, $newpath)
173
    {
174 1
        return (bool) $this->normalizeResponse(
175 1
            $this->cosApi->moveFile($this->getBucket(), $path, $newpath, true)
176 1
        );
177
    }
178
179
    /**
180
     * @param string $path
181
     * @param string $newpath
182
     *
183
     * @return bool
184
     */
185 1
    public function copy($path, $newpath)
186
    {
187 1
        return (bool) $this->normalizeResponse(
188 1
            $this->cosApi->copyFile($this->getBucket(), $path, $newpath, true)
189 1
        );
190
    }
191
192
    /**
193
     * @param string $path
194
     *
195
     * @return bool
196
     */
197 1
    public function delete($path)
198
    {
199 1
        return (bool) $this->normalizeResponse(
200 1
            $this->cosApi->delFile($this->getBucket(), $path)
201 1
        );
202
    }
203
204
    /**
205
     * @param string $dirname
206
     *
207
     * @return bool
208
     */
209 1
    public function deleteDir($dirname)
210
    {
211 1
        return (bool) $this->normalizeResponse(
212 1
            $this->cosApi->delFolder($this->getBucket(), $dirname)
213 1
        );
214
    }
215
216
    /**
217
     * @param string $dirname
218
     * @param Config $config
219
     *
220
     * @return array|bool
221
     */
222 1
    public function createDir($dirname, Config $config)
223
    {
224 1
        return $this->normalizeResponse(
225 1
            $this->cosApi->createFolder($this->getBucket(), $dirname)
226 1
        );
227
    }
228
229
    /**
230
     * @param string $path
231
     * @param string $visibility
232
     *
233
     * @return bool
234
     */
235 1
    public function setVisibility($path, $visibility)
236
    {
237 1
        $visibility = ($visibility === AdapterInterface::VISIBILITY_PUBLIC)
238 1
            ? 'eWPrivateRPublic' : 'eWRPrivate';
239
240 1
        return (bool) $this->normalizeResponse(
0 ignored issues
show
Bug Best Practice introduced by
The return type of return (bool) $this->nor...h, null, $visibility)); (boolean) is incompatible with the return type declared by the interface League\Flysystem\AdapterInterface::setVisibility of type array|false.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
241 1
            $this->cosApi->update($this->getBucket(), $path, null, $visibility)
242 1
        );
243
    }
244
245
    /**
246
     * @param string $path
247
     *
248
     * @return bool
249
     */
250 1
    public function has($path)
251
    {
252
        try {
253 1
            return (bool) $this->getMetadata($path);
254 1
        } catch (RuntimeException $exception) {
255 1
            return false;
256
        }
257
    }
258
259
    /**
260
     * @param string $path
261
     *
262
     * @return array|bool
263
     */
264
    public function read($path)
265
    {
266
        $contents = file_get_contents($this->applyPathPrefix($path));
267
268
        return $contents !== false ? compact('contents') : false;
269
    }
270
271
    /**
272
     * @param string $path
273
     *
274
     * @return array|bool
275
     */
276
    public function readStream($path)
277
    {
278
        $stream = fopen($this->applyPathPrefix($path), 'r');
279
280
        return $stream !== false ? compact('stream') : false;
281
    }
282
283
    /**
284
     * @param string $directory
285
     * @param bool   $recursive
286
     *
287
     * @return array|bool
288
     */
289 1
    public function listContents($directory = '', $recursive = false)
290
    {
291 1
        return $this->normalizeResponse(
292 1
            $this->cosApi->listFolder($this->getBucket(), $directory)
293 1
        );
294
    }
295
296
    /**
297
     * @param string $path
298
     *
299
     * @return array|bool
300
     */
301 6
    public function getMetadata($path)
302
    {
303 6
        return $this->normalizeResponse(
304 6
            $this->cosApi->stat($this->getBucket(), $path)
305 6
        );
306
    }
307
308
    /**
309
     * @param string $path
310
     *
311
     * @return array|bool
312
     */
313 1
    public function getSize($path)
314
    {
315 1
        $stat = $this->getMetadata($path);
316
317
        return isset($stat['filesize']) ? ['size' => $stat['filesize']] : false;
318
    }
319
320
    /**
321
     * @param string $path
322
     *
323
     * @return array|bool
324
     */
325 1
    public function getMimetype($path)
326
    {
327 1
        $stat = $this->getMetadata($path);
328
329
        return isset($stat['custom_headers']['Content-Type'])
330
            ? ['mimetype' => $stat['custom_headers']['Content-Type']] : false;
331
    }
332
333
    /**
334
     * @param string $path
335
     *
336
     * @return array|bool
337
     */
338 1
    public function getTimestamp($path)
339
    {
340 1
        $stat = $this->getMetadata($path);
341
342
        return isset($stat['ctime']) ? ['timestamp' => $stat['ctime']] : false;
343
    }
344
345
    /**
346
     * @param string $path
347
     *
348
     * @return array|bool
349
     */
350 1
    public function getVisibility($path)
351
    {
352 1
        $stat = $this->getMetadata($path);
353
354 View Code Duplication
        if (isset($stat['authority']) && $stat['authority'] === 'eWPrivateRPublic') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
355
            return ['visibility' => AdapterInterface::VISIBILITY_PUBLIC];
356
        }
357
358 View Code Duplication
        if (isset($stat['authority']) && $stat['authority'] === 'eWRPrivate') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
359
            return ['visibility' => AdapterInterface::VISIBILITY_PRIVATE];
360
        }
361
362
        return false;
363
    }
364
365
    /**
366
     * Creates a temporary file.
367
     *
368
     * @param string $content
369
     *
370
     * @throws RuntimeException
371
     *
372
     * @return string
373
     */
374
    protected function createTemporaryFile($content)
375
    {
376
        $temporaryPath = $this->getTemporaryPath();
377
378
        if (false === $temporaryPath) {
379
            throw new RuntimeException("Unable to create temporary file in '{$temporaryPath}'.");
380
        }
381
382
        file_put_contents($temporaryPath, $content);
383
384
        // The file is automatically removed when closed, or when the script ends.
385
        register_shutdown_function(function () use ($temporaryPath) {
386
            unlink($temporaryPath);
387
        });
388
389
        return $temporaryPath;
390
    }
391
392
    /**
393
     * Gets a temporary file path.
394
     *
395
     * @return bool|string
396
     */
397
    protected function getTemporaryPath()
398
    {
399
        return tempnam(sys_get_temp_dir(), uniqid('tencentyun', true));
400
    }
401
402
    /**
403
     * @param string $path
404
     * @param string $content
405
     *
406
     * @return bool
407
     */
408
    protected function setContentType($path, $content)
409
    {
410
        $custom_headers = [
411
            'Content-Type' => Util::guessMimeType($path, $content),
412
        ];
413
414
        return $this->normalizeResponse(
415
            $this->cosApi->update($this->getBucket(), $path, null, null, $custom_headers)
416
        );
417
    }
418
419
    /**
420
     * @param $response
421
     *
422
     * @throws RuntimeException
423
     *
424
     * @return mixed
425
     */
426 15
    protected function normalizeResponse($response)
427
    {
428 15
        if ($response['code'] == 0) {
429
            return isset($response['data']) ? $response['data'] : true;
430
        }
431
432 15
        if ($this->debug) {
433 15
            throw new RuntimeException($response['message'], $response['code']);
434
        }
435
436
        return false;
437
    }
438
}
439