Completed
Push — master ( c18819...86bd9f )
by frey
03:46
created

Adapter::getVisibility()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 10.8265

Importance

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