Completed
Push — master ( 8eb6e1...e8607e )
by frey
03:54 queued 01:36
created

Adapter::getUrl()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.576

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 6
cts 10
cp 0.6
rs 9.7666
c 0
b 0
f 0
cc 3
nc 2
nop 1
crap 3.576
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
use League\Flysystem\Adapter\CanOverwriteFiles;
13
14
/**
15
 * Class Adapter.
16
 */
17
class Adapter extends AbstractAdapter implements CanOverwriteFiles
18
{
19
    /**
20
     * @var Client
21
     */
22
    protected $client;
23
24
    /**
25
     * @var array
26
     */
27
    protected $config = [];
28
29
    /**
30
     * @var array
31
     */
32
    protected $regionMap = [
33
        'cn-east'      => 'ap-shanghai',
34
        'cn-sorth'     => 'ap-guangzhou',
35
        'cn-north'     => 'ap-beijing-1',
36
        'cn-south-2'   => 'ap-guangzhou-2',
37
        'cn-southwest' => 'ap-chengdu',
38
        'sg'           => 'ap-singapore',
39
        'tj'           => 'ap-beijing-1',
40
        'bj'           => 'ap-beijing',
41
        'sh'           => 'ap-shanghai',
42
        'gz'           => 'ap-guangzhou',
43
        'cd'           => 'ap-chengdu',
44
        'sgp'          => 'ap-singapore',
45
    ];
46
47
    /**
48
     * Adapter constructor.
49
     *
50
     * @param Client $client
51
     * @param array  $config
52
     */
53
    public function __construct(Client $client, array $config)
54
    {
55
        $this->client = $client;
56
        $this->config = $config;
57
58
        $this->setPathPrefix($config['cdn']);
59
    }
60
61
    /**
62
     * @return string
63
     */
64 18
    public function getBucket()
65
    {
66 18
        return $this->config['bucket'];
67
    }
68
69
    /**
70
     * @return string
71
     */
72 2
    public function getAppId()
73
    {
74 2
        return $this->config['credentials']['appId'];
75
    }
76
77
    /**
78
     * @return string
79
     */
80 2
    public function getRegion()
81
    {
82 2
        return array_key_exists($this->config['region'], $this->regionMap)
83 2
            ? $this->regionMap[$this->config['region']] : $this->config['region'];
84
    }
85
86
    /**
87
     * @param $path
88
     *
89
     * @return string
90
     */
91 2
    public function getSourcePath($path)
92
    {
93 2
        return sprintf('%s-%s.cos.%s.myqcloud.com/%s',
94 2
            $this->getBucket(), $this->getAppId(), $this->getRegion(), $path
95 2
        );
96
    }
97
98
    /**
99
     * @param string $path
100
     *
101
     * @return string
102
     */
103 4
    public function getUrl($path)
104 4
    {
105 2
        if ($this->config['cdn']) {
106 2
            return $this->applyPathPrefix($path);
107
        }
108
109
        $objectUrl = $this->client->getObjectUrl(
110 4
            $this->getBucket(), $path, null,
111
                ['Scheme' => isset($this->config['scheme']) ? $this->config['scheme'] : 'http']
112 4
        );
113
114
        $url = parse_url($objectUrl);
115
116
        return sprintf('%s://%s%s', $url['scheme'], $url['host'], urldecode($url['path']));
117
    }
118
119
    /**
120
     * @param string             $path
121
     * @param \DateTimeInterface $expiration
122
     * @param array              $options
123
     *
124
     * @return string
125
     */
126
    public function getTemporaryUrl($path, DateTimeInterface $expiration, array $options = [])
127
    {
128
        $options = array_merge($options,
129
            ['Scheme' => isset($this->config['scheme']) ? $this->config['scheme'] : 'http']);
130
131
        $objectUrl = $this->client->getObjectUrl(
132
            $this->getBucket(), $path, $expiration->format('c'), $options
133
        );
134
135
        $url = parse_url($objectUrl);
136
137
        if ($this->config['cdn']) {
138
            return $this->config['cdn'].urldecode($url['path']).'?'.$url['query'];
139
        }
140
141
        return sprintf('%s://%s%s?%s', $url['scheme'], $url['host'], urldecode($url['path']), $url['query']);
142
    }
143
144
    /**
145
     * @param string $path
146
     * @param string $contents
147
     * @param Config $config
148
     *
149
     * @return array|bool
150
     */
151 2
    public function write($path, $contents, Config $config)
152
    {
153 2
        return $this->client->upload($this->getBucket(), $path, $contents);
154
    }
155
156
    /**
157
     * @param string   $path
158
     * @param resource $resource
159
     * @param Config   $config
160
     *
161
     * @return array|bool
162
     */
163 2
    public function writeStream($path, $resource, Config $config)
164
    {
165 2
        return $this->client->upload($this->getBucket(), $path, stream_get_contents($resource, -1, 0));
166
    }
167
168
    /**
169
     * @param string $path
170
     * @param string $contents
171
     * @param Config $config
172
     *
173
     * @return array|bool
174
     */
175 2
    public function update($path, $contents, Config $config)
176 2
    {
177 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 177 which is incompatible with the return type declared by the interface League\Flysystem\AdapterInterface::update of type array|false.
Loading history...
178
    }
179
180
    /**
181
     * @param string   $path
182
     * @param resource $resource
183
     * @param Config   $config
184
     *
185
     * @return array|bool
186
     */
187 1
    public function updateStream($path, $resource, Config $config)
188
    {
189 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 189 which is incompatible with the return type declared by the interface League\Flysystem\AdapterInterface::updateStream of type array|false.
Loading history...
190
    }
191
192
    /**
193
     * @param string $path
194
     * @param string $newpath
195
     *
196
     * @return bool
197
     */
198 1
    public function rename($path, $newpath)
199
    {
200 1
        $result = $this->copy($path, $newpath);
201
202 1
        $this->delete($path);
203
204 1
        return $result;
205
    }
206
207
    /**
208
     * @param string $path
209
     * @param string $newpath
210
     *
211
     * @return bool
212
     */
213 2
    public function copy($path, $newpath)
214
    {
215 2
        $source = $this->getSourcePath($path);
216
217 2
        return (bool) $this->client->copy($this->getBucket(), $newpath, $source);
218
    }
219
220
    /**
221
     * @param string $path
222
     *
223
     * @return bool
224
     */
225 2
    public function delete($path)
226
    {
227 2
        return (bool) $this->client->deleteObject([
228 2
            'Bucket' => $this->getBucket(),
229 2
            'Key'    => $path,
230 2
        ]);
231
    }
232
233
    /**
234
     * @param string $dirname
235
     *
236
     * @return bool
237
     */
238 1
    public function deleteDir($dirname)
239
    {
240 1
        $response = $this->listObjects($dirname);
241
242 1
        if (!isset($response['Contents'])) {
243
            return true;
244
        }
245
246 1
        $keys = array_map(function ($item) {
247 1
            return ['Key' => $item['Key']];
248 1
        }, (array) $response['Contents']);
249
250 1
        return (bool) $this->client->deleteObjects([
251 1
            'Bucket'  => $this->getBucket(),
252 1
            'Objects' => $keys,
253 1
        ]);
254
    }
255
256
    /**
257
     * @param string $dirname
258
     * @param Config $config
259
     *
260
     * @return array|bool
261
     */
262 1
    public function createDir($dirname, Config $config)
263
    {
264 1
        return $this->client->putObject([
265 1
            'Bucket' => $this->getBucket(),
266 1
            'Key'    => $dirname.'/_blank',
267 1
            'Body'   => '',
268 1
        ]);
269
    }
270
271
    /**
272
     * @param string $path
273
     * @param string $visibility
274
     *
275
     * @return bool
276
     */
277 1
    public function setVisibility($path, $visibility)
278
    {
279 1
        $visibility = ($visibility === AdapterInterface::VISIBILITY_PUBLIC)
280 1
            ? 'public-read' : 'private';
281
282 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...
283 1
            'Bucket' => $this->getBucket(),
284 1
            'Key'    => $path,
285 1
            'ACL'    => $visibility,
286 1
        ]);
