Passed
Push — master ( 65fed3...c68220 )
by frey
05:44 queued 02:42
created

Adapter   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 446
Duplicated Lines 20.18 %

Coupling/Cohesion

Components 2
Dependencies 6

Test Coverage

Coverage 44.37%

Importance

Changes 6
Bugs 2 Features 0
Metric Value
wmc 45
c 6
b 2
f 0
lcom 2
cbo 6
dl 90
loc 446
ccs 63
cts 142
cp 0.4437
rs 8.3673

26 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 14 1
A getBucket() 0 4 1
A getUrl() 0 4 1
B write() 25 25 3
A writeStream() 17 17 2
B update() 25 25 3
A updateStream() 17 17 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 8 2
A has() 0 8 2
A read() 0 4 1
A readStream() 0 4 1
A listContents() 0 6 1
A getMetadata() 0 6 1
A getTimestamp() 0 10 2
A getSize() 0 10 2
A getMimetype() 0 10 2
A writeTempFile() 0 10 1
A deleteTempFile() 0 8 2
A setContentType() 0 10 1
A normalizeResponse() 0 12 4
B getVisibility() 6 14 5

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\Client\Conf;
6
use Freyo\Flysystem\QcloudCOSv4\Client\Cosapi;
7
use Freyo\Flysystem\QcloudCOSv4\Exceptions\RuntimeException;
8
use League\Flysystem\Adapter\AbstractAdapter;
9
use League\Flysystem\AdapterInterface;
10
use League\Flysystem\Config;
11
use League\Flysystem\Util;
12
13
/**
14
 * Class Adapter.
15
 */
