Test Failed
Push — master ( 204b73...d3d542 )
by frey
12:19
created

Adapter   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 446
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 60.52%

Importance

Changes 0
Metric Value
wmc 46
lcom 1
cbo 3
dl 0
loc 446
ccs 92
cts 152
cp 0.6052
rs 8.3999
c 0
b 0
f 0

28 Methods

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

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

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
307 1
                'Bucket' => $this->getBucket(),
308 1
                'Key'    => $path,
309 1
            ]);
310
311
            return ['contents' => (string) $response->get('Body')];
312 1
        } catch (NoSuchKeyException $e) {
313
            return false;
314
        }
315
    }
316
317
    /**
318
     * @param string $path
319
     *
320
     * @return array|bool
321
     */
322
    public function readStream($path)
323
    {
324
        try {
325
            return ['stream' => fopen($this->getUrl($path), 'rb', false)];
326
        } catch (NoSuchKeyException $e) {
327
            return false;
328
        }
329
    }
330
331
    /**
332
     * @param string $directory
333
     * @param bool   $recursive
334
     *
335
     * @return array|bool
336
     */
337 1
    public function listContents($directory = '', $recursive = false)
338
    {
339 1
        $list = [];
340
341 1
        $response = $this->listObjects($directory, $recursive);
342
343
        foreach ((array) $response->get('Contents') as $content) {
344
            $list[] = $this->normalizeFileInfo($content);
345
        }
346
347
        return $list;
348
    }
349
350
    /**
351
     * @param string $path
352
     *
353
     * @return array|bool
354
     */
355 5
    public function getMetadata($path)
356
    {
357 5
        return $this->client->headObject([
358 5
            'Bucket' => $this->getBucket(),
359 5
            'Key'    => $path,
360 5
        ])->toArray();
361
    }
362
363
    /**
364
     * @param string $path
365
     *
366
     * @return array|bool
367
     */
368 1
    public function getSize($path)
369
    {
370 1
        $meta = $this->getMetadata($path);
371
372
        return isset($meta['ContentLength'])
373
            ? ['size' => $meta['ContentLength']] : false;
374
    }
375
376
    /**
377
     * @param string $path
378
     *
379
     * @return array|bool
380
     */
381 1
    public function getMimetype($path)
382
    {
383 1
        $meta = $this->getMetadata($path);
384
385
        return isset($meta['ContentType'])
386
            ? ['mimetype' => $meta['ContentType']] : false;
387
    }
388
389
    /**
390
     * @param string $path
391
     *
392
     * @return array|bool
393
     */
394 1
    public function getTimestamp($path)
395
    {
396 1
        $meta = $this->getMetadata($path);
397
398
        return isset($meta['LastModified'])
399
            ? ['timestamp' => strtotime($meta['LastModified'])] : false;
400
    }
401
402
    /**
403
     * @param string $path
404
     *
405
     * @return array|bool
406
     */
407 1
    public function getVisibility($path)
408
    {
409 1
        $meta = $this->client->getObjectAcl([
410 1
            'Bucket' => $this->getBucket(),
411 1
            'Key'    => $path,
412 1
        ]);
413
414
        foreach ($meta->get('Grants') as $grant) {
415
            if (isset($grant['Grantee']['URI'])
416
                && $grant['Permission'] === 'READ'
417
                && strpos($grant['Grantee']['URI'], 'global/AllUsers') !== false
418
            ) {
419
                return ['visibility' => AdapterInterface::VISIBILITY_PUBLIC];
420
            }
421
        }
422
423
        return ['visibility' => AdapterInterface::VISIBILITY_PRIVATE];
424
    }
425
426
    /**
427
     * @param array $content
428
     *
429
     * @return array
430
     */
431
    private function normalizeFileInfo(array $content)
432
    {
433
        $path = pathinfo($content['Key']);
434
435
        return [
436
            'type'      => 'file',
437
            'path'      => $content['Key'],
438
            'timestamp' => Carbon::parse($content['LastModified'])->getTimestamp(),
439
            'size'      => (int) $content['Size'],
440
            'dirname'   => (string) $path['dirname'],
441
            'basename'  => (string) $path['basename'],
442
            'extension' => isset($path['extension']) ? $path['extension'] : '',
443
            'filename'  => (string) $path['filename'],
444
        ];
445
    }
446
447
    /**
448
     * @param string $directory
449
     * @param bool   $recursive
450
     *
451
     * @return mixed
452
     */
453 2
    private function listObjects($directory = '', $recursive = false)
454
    {
455 2
        return $this->client->listObjects([
456 2
            'Bucket'    => $this->getBucket(),
457 2
            'Prefix'    => ((string) $directory === '') ? '' : ($directory.'/'),
458 2
            'Delimiter' => $recursive ? '' : '/',
459 2
        ]);
460
    }
461
}
462