287
    }
288
289
    /**
290
     * @param string $path
291
     *
292
     * @return bool
293
     */
294 1
    public function has($path)
295
    {
296
        try {
297 1
            return (bool) $this->getMetadata($path);
298
        } catch (NoSuchKeyException $e) {
299
            return false;
300
        }
301
    }
302
303
    /**
304
     * @param string $path
305
     *
306
     * @return array|bool
307
     */
308 1
    public function read($path)
309
    {
310
        try {
311 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...
312 1
                'Bucket' => $this->getBucket(),
313 1
                'Key'    => $path,
314 1
            ]);
315
316 1
            return ['contents' => (string) $response->get('Body')];
317
        } catch (NoSuchKeyException $e) {
318
            return false;
319
        }
320
    }
321
322
    /**
323
     * @param string $path
324
     *
325
     * @return array|bool
326
     */
327 1
    public function readStream($path)
328
    {
329
        try {
330 1
            return ['stream' => fopen($this->getUrl($path), 'rb', false)];
331
        } catch (NoSuchKeyException $e) {
332
            return false;
333
        }
334
    }
335
336
    /**
337
     * @param string $directory
338
     * @param bool   $recursive
339
     *
340
     * @return array|bool
341
     */
342 1
    public function listContents($directory = '', $recursive = false)
