Test Failed
Push — master ( 0b1286...f76d43 )
by frey
02:40
created

Adapter::readStream()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 0
cts 4
cp 0
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 1
crap 6
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
        $objectUrl = $this->client->getObjectUrl(
109
            $this->getBucket(), $path, null, ['Scheme' => $this->config['scheme']]
110 4
        );
111
112
        $url = parse_url($objectUrl);
113
114
        return sprintf('%s://%s%s', $url['scheme'], $url['host'], urldecode($url['path']));
115
    }
116
117
    /**
118
     * @param string             $path
119
     * @param \DateTimeInterface $expiration
120
     * @param array              $options
121
     *
122
     * @return string
123
     */
124
    public function getTemporaryUrl($path, DateTimeInterface $expiration, array $options = [])
125
    {
126
        $options = array_merge($options, ['Scheme' => $this->config['scheme']]);
127
128
        $objectUrl = $this->client->getObjectUrl(
129
            $this->getBucket(), $path, $expiration->format('c'), $options
130
        );
131
132
        $url = parse_url($objectUrl);
133
134
        if ($this->config['cdn']) {
135
            return $this->config['cdn'] . urldecode($url['path']) . '?' . $url['query'];
136
        }
137
138
        return sprintf('%s://%s%s?%s', $url['scheme'], $url['host'], urldecode($url['path']), $url['query']);
139
    }
140
141
    /**
142
     * @param string $path
143
     * @param string $contents
144
     * @param Config $config
145
     *
146
     * @return array|bool
147
     */
148 2
    public function write($path, $contents, Config $config)
149
    {
150 2
        return $this->client->upload($this->getBucket(), $path, $contents);
151
    }
152
153
    /**
154
     * @param string   $path
155
     * @param resource $resource
156
     * @param Config   $config
157
     *
158
     * @return array|bool
159
     */
160 2
    public function writeStream($path, $resource, Config $config)
161
    {
162 2
        return $this->client->upload($this->getBucket(), $path, stream_get_contents($resource, -1, 0));
163
    }
164
165
    /**
166
     * @param string $path
167
     * @param string $contents
168
     * @param Config $config
169
     *
170
     * @return array|bool
171
     */
172 1
    public function update($path, $contents, Config $config)
173
    {
174 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 174 which is incompatible with the return type declared by the interface League\Flysystem\AdapterInterface::update of type array|false.
Loading history...
175
    }
176
177
    /**
178
     * @param string   $path
179
     * @param resource $resource
180
     * @param Config   $config
181
     *
182
     * @return array|bool
183
     */
184 1
    public function updateStream($path, $resource, Config $config)
185
    {
186 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 186 which is incompatible with the return type declared by the interface League\Flysystem\AdapterInterface::updateStream of type array|false.
Loading history...
187
    }
188
189
    /**
190
     * @param string $path
191
     * @param string $newpath
192
     *
193
     * @return bool
194
     */
195 1
    public function rename($path, $newpath)
196
    {
197 1
        $result = $this->copy($path, $newpath);
198
199
        $this->delete($path);
200
201
        return $result;
202
    }
203
204
    /**
205
     * @param string $path
206
     * @param string $newpath
207
     *
208
     * @return bool
209
     */
210 2
    public function copy($path, $newpath)
211
    {
212 2
        $source = $this->getSourcePath($path);
213
214 2
        return (bool) $this->client->copy($this->getBucket(), $newpath, $source);
215
    }
216
217
    /**
218
     * @param string $path
219
     *
220
     * @return bool
221
     */
222 1
    public function delete($path)
223
    {
224 1
        return (bool) $this->client->deleteObject([
225 1
            'Bucket' => $this->getBucket(),
226 1
            'Key'    => $path,
227 1
        ]);
228
    }
229
230
    /**
231
     * @param string $dirname
232
     *
233
     * @return bool
234
     */
235 1
    public function deleteDir($dirname)
236
    {
237 1
        $response = $this->listObjects($dirname);
238
239
        if (!isset($response['Contents'])) {
240
            return true;
241
        }
242
243
        $keys = array_map(function ($item) {
244
            return ['Key' => $item['Key']];
245
        }, (array) $response['Contents']);
246
247
        return (bool) $this->client->deleteObjects([
248
            'Bucket'  => $this->getBucket(),
249
            'Objects' => $keys,
250
        ]);
251
    }
252
253
    /**
254
     * @param string $dirname
255
     * @param Config $config
256
     *
257
     * @return array|bool
258
     */
259 1
    public function createDir($dirname, Config $config)
260
    {
261 1
        return $this->client->putObject([
262 1
            'Bucket' => $this->getBucket(),
263 1
            'Key'    => $dirname.'/_blank',
264 1
            'Body'   => '',
265 1
        ]);
266
    }
267
268
    /**
269
     * @param string $path
270
     * @param string $visibility
271
     *
272
     * @return bool
273
     */
274 1
    public function setVisibility($path, $visibility)
