Completed
Push — master ( a78609...dfcc2e )
by Tobias
02:19
created

AsyncAwsS3::computeKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
namespace Gaufrette\Adapter;
4
5
use AsyncAws\SimpleS3\SimpleS3Client;
6
use Gaufrette\Adapter;
7
use Gaufrette\Util;
8
9
/**
10
 * Amazon S3 adapter using the AsyncAws.
11
 *
12
 * @author  Michael Dowling <[email protected]>
13
 * @author Tobias Nyholm <[email protected]>
14
 */
15
class AsyncAwsS3 implements Adapter, MetadataSupporter, ListKeysAware, SizeCalculator, MimeTypeProvider
16
{
17
    /** @var SimpleS3Client */
18
    protected $service;
19
    /** @var string */
20
    protected $bucket;
21
    /** @var array */
22
    protected $options;
23
    /** @var bool */
24
    protected $bucketExists;
25
    /** @var array */
26
    protected $metadata = [];
27
    /** @var bool */
28
    protected $detectContentType;
29
30
    /**
31
     * @param SimpleS3Client $service
32
     * @param string   $bucket
33
     * @param array    $options
34
     * @param bool     $detectContentType
35
     */
36 View Code Duplication
    public function __construct(SimpleS3Client $service, $bucket, array $options = [], $detectContentType = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
37
    {
38
        if (!class_exists(SimpleS3Client::class)) {
39
            throw new \LogicException('You need to install package "async-aws/simple-s3" to use this adapter');
40
        }
41
        $this->service = $service;
42
        $this->bucket = $bucket;
43
        $this->options = array_replace(
44
            [
45
                'create' => false,
46
                'directory' => '',
47
                'acl' => 'private',
48
            ],
49
            $options
50
        );
51
52
        $this->detectContentType = $detectContentType;
53
    }
54
55
    /**
56
     * {@inheritdoc}
57
     */
58 View Code Duplication
    public function setMetadata($key, $content)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
59
    {
60
        // BC with AmazonS3 adapter
61
        if (isset($content['contentType'])) {
62
            $content['ContentType'] = $content['contentType'];
63
            unset($content['contentType']);
64
        }
65
66
        $this->metadata[$key] = $content;
67
    }
68
69
    /**
70
     * {@inheritdoc}
71
     */
72
    public function getMetadata($key)
73
    {
74
        return isset($this->metadata[$key]) ? $this->metadata[$key] : [];
75
    }
76
77
    /**
78
     * {@inheritdoc}
79
     */
80 View Code Duplication
    public function read($key)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
81
    {
82
        $this->ensureBucketExists();
83
        $options = $this->getOptions($key);
84
85
        try {
86
            // Get remote object
87
            $object = $this->service->getObject($options);
88
            // If there's no metadata array set up for this object, set it up
89
            if (!array_key_exists($key, $this->metadata) || !is_array($this->metadata[$key])) {
90
                $this->metadata[$key] = [];
91
            }
92
            // Make remote ContentType metadata available locally
93
            $this->metadata[$key]['ContentType'] = $object->getContentType();
94
95
            return $object->getBody()->getContentAsString();
96
        } catch (\Exception $e) {
97
            return false;
98
        }
99
    }
100
101
    /**
102
     * {@inheritdoc}
103
     */
104 View Code Duplication
    public function rename($sourceKey, $targetKey)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
105
    {
106
        $this->ensureBucketExists();
107
        $options = $this->getOptions(
108
            $targetKey,
109
            ['CopySource' => $this->bucket . '/' . $this->computePath($sourceKey)]
110
        );
111
112
        try {
113
            $this->service->copyObject(array_merge($options, $this->getMetadata($targetKey)));
114
115
            return $this->delete($sourceKey);
116
        } catch (\Exception $e) {
117
            return false;
118
        }
119
    }
120
121
    /**
122
     * {@inheritdoc}
123
     * @param string|resource $content
124
     */
125
    public function write($key, $content)
126
    {
127
        $this->ensureBucketExists();
128
        $options = $this->getOptions($key);
129
        unset($options['Bucket'], $options['Key']);
130
131
        /*
132
         * If the ContentType was not already set in the metadata, then we autodetect
133
         * it to prevent everything being served up as binary/octet-stream.
134
         */
135 View Code Duplication
        if (!isset($options['ContentType']) && $this->detectContentType) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
136
            $options['ContentType'] = $this->guessContentType($content);
137
        }
138
139
        try {
140
            $this->service->upload($this->bucket, $this->computePath($key), $content, $options);
141
142
            if (is_resource($content)) {
143
                return (int) Util\Size::fromResource($content);
144
            }
145
146
            return Util\Size::fromContent($content);
0 ignored issues
show
Bug introduced by
It seems like $content defined by parameter $content on line 125 can also be of type resource; however, Gaufrette\Util\Size::fromContent() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
147
        } catch (\Exception $e) {
148
            return false;
149
        }
150
    }
151
152
    /**
153
     * {@inheritdoc}
154
     */
155
    public function exists($key)
156
    {
157
        return $this->service->has($this->bucket, $this->computePath($key));
158
    }
159
160
    /**
161
     * {@inheritdoc}
162
     */
163 View Code Duplication
    public function mtime($key)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
164
    {
165
        try {
166
            $result = $this->service->headObject($this->getOptions($key));
167
168
            return $result->getLastModified()->getTimestamp();
169
        } catch (\Exception $e) {
170
            return false;
171
        }
172
    }
173
174
    /**
175
     * {@inheritdoc}
176
     */
177
    public function size($key)
178
    {
179
        $result = $this->service->headObject($this->getOptions($key));
180
181
        return (int) $result->getContentLength();
182
    }
183
184
    public function mimeType($key)
185
    {
186
        $result = $this->service->headObject($this->getOptions($key));
187
188
        return $result->getContentType();
189
    }
190
191
    /**
192
     * {@inheritdoc}
193
     */
194
    public function keys()
195
    {
196
        return $this->listKeys();
197
    }
198
199
    /**
200
     * {@inheritdoc}
201
     */
202 View Code Duplication
    public function listKeys($prefix = '')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
203
    {
204
        $this->ensureBucketExists();
205
206
        $options = ['Bucket' => $this->bucket];
207
        if ((string) $prefix != '') {
208
            $options['Prefix'] = $this->computePath($prefix);
209
        } elseif (!empty($this->options['directory'])) {
210
            $options['Prefix'] = $this->options['directory'];
211
        }
212
213
        $keys = [];
214
        $result = $this->service->listObjectsV2($options);
215
        foreach ($result->getContents() as $file) {
216
            $keys[] = $this->computeKey($file->getKey());
217
        }
218
219
        return $keys;
220
    }
221
222
    /**
223
     * {@inheritdoc}
224
     */
225
    public function delete($key)
226
    {
227
        try {
228
            $this->service->deleteObject($this->getOptions($key));
229
230
            return true;
231
        } catch (\Exception $e) {
232
            return false;
233
        }
234
    }
235
236
    /**
237
     * {@inheritdoc}
238
     */
239
    public function isDirectory($key)
240
    {
241
        $result = $this->service->listObjectsV2([
242
            'Bucket' => $this->bucket,
243
            'Prefix' => rtrim($this->computePath($key), '/') . '/',
244
            'MaxKeys' => 1,
245
        ]);
246
247
        foreach ($result->getContents(true) as $file) {
248
            return true;
249
        }
250
251
        return false;
252
    }
253
254
    /**
255
     * Ensures the specified bucket exists. If the bucket does not exists
256
     * and the create option is set to true, it will try to create the
257
     * bucket. The bucket is created using the same region as the supplied
258
     * client object.
259
     *
260
     * @throws \RuntimeException if the bucket does not exists or could not be
261
     *                           created
262
     */
263 View Code Duplication
    protected function ensureBucketExists()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
264
    {
265
        if ($this->bucketExists) {
266
            return true;
267
        }
268
269
        if ($this->bucketExists = $this->service->bucketExists(['Bucket' => $this->bucket])->isSuccess()) {
270
            return true;
271
        }
272
273
        if (!$this->options['create']) {
274
            throw new \RuntimeException(sprintf(
275
                'The configured bucket "%s" does not exist.',
276
                $this->bucket
277
            ));
278
        }
279
280
        $this->service->createBucket([
281
            'Bucket' => $this->bucket,
282
        ]);
283
        $this->bucketExists = true;
284
285
        return true;
286
    }
287
288 View Code Duplication
    protected function getOptions($key, array $options = [])
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
289
    {
290
        $options['ACL'] = $this->options['acl'];
291
        $options['Bucket'] = $this->bucket;
292
        $options['Key'] = $this->computePath($key);
293
294
        /*
295
         * Merge global options for adapter, which are set in the constructor, with metadata.
296
         * Metadata will override global options.
297
         */
298
        $options = array_merge($this->options, $options, $this->getMetadata($key));
299
300
        return $options;
301
    }
302
303
    protected function computePath($key)
304
    {
305
        if (empty($this->options['directory'])) {
306
            return $key;
307
        }
308
309
        return sprintf('%s/%s', $this->options['directory'], $key);
310
    }
311
312
    /**
313
     * Computes the key from the specified path.
314
     *
315
     * @param string $path
316
     *
317
     * return string
318
     */
319
    protected function computeKey($path)
320
    {
321
        return ltrim(substr($path, strlen($this->options['directory'])), '/');
322
    }
323
324
    /**
325
     * @param string|resource $content
326
     *
327
     * @return string
328
     */
329 View Code Duplication
    private function guessContentType($content)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
330
    {
331
        $fileInfo = new \finfo(FILEINFO_MIME_TYPE);
332
333
        if (is_resource($content)) {
334
            return $fileInfo->file(stream_get_meta_data($content)['uri']);
335
        }
336
337
        return $fileInfo->buffer($content);
338
    }
339
}
340