16
class Adapter extends AbstractAdapter
17
{
18
    /**
19
     * @var
20
     */
21
    protected $bucket;
22
23
    /**
24
     * @var
25
     */
26
    protected $debug;
27
28
    /**
29
     * Adapter constructor.
30
     *
31
     * @param $config
32
     */
33
    public function __construct($config)
34
    {
35
        Conf::setAppId($config['app_id']);
36
        Conf::setSecretId($config['secret_id']);
37
        Conf::setSecretKey($config['secret_key']);
38
39
        $this->bucket = $config['bucket'];
40
        $this->debug = $config['debug'];
41
42
        $this->setPathPrefix($config['protocol'].'://'.$config['domain'].'/');
43
44
        Cosapi::setTimeout($config['timeout']);
45
        Cosapi::setRegion($config['region']);
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
        $tmpfname = $this->writeTempFile($contents);
78
79
        try {
80
            $response = Cosapi::upload($this->getBucket(), $tmpfname, $path,
0 ignored issues
show
Bug introduced by
It seems like $tmpfname defined by $this->writeTempFile($contents) on line 77 can also be of type boolean; however, Freyo\Flysystem\QcloudCO...Client\Cosapi::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...
81
                                        null, null, $config->get('insertOnly', 1));
82
83
            $this->deleteTempFile($tmpfname);
84
85
            $response = $this->normalizeResponse($response);
86
87
            if (false === $response) {
88
                return false;
89
            }
90
91
            $this->setContentType($path, $contents);
92
        } catch (RuntimeException $exception) {
93
            $this->deleteTempFile($tmpfname);
94
95
            throw $exception;
96
        }
97
98
        return $response;
99
    }
100
101
    /**
102
     * @param string   $path
103
     * @param resource $resource
104
     * @param Config   $config
105
     *
106
     * @throws RuntimeException
107
     *
108
     * @return array|bool
109
     */
110 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...
111
    {
112 1
        $uri = stream_get_meta_data($resource)['uri'];
113
114 1
        $response = Cosapi::upload($this->getBucket(), $uri, $path,
115 1
                                    null, null, $config->get('insertOnly', 1));
116
117 1
        $response = $this->normalizeResponse($response);
118
119
        if (false === $response) {
120
            return false;
121
        }
122
123
        $this->setContentType($path, stream_get_contents($resource));
124
125
        return $response;
126
    }
127
128
    /**
129
     * @param string $path
130
     * @param string $contents
131
     * @param Config $config
132
     *
133
     * @throws RuntimeException
134
     *
135
     * @return array|bool
136
     */
137 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...
138
    {
139
        $tmpfname = $this->writeTempFile($contents);
140
141
        try {
142
            $response = Cosapi::upload($this->getBucket(), $tmpfname, $path,
0 ignored issues
show
Bug introduced by
It seems like $tmpfname defined by $this->writeTempFile($contents) on line 139 can also be of type boolean; however, Freyo\Flysystem\QcloudCO...Client\Cosapi::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...
143
                                        null, null, $config->get('insertOnly', 0));
144
145
            $this->deleteTempFile($tmpfname);
146
147
            $response = $this->normalizeResponse($response);
148
149
            if (false === $response) {
150
                return false;
151
            }
152
153
            $this->setContentType($path, $contents);
154
        } catch (RuntimeException $exception) {
155
            $this->deleteTempFile($tmpfname);
156
157
            throw $exception;
158
        }
159
160
        return $response;
161
    }
162
163
    /**
164
     * @param string   $path
165
     * @param resource $resource
166
     * @param Config   $config
167
     *
168
     * @throws RuntimeException
169
     *
170
     * @return array|bool
171
     */
172 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...
173
    {
174 1
        $uri = stream_get_meta_data($resource)['uri'];
175
176 1
        $response = Cosapi::upload($this->getBucket(), $uri, $path,
177 1
                                    null, null, $config->get('insertOnly', 0));
178
179 1
        $response = $this->normalizeResponse($response);
180
181
        if (false === $response) {
182
            return false;
183
        }
184
185
        $this->setContentType($path, stream_get_contents($resource));
186
187
        return $response;
188
    }
189
190
    /**
191
     * @param string $path
192
     * @param string $newpath
193
     *
194
     * @return bool
195
     */
196 1
    public function rename($path, $newpath)
197
    {
198 1
        return (bool) $this->normalizeResponse(
199 1
            Cosapi::moveFile($this->getBucket(), $path, $newpath, 1)
0 ignored issues
show
Documentation introduced by
1 is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
200 1
        );
201
    }
202
203
    /**
204
     * @param string $path
205
     * @param string $newpath
206
     *
207
     * @return bool
208
     */
209 1
    public function copy($path, $newpath)
210
    {
211 1
        return (bool) $this->normalizeResponse(
212 1
            Cosapi::copyFile($this->getBucket(), $path, $newpath, 1)
0 ignored issues
show
Documentation introduced by
1 is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
213 1
        );
214
    }
215
216
    /**
217
     * @param string $path
218
     *
219
     * @return bool
220
     */
221 1
    public function delete($path)
222
    {
223 1
        return (bool) $this->normalizeResponse(
224 1
            Cosapi::delFile($this->getBucket(), $path)
225 1
        );
226
    }
227
228
    /**
229
     * @param string $dirname
230
     *
231
     * @return bool
232
     */
233 1
    public function deleteDir($dirname)
234
    {
235 1
        return (bool) $this->normalizeResponse(
236 1
            Cosapi::delFolder($this->getBucket(), $dirname)
237 1
        );
238
    }
239
240
    /**
241
     * @param string $dirname
242
     * @param Config $config
243
     *
244
     * @return array|bool
245
     */
246 1
    public function createDir($dirname, Config $config)
247
    {
248 1
        return $this->normalizeResponse(
249 1
            Cosapi::createFolder($this->getBucket(), $dirname)
250 1
        );
251
    }
252
253
    /**
254
     * @param string $path
255
     * @param string $visibility
256
     *
257
     * @return bool
258
     */
259 1
    public function setVisibility($path, $visibility)
260
    {
261 1
        $visibility = $visibility === AdapterInterface::VISIBILITY_PUBLIC ? 'eWPrivateRPublic' : 'eWRPrivate';
262
263 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...
264 1
            Cosapi::update($this->getBucket(), $path, null, $visibility)
265 1
        );
266
    }
267
268
    /**
269
     * @param string $path
270
     *
271
     * @return bool
272
     */
273 1
    public function has($path)
274
    {
275
        try {
276 1
            return (bool) $this->getMetadata($path);
277 1
        } catch (RuntimeException $exception) {
278 1
            return false;
279
        }
280
    }
281
282
    /**
283
     * @param string $path
284
     *
285
     * @return array
286
     */