275
    {
276 1
        $visibility = ($visibility === AdapterInterface::VISIBILITY_PUBLIC)
277 1
            ? 'public-read' : 'private';
278
279 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...
280 1
            'Bucket' => $this->getBucket(),
281 1
            'Key'    => $path,
282 1
            'ACL'    => $visibility,
283 1
        ]);
284
    }
285
286
    /**
287
     * @param string $path
288
     *
289
     * @return bool
290
     */
291 1
    public function has($path)
292
    {
293
        try {
294 1
            return (bool) $this->getMetadata($path);
295 1
        } catch (NoSuchKeyException $e) {
296
            return false;
297
        }
298
    }
299
300
    /**
301
     * @param string $path
302
     *
303
     * @return array|bool
304
     */
305 1
    public function read($path)
306
    {
307
        try {
308 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...
309 1
                'Bucket' => $this->getBucket(),
310 1
                'Key'    => $path,
311 1
            ]);
312
313
            return ['contents' => (string) $response->get('Body')];
314 1
        } catch (NoSuchKeyException $e) {
315
            return false;
316
        }
317
    }
318
319
    /**
320
     * @param string $path
321
     *
322
     * @return array|bool
323
     */
324
    public function readStream($path)
325
    {
326
        try {
327
            return ['stream' => fopen($this->getUrl($path), 'rb', false)];
328
        } catch (NoSuchKeyException $e) {
329
            return false;
330
        }
331
    }
332
333
    /**
334
     * @param string $directory
335
     * @param bool   $recursive
336
     *
337
     * @return array|bool
338
     */
339 1
    public function listContents($directory = '', $recursive = false)
340
    {
341 1
        $list = [];
342
343 1
        $response = $this->listObjects($directory, $recursive);
344
345
        foreach ((array) $response->get('Contents') as $content) {
346
            $list[] = $this->normalizeFileInfo($content);
347
        }
348
349
        return $list;
350
    }
351
352
    /**
353
     * @param string $path
354
     *
355
     * @return array|bool
356
     */
357 5
    public function getMetadata($path)
358
    {
359 5
        return $this->client->headObject([
360 5
            'Bucket' => $this->getBucket(),
361 5
            'Key'    => $path,
362 5
        ])->toArray();
363
    }
364
365
    /**
366
     * @param string $path
367
     *
368
     * @return array|bool
369
     */
370 1
    public function getSize($path)
371
    {
372 1
        $meta = $this->getMetadata($path);
373
374
        return isset($meta['ContentLength'])
375
            ? ['size' => $meta['ContentLength']] : false;
376
    }
377
378
    /**
379
     * @param string $path
380
     *
381
     * @return array|bool
382
     */
383 1
    public function getMimetype($path)
384
    {
385 1
        $meta = $this->getMetadata($path);
386
387
        return isset($meta['ContentType'])
388
            ? ['mimetype' => $meta['ContentType']] : false;
389
    }
390
391
    /**
392
     * @param string $path
393
     *
394
     * @return array|bool
395
     */
396 1
    public function getTimestamp($path)
397
    {
398 1
        $meta = $this->getMetadata($path);
399
400
        return isset($meta['LastModified'])
401
            ? ['timestamp' => strtotime($meta['LastModified'])] : false;
402
    }
403
404
    /**
405
     * @param string $path
406
     *
407
     * @return array|bool
408
     */
409 1
    public function getVisibility($path)
410
    {
411 1
        $meta = $this->client->getObjectAcl([
412 1
            'Bucket' => $this->getBucket(),
413 1
            'Key'    => $path,
414 1
        ]);
415
416
        foreach ($meta->get('Grants') as $grant) {
417
            if (isset($grant['Grantee']['URI'])
418
                && $grant['Permission'] === 'READ'
419
                && strpos($grant['Grantee']['URI'], 'global/AllUsers') !== false
420
            ) {
421
                return ['visibility' => AdapterInterface::VISIBILITY_PUBLIC];
422
            }
423
        }
424
425
        return ['visibility' => AdapterInterface::VISIBILITY_PRIVATE];
426
    }
427
428
    /**
429
     * @param array $content
430
     *
431
     * @return array
432
     */
433
    private function normalizeFileInfo(array $content)
434
    {
435
        $path = pathinfo($content['Key']);
436
437
        return [
438
            'type'      => 'file',
439
            'path'      => $content['Key'],
440
            'timestamp' => Carbon::parse($content['LastModified'])->getTimestamp(),
441
            'size'      => (int) $content['Size'],
442
            'dirname'   => (string) $path['dirname'],
443
            'basename'  => (string) $path['basename'],
444
            'extension' => isset($path['extension']) ? $path['extension'] : '',
445
            'filename'  => (string) $path['filename'],
446
        ];
447
    }
448
449
    /**
450
     * @param string $directory
451
     * @param bool   $recursive
452
     *
453
     * @return mixed
454
     */
455 2
    private function listObjects($directory = '', $recursive = false)
456
    {
457 2
        return $this->client->listObjects([
458 2
            'Bucket'    => $this->getBucket(),
459 2
            'Prefix'    => ((string) $directory === '') ? '' : ($directory.'/'),
460 2
            'Delimiter' => $recursive ? '' : '/',
461 2
        ]);
462
    }
463
}
464