Completed
Push — master ( ce6325...57d4d8 )
by frey
09:42
created

Adapter::getUrl()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.5

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 4
cts 8
cp 0.5
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 7
nc 2
nop 1
crap 2.5
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
        $source = $this->getSourcePath($path);
198
199 1
        $response = $this->client->copyObject([
200 1
            'Bucket'     => $this->getBucket(),
201 1
            'Key'        => $newpath,
202 1
            'CopySource' => $source,
203 1
        ]);
204
205
        $this->delete($path);
206
207
        return (bool) $response;
208
    }
209
210
    /**
211
     * @param string $path
212
     * @param string $newpath
213
     *
214
     * @return bool
215
     */
216 1
    public function copy($path, $newpath)
217
    {
218 1
        $source = $this->getSourcePath($path);
219
220 1
        return (bool) $this->client->copyObject([
221 1
            'Bucket'     => $this->getBucket(),
222 1
            'Key'        => $newpath,
223 1
            'CopySource' => $source,
224 1
        ]);
225
    }
226
227
    /**
228
     * @param string $path
229
     *
230
     * @return bool
231
     */
232 1
    public function delete($path)
233
    {
234 1
        return (bool) $this->client->deleteObject([
235 1
            'Bucket' => $this->getBucket(),
236 1
            'Key'    => $path,
237 1
        ]);
238
    }
239
240
    /**
241
     * @param string $dirname
242
     *
243
     * @return bool
244
     */
245 1
    public function deleteDir($dirname)
246
    {
247 1
        $response = $this->listObjects($dirname);
248
249
        if (!isset($response['Contents'])) {
250
            return true;
251
        }
252
253
        $keys = array_map(function ($item) {
254
            return ['Key' => $item['Key']];
255
        }, (array) $response['Contents']);
256
257
        return (bool) $this->client->deleteObjects([
258
            'Bucket'  => $this->getBucket(),
259
            'Objects' => $keys,
260
        ]);
261
    }
262
263
    /**
264
     * @param string $dirname
265
     * @param Config $config
266
     *
267
     * @return array|bool
268
     */
269 1
    public function createDir($dirname, Config $config)
270
    {
271 1
        return $this->client->putObject([
272 1
            'Bucket' => $this->getBucket(),
273 1
            'Key'    => $dirname.'/_blank',
274 1
            'Body'   => '',
275 1
        ]);
276
    }
277
278
    /**
279
     * @param string $path
280
     * @param string $visibility
281
     *
282
     * @return bool
283
     */
284 1
    public function setVisibility($path, $visibility)
285
    {
286 1
        $visibility = ($visibility === AdapterInterface::VISIBILITY_PUBLIC)
287 1
            ? 'public-read' : 'private';
288
289 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...
290 1
            'Bucket' => $this->getBucket(),
291 1
            'Key'    => $path,
292 1
            'ACL'    => $visibility,
293 1
        ]);
294
    }
295
296
    /**
297
     * @param string $path
298
     *
299
     * @return bool
300
     */
301 1
    public function has($path)
302
    {
303
        try {
304 1
            return (bool) $this->getMetadata($path);
305 1
        } catch (NoSuchKeyException $e) {
306
            return false;
307
        }
308
    }
309
310
    /**
311
     * @param string $path
312
     *
313
     * @return array|bool
314
     */
315 1
    public function read($path)
316
    {
317
        try {
318 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...
319 1
                'Bucket' => $this->getBucket(),
320 1
                'Key'    => $path,
321 1
            ]);
322
323
            return ['contents' => (string) $response->get('Body')];
324 1
        } catch (NoSuchKeyException $e) {
325
            return false;
326
        }
327
    }
328
329
    /**
330
     * @param string $path
331
     *
332
     * @return array|bool
333
     */
334 1
    public function readStream($path)
335
    {
336
        try {
337 1
            return ['stream' => fopen($this->getUrl($path), 'rb', false)];
338
        } catch (NoSuchKeyException $e) {
339
            return false;
340
        }
341
    }