343
    {
344 1
        $list = [];
345
346 1
        $response = $this->listObjects($directory, $recursive);
347
348 1
        foreach ((array) $response->get('Contents') as $content) {
349 1
            $list[] = $this->normalizeFileInfo($content);
350 1
        }
351
352 1
        return $list;
353
    }
354
355
    /**
356
     * @param string $path
357
     *
358
     * @return array|bool
359
     */
360 5
    public function getMetadata($path)
361
    {
362 5
        return $this->client->headObject([
363 5
            'Bucket' => $this->getBucket(),
364 5
            'Key'    => $path,
365 5
        ])->toArray();
366
    }
367
368
    /**
369
     * @param string $path
370
     *
371
     * @return array|bool
372
     */
373 1
    public function getSize($path)
374
    {
375 1
        $meta = $this->getMetadata($path);
376
377 1
        return isset($meta['ContentLength'])
378 1
            ? ['size' => $meta['ContentLength']] : false;
379
    }
380
381
    /**
382
     * @param string $path
383
     *
384
     * @return array|bool
385
     */
386 1
    public function getMimetype($path)
387
    {
388 1
        $meta = $this->getMetadata($path);
389
390 1
        return isset($meta['ContentType'])
391 1
            ? ['mimetype' => $meta['ContentType']] : false;
392
    }
393
394
    /**
395
     * @param string $path
396
     *
397
     * @return array|bool
398
     */
399 1
    public function getTimestamp($path)
400
    {
401 1
        $meta = $this->getMetadata($path);
402
403 1
        return isset($meta['LastModified'])
404 1
            ? ['timestamp' => strtotime($meta['LastModified'])] : false;
405
    }
406
407
    /**
408
     * @param string $path
409
     *
410
     * @return array|bool
411
     */
412 1
    public function getVisibility($path)
413
    {
414 1
        $meta = $this->client->getObjectAcl([
415 1
            'Bucket' => $this->getBucket(),
416 1
            'Key'    => $path,
417 1
        ]);
418
419 1
        foreach ($meta->get('Grants') as $grant) {
420 1
            if (isset($grant['Grantee']['URI'])
421 1
                && $grant['Permission'] === 'READ'
422 1
                && strpos($grant['Grantee']['URI'], 'global/AllUsers') !== false
423 1
            ) {
424
                return ['visibility' => AdapterInterface::VISIBILITY_PUBLIC];
425
            }
426 1
        }
427
428 1
        return ['visibility' => AdapterInterface::VISIBILITY_PRIVATE];
429
    }
430
431
    /**
432
     * @param array $content
433
     *
434
     * @return array
435
     */
436 1
    private function normalizeFileInfo(array $content)
437
    {
438 1
        $path = pathinfo($content['Key']);
439
440
        return [
441 1
            'type'      => 'file',
442 1
            'path'      => $content['Key'],
443 1
            'timestamp' => Carbon::parse($content['LastModified'])->getTimestamp(),
444 1
            'size'      => (int) $content['Size'],
445 1
            'dirname'   => (string) $path['dirname'],
446 1
            'basename'  => (string) $path['basename'],
447 1
            'extension' => isset($path['extension']) ? $path['extension'] : '',
448 1
            'filename'  => (string) $path['filename'],
449 1
        ];
450
    }
451
452
    /**
453
     * @param string $directory
454
     * @param bool   $recursive
455
     *
456
     * @return mixed
457
     */
458 2
    private function listObjects($directory = '', $recursive = false)
459
    {
460 2
        return $this->client->listObjects([
461 2
            'Bucket'    => $this->getBucket(),
462 2
            'Prefix'    => ((string) $directory === '') ? '' : ($directory.'/'),
463 2
            'Delimiter' => $recursive ? '' : '/',
464 2
        ]);
465
    }
466
}
467