Completed
Push — master ( 2a3fa4...ce6325 )
by frey
06:55
created

Adapter::getUrl()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.1481

Importance

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