342
343
    /**
344
     * @param string $directory
345
     * @param bool   $recursive
346
     *
347
     * @return array|bool
348
     */
349 1
    public function listContents($directory = '', $recursive = false)
350
    {
351 1
        $list = [];
352
353 1
        $response = $this->listObjects($directory, $recursive);
354
355
        foreach ((array) $response->get('Contents') as $content) {
356
            $list[] = $this->normalizeFileInfo($content);
357
        }
358
359
        return $list;
360
    }
361
362
    /**
363
     * @param string $path
364
     *
365
     * @return array|bool
366
     */
367 5
    public function getMetadata($path)
368
    {
369 5
        return $this->client->headObject([
370 5
            'Bucket' => $this->getBucket(),
371 5
            'Key'    => $path,
372 5
        ])->toArray();
373
    }
374
375
    /**
376
     * @param string $path
377
     *
378
     * @return array|bool
379
     */
380 1
    public function getSize($path)
381
    {
382 1
        $meta = $this->getMetadata($path);
383
384
        return isset($meta['ContentLength'])
385
            ? ['size' => $meta['ContentLength']] : false;
386
    }
387
388
    /**
389
     * @param string $path
390
     *
391
     * @return array|bool
392
     */
393 1
    public function getMimetype($path)
394
    {
395 1
        $meta = $this->getMetadata($path);
396
397
        return isset($meta['ContentType'])
398
            ? ['mimetype' => $meta['ContentType']] : false;
399
    }
400
401
    /**
402
     * @param string $path
403
     *
404
     * @return array|bool
405
     */
406 1
    public function getTimestamp($path)
407
    {
408 1
        $meta = $this->getMetadata($path);
409
410
        return isset($meta['LastModified'])
411
            ? ['timestamp' => strtotime($meta['LastModified'])] : false;
412
    }
413
414
    /**
415
     * @param string $path
416
     *
417
     * @return array|bool
418
     */
419 1
    public function getVisibility($path)
420
    {
421 1
        $meta = $this->client->getObjectAcl([
422 1
            'Bucket' => $this->getBucket(),
423 1
            'Key'    => $path,
424 1
        ]);
425
426
        foreach ($meta->get('Grants') as $grant) {
427
            if (isset($grant['Grantee']['URI'])
428
                && $grant['Permission'] === 'READ'
429
                && strpos($grant['Grantee']['URI'], 'global/AllUsers') !== false
430
            ) {
431
                return ['visibility' => AdapterInterface::VISIBILITY_PUBLIC];
432
            }
433
        }
434
435
        return ['visibility' => AdapterInterface::VISIBILITY_PRIVATE];
436
    }
437
438
    /**
439
     * @param array $content
440
     *
441
     * @return array
442
     */
443
    private function normalizeFileInfo(array $content)
444
    {
445
        $path = pathinfo($content['Key']);
446
447
        return [
448
            'type'      => 'file',
449
            'path'      => $content['Key'],
450
            'timestamp' => Carbon::parse($content['LastModified'])->getTimestamp(),
451
            'size'      => (int) $content['Size'],
452
            'dirname'   => (string) $path['dirname'],
453
            'basename'  => (string) $path['basename'],
454
            'extension' => isset($path['extension']) ? $path['extension'] : '',
455
            'filename'  => (string) $path['filename'],
456
        ];
457
    }
458
459
    /**
460
     * @param string $directory
461
     * @param bool   $recursive
462
     *
463
     * @return mixed
464
     */
465 2
    private function listObjects($directory = '', $recursive = false)
466
    {
467 2
        return $this->client->listObjects([
468 2
            'Bucket'    => $this->getBucket(),
469 2
            'Prefix'    => ((string) $directory === '') ? '' : ($directory.'/'),
470 2
            'Delimiter' => $recursive ? '' : '/',
471 2
        ]);
472
    }
473
}
474