These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Gaufrette\Adapter; |
||
4 | |||
5 | use Gaufrette\Adapter; |
||
6 | use Aws\S3\S3Client; |
||
7 | use Gaufrette\Util; |
||
8 | |||
9 | /** |
||
10 | * Amazon S3 adapter using the AWS SDK for PHP v2.x. |
||
11 | * |
||
12 | * @author Michael Dowling <[email protected]> |
||
13 | */ |
||
14 | class AwsS3 implements Adapter, |
||
15 | MetadataSupporter, |
||
16 | ListKeysAware, |
||
17 | SizeCalculator, |
||
18 | MimeTypeProvider |
||
19 | { |
||
20 | /** @var S3Client */ |
||
21 | protected $service; |
||
22 | /** @var string */ |
||
23 | protected $bucket; |
||
24 | /** @var array */ |
||
25 | protected $options; |
||
26 | /** @var bool */ |
||
27 | protected $bucketExists; |
||
28 | /** @var array */ |
||
29 | protected $metadata = []; |
||
30 | /** @var bool */ |
||
31 | protected $detectContentType; |
||
32 | |||
33 | /** |
||
34 | * @param S3Client $service |
||
35 | * @param string $bucket |
||
36 | * @param array $options |
||
37 | * @param bool $detectContentType |
||
38 | */ |
||
39 | View Code Duplication | public function __construct(S3Client $service, $bucket, array $options = [], $detectContentType = false) |
|
0 ignored issues
–
show
|
|||
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 | * Gets the publicly accessible URL of an Amazon S3 object. |
||
57 | * |
||
58 | * @param string $key Object key |
||
59 | * @param array $options Associative array of options used to buld the URL |
||
60 | * - expires: The time at which the URL should expire |
||
61 | * represented as a UNIX timestamp |
||
62 | * - Any options available in the Amazon S3 GetObject |
||
63 | * operation may be specified. |
||
64 | * |
||
65 | * @return string |
||
66 | * |
||
67 | * @deprecated 1.0 Resolving object path into URLs is out of the scope of this repository since v0.4. gaufrette/extras |
||
68 | * provides a Filesystem decorator with a regular resolve() method. You should use it instead. |
||
69 | * |
||
70 | * @see https://github.com/Gaufrette/extras |
||
71 | */ |
||
72 | public function getUrl($key, array $options = []) |
||
73 | { |
||
74 | @trigger_error( |
||
0 ignored issues
–
show
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.
If you suppress an error, we recommend checking for the error condition explicitly: // For example instead of
@mkdir($dir);
// Better use
if (@mkdir($dir) === false) {
throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
|
|||
75 | E_USER_DEPRECATED, |
||
76 | 'Using AwsS3::getUrl() method was deprecated since v0.4. Please chek gaufrette/extras package if you want this feature' |
||
77 | ); |
||
78 | |||
79 | return $this->service->getObjectUrl( |
||
80 | $this->bucket, |
||
81 | $this->computePath($key), |
||
82 | isset($options['expires']) ? $options['expires'] : null, |
||
0 ignored issues
–
show
The call to
S3Client::getObjectUrl() has too many arguments starting with isset($options['expires'...tions['expires'] : null .
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. In this case you can add the
Loading history...
|
|||
83 | $options |
||
84 | ); |
||
85 | } |
||
86 | |||
87 | /** |
||
88 | * {@inheritdoc} |
||
89 | */ |
||
90 | public function setMetadata($key, $metadata) |
||
91 | { |
||
92 | // BC with AmazonS3 adapter |
||
93 | if (isset($metadata['contentType'])) { |
||
94 | $metadata['ContentType'] = $metadata['contentType']; |
||
95 | unset($metadata['contentType']); |
||
96 | } |
||
97 | |||
98 | $this->metadata[$key] = $metadata; |
||
99 | } |
||
100 | |||
101 | /** |
||
102 | * {@inheritdoc} |
||
103 | */ |
||
104 | public function getMetadata($key) |
||
105 | { |
||
106 | return isset($this->metadata[$key]) ? $this->metadata[$key] : []; |
||
107 | } |
||
108 | |||
109 | /** |
||
110 | * {@inheritdoc} |
||
111 | */ |
||
112 | public function read($key) |
||
113 | { |
||
114 | $this->ensureBucketExists(); |
||
115 | $options = $this->getOptions($key); |
||
116 | |||
117 | try { |
||
118 | // Get remote object |
||
119 | $object = $this->service->getObject($options); |
||
120 | // If there's no metadata array set up for this object, set it up |
||
121 | if (!array_key_exists($key, $this->metadata) || !is_array($this->metadata[$key])) { |
||
122 | $this->metadata[$key] = []; |
||
123 | } |
||
124 | // Make remote ContentType metadata available locally |
||
125 | $this->metadata[$key]['ContentType'] = $object->get('ContentType'); |
||
126 | |||
127 | return (string) $object->get('Body'); |
||
128 | } catch (\Exception $e) { |
||
129 | return false; |
||
130 | } |
||
131 | } |
||
132 | |||
133 | /** |
||
134 | * {@inheritdoc} |
||
135 | */ |
||
136 | public function rename($sourceKey, $targetKey) |
||
137 | { |
||
138 | $this->ensureBucketExists(); |
||
139 | $options = $this->getOptions( |
||
140 | $targetKey, |
||
141 | ['CopySource' => $this->bucket.'/'.$this->computePath($sourceKey)] |
||
142 | ); |
||
143 | |||
144 | try { |
||
145 | $this->service->copyObject(array_merge($options, $this->getMetadata($targetKey))); |
||
146 | |||
147 | return $this->delete($sourceKey); |
||
148 | } catch (\Exception $e) { |
||
149 | return false; |
||
150 | } |
||
151 | } |
||
152 | |||
153 | /** |
||
154 | * {@inheritdoc} |
||
155 | */ |
||
156 | public function write($key, $content) |
||
157 | { |
||
158 | $this->ensureBucketExists(); |
||
159 | $options = $this->getOptions($key, ['Body' => $content]); |
||
160 | |||
161 | /* |
||
162 | * If the ContentType was not already set in the metadata, then we autodetect |
||
163 | * it to prevent everything being served up as binary/octet-stream. |
||
164 | */ |
||
165 | if (!isset($options['ContentType']) && $this->detectContentType) { |
||
166 | $options['ContentType'] = $this->guessContentType($content); |
||
167 | } |
||
168 | |||
169 | try { |
||
170 | $this->service->putObject($options); |
||
171 | |||
172 | if (is_resource($content)) { |
||
173 | return Util\Size::fromResource($content); |
||
0 ignored issues
–
show
The return type of
return \Gaufrette\Util\S...fromResource($content); (string ) is incompatible with the return type declared by the interface Gaufrette\Adapter::write of type integer|boolean .
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
Loading history...
|
|||
174 | } |
||
175 | |||
176 | return Util\Size::fromContent($content); |
||
177 | } catch (\Exception $e) { |
||
178 | return false; |
||
179 | } |
||
180 | } |
||
181 | |||
182 | /** |
||
183 | * {@inheritdoc} |
||
184 | */ |
||
185 | public function exists($key) |
||
186 | { |
||
187 | return $this->service->doesObjectExist($this->bucket, $this->computePath($key)); |
||
188 | } |
||
189 | |||
190 | /** |
||
191 | * {@inheritdoc} |
||
192 | */ |
||
193 | View Code Duplication | public function mtime($key) |
|
0 ignored issues
–
show
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...
|
|||
194 | { |
||
195 | try { |
||
196 | $result = $this->service->headObject($this->getOptions($key)); |
||
197 | |||
198 | return strtotime($result['LastModified']); |
||
199 | } catch (\Exception $e) { |
||
200 | return false; |
||
201 | } |
||
202 | } |
||
203 | |||
204 | /** |
||
205 | * {@inheritdoc} |
||
206 | */ |
||
207 | View Code Duplication | public function size($key) |
|
0 ignored issues
–
show
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...
|
|||
208 | { |
||
209 | try { |
||
210 | $result = $this->service->headObject($this->getOptions($key)); |
||
211 | |||
212 | return $result['ContentLength']; |
||
213 | } catch (\Exception $e) { |
||
214 | return false; |
||
0 ignored issues
–
show
The return type of
return false; (false ) is incompatible with the return type declared by the interface Gaufrette\Adapter\SizeCalculator::size of type integer .
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
Loading history...
|
|||
215 | } |
||
216 | } |
||
217 | |||
218 | /** |
||
219 | * {@inheritdoc} |
||
220 | */ |
||
221 | public function keys() |
||
222 | { |
||
223 | return $this->listKeys(); |
||
224 | } |
||
225 | |||
226 | /** |
||
227 | * {@inheritdoc} |
||
228 | */ |
||
229 | public function listKeys($prefix = '') |
||
230 | { |
||
231 | $this->ensureBucketExists(); |
||
232 | |||
233 | $options = ['Bucket' => $this->bucket]; |
||
234 | View Code Duplication | if ((string) $prefix != '') { |
|
0 ignored issues
–
show
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...
|
|||
235 | $options['Prefix'] = $this->computePath($prefix); |
||
236 | } elseif (!empty($this->options['directory'])) { |
||
237 | $options['Prefix'] = $this->options['directory']; |
||
238 | } |
||
239 | |||
240 | $keys = []; |
||
241 | $iter = $this->service->getIterator('ListObjects', $options); |
||
242 | foreach ($iter as $file) { |
||
243 | $keys[] = $this->computeKey($file['Key']); |
||
244 | } |
||
245 | |||
246 | return $keys; |
||
247 | } |
||
248 | |||
249 | /** |
||
250 | * {@inheritdoc} |
||
251 | */ |
||
252 | public function delete($key) |
||
253 | { |
||
254 | try { |
||
255 | $this->service->deleteObject($this->getOptions($key)); |
||
256 | |||
257 | return true; |
||
258 | } catch (\Exception $e) { |
||
259 | return false; |
||
260 | } |
||
261 | } |
||
262 | |||
263 | /** |
||
264 | * {@inheritdoc} |
||
265 | */ |
||
266 | public function isDirectory($key) |
||
267 | { |
||
268 | $result = $this->service->listObjects([ |
||
269 | 'Bucket' => $this->bucket, |
||
270 | 'Prefix' => rtrim($this->computePath($key), '/').'/', |
||
271 | 'MaxKeys' => 1, |
||
272 | ]); |
||
273 | if (isset($result['Contents'])) { |
||
274 | if (is_array($result['Contents']) || $result['Contents'] instanceof \Countable) { |
||
275 | return count($result['Contents']) > 0; |
||
276 | } |
||
277 | } |
||
278 | |||
279 | return false; |
||
280 | } |
||
281 | |||
282 | /** |
||
283 | * Ensures the specified bucket exists. If the bucket does not exists |
||
284 | * and the create option is set to true, it will try to create the |
||
285 | * bucket. The bucket is created using the same region as the supplied |
||
286 | * client object. |
||
287 | * |
||
288 | * @throws \RuntimeException if the bucket does not exists or could not be |
||
289 | * created |
||
290 | */ |
||
291 | protected function ensureBucketExists() |
||
292 | { |
||
293 | if ($this->bucketExists) { |
||
294 | return true; |
||
295 | } |
||
296 | |||
297 | if ($this->bucketExists = $this->service->doesBucketExist($this->bucket)) { |
||
298 | return true; |
||
299 | } |
||
300 | |||
301 | if (!$this->options['create']) { |
||
302 | throw new \RuntimeException(sprintf( |
||
303 | 'The configured bucket "%s" does not exist.', |
||
304 | $this->bucket |
||
305 | )); |
||
306 | } |
||
307 | |||
308 | $this->service->createBucket([ |
||
309 | 'Bucket' => $this->bucket, |
||
310 | 'LocationConstraint' => $this->service->getRegion() |
||
311 | ]); |
||
312 | $this->bucketExists = true; |
||
313 | |||
314 | return true; |
||
315 | } |
||
316 | |||
317 | protected function getOptions($key, array $options = []) |
||
318 | { |
||
319 | $options['ACL'] = $this->options['acl']; |
||
320 | $options['Bucket'] = $this->bucket; |
||
321 | $options['Key'] = $this->computePath($key); |
||
322 | |||
323 | /* |
||
324 | * Merge global options for adapter, which are set in the constructor, with metadata. |
||
325 | * Metadata will override global options. |
||
326 | */ |
||
327 | $options = array_merge($this->options, $options, $this->getMetadata($key)); |
||
328 | |||
329 | return $options; |
||
330 | } |
||
331 | |||
332 | View Code Duplication | protected function computePath($key) |
|
0 ignored issues
–
show
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...
|
|||
333 | { |
||
334 | if (empty($this->options['directory'])) { |
||
335 | return $key; |
||
336 | } |
||
337 | |||
338 | return sprintf('%s/%s', $this->options['directory'], $key); |
||
339 | } |
||
340 | |||
341 | /** |
||
342 | * Computes the key from the specified path. |
||
343 | * |
||
344 | * @param string $path |
||
345 | * |
||
346 | * return string |
||
347 | */ |
||
348 | protected function computeKey($path) |
||
349 | { |
||
350 | return ltrim(substr($path, strlen($this->options['directory'])), '/'); |
||
351 | } |
||
352 | |||
353 | /** |
||
354 | * @param string $content |
||
355 | * |
||
356 | * @return string |
||
357 | */ |
||
358 | View Code Duplication | private function guessContentType($content) |
|
0 ignored issues
–
show
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...
|
|||
359 | { |
||
360 | $fileInfo = new \finfo(FILEINFO_MIME_TYPE); |
||
361 | |||
362 | if (is_resource($content)) { |
||
363 | return $fileInfo->file(stream_get_meta_data($content)['uri']); |
||
364 | } |
||
365 | |||
366 | return $fileInfo->buffer($content); |
||
367 | } |
||
368 | |||
369 | View Code Duplication | public function mimeType($key) |
|
0 ignored issues
–
show
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...
|
|||
370 | { |
||
371 | try { |
||
372 | $result = $this->service->headObject($this->getOptions($key)); |
||
373 | return ($result['ContentType']); |
||
374 | } catch (\Exception $e) { |
||
375 | return false; |
||
0 ignored issues
–
show
The return type of
return false; (false ) is incompatible with the return type declared by the interface Gaufrette\Adapter\MimeTypeProvider::mimeType of type string .
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
Loading history...
|
|||
376 | } |
||
377 | } |
||
378 | } |
||
379 |
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.