287
    public function read($path)
288
    {
289
        return ['contents' => file_get_contents($this->getUrl($path))];
290
    }
291
292
    /**
293
     * @param string $path
294
     *
295
     * @return array
296
     */
297
    public function readStream($path)
298
    {
299
        return ['stream' => fopen($this->getUrl($path), 'r')];
300
    }
301
302
    /**
303
     * @param string $directory
304
     * @param bool   $recursive
305
     *
306
     * @return array|bool
307
     */
308 1
    public function listContents($directory = '', $recursive = false)
309
    {
310 1
        return $this->normalizeResponse(
311 1
            Cosapi::listFolder($this->getBucket(), $directory)
312 1
        );
313
    }
314
315
    /**
316
     * @param string $path
317
     *
318
     * @return array|bool
319
     */
320 6
    public function getMetadata($path)
321
    {
322 6
        return $this->normalizeResponse(
323 6
            Cosapi::stat($this->getBucket(), $path)
324 6
        );
325
    }
326
327
    /**
328
     * @param string $path
329
     *
330
     * @return array|bool
331
     */
332 1
    public function getSize($path)
333
    {
334 1
        $stat = $this->getMetadata($path);
335
336
        if (isset($stat['filesize'])) {
337
            return ['size' => $stat['filesize']];
338
        }
339
340
        return false;
341
    }
342
343
    /**
344
     * @param string $path
345
     *
346
     * @return array|bool
347
     */
348 1
    public function getMimetype($path)
349
    {
350 1
        $stat = $this->getMetadata($path);
351
352
        if (isset($stat['custom_headers']['Content-Type'])) {
353
            return ['mimetype' => $stat['custom_headers']['Content-Type']];
354
        }
355
356
        return false;
357
    }
358
359
    /**
360
     * @param string $path
361
     *
362
     * @return array|bool
363
     */
364 1
    public function getTimestamp($path)
365
    {
366 1
        $stat = $this->getMetadata($path);
367
368
        if (isset($stat['ctime'])) {
369
            return ['timestamp' => $stat['ctime']];
370
        }
371
372
        return false;
373
    }
374
375
    /**
376
     * @param string $path
377
     *
378
     * @return array|bool
379
     */
380 1
    public function getVisibility($path)
381
    {
382 1
        $stat = $this->getMetadata($path);
383
384 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...
385
            return ['visibility' => AdapterInterface::VISIBILITY_PUBLIC];
386
        }
387
388 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...
389
            return ['visibility' => AdapterInterface::VISIBILITY_PRIVATE];
390
        }
391
392
        return false;
393
    }
394
395
    /**
396
     * @param string $content
397
     *
398
     * @return string|bool
399
     */
400
    private function writeTempFile($content)
401
    {
402
        $tmpfname = tempnam('/tmp', 'dir');
403
404
        chmod($tmpfname, 0777);
405
406
        file_put_contents($tmpfname, $content);
407
408
        return $tmpfname;
409
    }
410
411
    /**
412
     * @param string|bool $tmpfname
413
     *
414
     * @return bool
415
     */
416
    private function deleteTempFile($tmpfname)
417
    {
418
        if (false === $tmpfname) {
419
            return false;
420
        }
421
422
        return unlink($tmpfname);
423
    }
424
425
    /**
426
     * @param string $path
427
     * @param string $content
428
     *
429
     * @return bool
430
     */
431
    protected function setContentType($path, $content)
432
    {
433
        $custom_headers = [
434
            'Content-Type' => Util::guessMimeType($path, $content),
435
        ];
436
437
        return $this->normalizeResponse(
438
            Cosapi::update($this->getBucket(), $path, null, null, $custom_headers)
439
        );
440
    }
441
442
    /**
443
     * @param $response
444
     *
445
     * @throws RuntimeException
446
     *
447
     * @return mixed
448
     */
449 15
    protected function normalizeResponse($response)
450
    {
451 15
        if ($response['code'] == 0) {
452
            return isset($response['data']) ? $response['data'] : true;
453
        }
454
455 15
        if ($this->debug) {
456 15
            throw new RuntimeException($response['message'], $response['code']);
457
        }
458
459
        return false;
460
    }
461
}
462