1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Gaufrette\Adapter\GCS; |
4
|
|
|
|
5
|
|
|
use Gaufrette\Adapter; |
6
|
|
|
|
7
|
|
|
/** |
8
|
|
|
* Google Cloud Storage adapter using the Google APIs Client Library for PHP. |
9
|
|
|
* |
10
|
|
|
* @author Patrik Karisch <[email protected]> |
11
|
|
|
*/ |
12
|
|
View Code Duplication |
class GCS implements Adapter, Adapter\MetadataSupporter, Adapter\ListKeysAware |
|
|
|
|
13
|
|
|
{ |
14
|
|
|
protected $service; |
15
|
|
|
protected $bucket; |
16
|
|
|
protected $options; |
17
|
|
|
protected $bucketExists; |
18
|
|
|
protected $metadata = array(); |
19
|
|
|
protected $detectContentType; |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* @param \Google_Service_Storage $service The storage service class with authenticated |
23
|
|
|
* client and full access scope |
24
|
|
|
* @param string $bucket The bucket name |
25
|
|
|
* @param array $options Options can be directory and acl |
26
|
|
|
* @param bool $detectContentType Whether to detect the content type or not |
27
|
|
|
*/ |
28
|
|
|
public function __construct( |
29
|
|
|
\Google_Service_Storage $service, |
30
|
|
|
$bucket, |
31
|
|
|
array $options = array(), |
32
|
|
|
$detectContentType = false |
33
|
|
|
) { |
34
|
|
|
$this->service = $service; |
35
|
|
|
$this->bucket = $bucket; |
36
|
|
|
$this->options = array_replace( |
37
|
|
|
array( |
38
|
|
|
'directory' => '', |
39
|
|
|
'acl' => 'private', |
40
|
|
|
), |
41
|
|
|
$options |
42
|
|
|
); |
43
|
|
|
|
44
|
|
|
$this->detectContentType = $detectContentType; |
45
|
|
|
} |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* @return array The actual options |
49
|
|
|
*/ |
50
|
|
|
public function getOptions() |
51
|
|
|
{ |
52
|
|
|
return $this->options; |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* @param array $options The new options |
57
|
|
|
*/ |
58
|
|
|
public function setOptions($options) |
59
|
|
|
{ |
60
|
|
|
$this->options = array_replace($this->options, $options); |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* @return string The current bucket name |
65
|
|
|
*/ |
66
|
|
|
public function getBucket() |
67
|
|
|
{ |
68
|
|
|
return $this->bucket; |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* Sets a new bucket name. |
73
|
|
|
* |
74
|
|
|
* @param string $bucket The new bucket name |
75
|
|
|
*/ |
76
|
|
|
public function setBucket($bucket) |
77
|
|
|
{ |
78
|
|
|
$this->bucket = $bucket; |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* {@inheritdoc} |
83
|
|
|
*/ |
84
|
|
|
public function read($key) |
85
|
|
|
{ |
86
|
|
|
$this->ensureBucketExists(); |
87
|
|
|
$path = $this->computePath($key); |
88
|
|
|
|
89
|
|
|
$object = $this->getObjectData($path); |
90
|
|
|
if ($object === false) { |
91
|
|
|
return false; |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
$request = new \Google_Http_Request($object->getMediaLink()); |
95
|
|
|
$this->service->getClient()->getAuth()->sign($request); |
96
|
|
|
|
97
|
|
|
$response = $this->service->getClient()->getIo()->executeRequest($request); |
98
|
|
|
|
99
|
|
|
if ($response[2] == 200) { |
100
|
|
|
$this->setMetadata($key, $object->getMetadata()); |
101
|
|
|
|
102
|
|
|
return $response[0]; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
return false; |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* {@inheritdoc} |
110
|
|
|
*/ |
111
|
|
|
public function write($key, $content) |
112
|
|
|
{ |
113
|
|
|
$this->ensureBucketExists(); |
114
|
|
|
$path = $this->computePath($key); |
115
|
|
|
|
116
|
|
|
$metadata = $this->getMetadata($key); |
117
|
|
|
$options = array( |
118
|
|
|
'uploadType' => 'multipart', |
119
|
|
|
'data' => $content, |
120
|
|
|
); |
121
|
|
|
|
122
|
|
|
/* |
123
|
|
|
* If the ContentType was not already set in the metadata, then we autodetect |
124
|
|
|
* it to prevent everything being served up as application/octet-stream. |
125
|
|
|
*/ |
126
|
|
|
if (!isset($metadata['ContentType']) && $this->detectContentType) { |
127
|
|
|
$finfo = new \finfo(FILEINFO_MIME_TYPE); |
128
|
|
|
$options['mimeType'] = $finfo->buffer($content); |
129
|
|
|
unset($metadata['ContentType']); |
130
|
|
|
} elseif (isset($metadata['ContentType'])) { |
131
|
|
|
$options['mimeType'] = $metadata['ContentType']; |
132
|
|
|
unset($metadata['ContentType']); |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
$object = new \Google_Service_Storage_StorageObject(); |
136
|
|
|
$object->name = $path; |
137
|
|
|
|
138
|
|
|
if (isset($metadata['ContentDisposition'])) { |
139
|
|
|
$object->setContentDisposition($metadata['ContentDisposition']); |
140
|
|
|
unset($metadata['ContentDisposition']); |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
if (isset($metadata['CacheControl'])) { |
144
|
|
|
$object->setCacheControl($metadata['CacheControl']); |
145
|
|
|
unset($metadata['CacheControl']); |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
if (isset($metadata['ContentLanguage'])) { |
149
|
|
|
$object->setContentLanguage($metadata['ContentLanguage']); |
150
|
|
|
unset($metadata['ContentLanguage']); |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
if (isset($metadata['ContentEncoding'])) { |
154
|
|
|
$object->setContentEncoding($metadata['ContentEncoding']); |
155
|
|
|
unset($metadata['ContentEncoding']); |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
$object->setMetadata($metadata); |
159
|
|
|
|
160
|
|
|
try { |
161
|
|
|
$object = $this->service->objects->insert($this->bucket, $object, $options); |
162
|
|
|
|
163
|
|
|
if ($this->options['acl'] == 'public') { |
164
|
|
|
$acl = new \Google_Service_Storage_ObjectAccessControl(); |
165
|
|
|
$acl->setEntity('allUsers'); |
166
|
|
|
$acl->setRole('READER'); |
167
|
|
|
|
168
|
|
|
$this->service->objectAccessControls->insert($this->bucket, $path, $acl); |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
return $object->getSize(); |
172
|
|
|
} catch (\Google_Service_Exception $e) { |
|
|
|
|
173
|
|
|
return false; |
174
|
|
|
} |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
/** |
178
|
|
|
* {@inheritdoc} |
179
|
|
|
*/ |
180
|
|
|
public function exists($key) |
181
|
|
|
{ |
182
|
|
|
$this->ensureBucketExists(); |
183
|
|
|
$path = $this->computePath($key); |
184
|
|
|
|
185
|
|
|
try { |
186
|
|
|
$this->service->objects->get($this->bucket, $path); |
187
|
|
|
} catch (\Google_Service_Exception $e) { |
|
|
|
|
188
|
|
|
return false; |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
return true; |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* {@inheritdoc} |
196
|
|
|
*/ |
197
|
|
|
public function keys() |
198
|
|
|
{ |
199
|
|
|
return $this->listKeys(); |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
/** |
203
|
|
|
* {@inheritdoc} |
204
|
|
|
*/ |
205
|
|
|
public function mtime($key) |
206
|
|
|
{ |
207
|
|
|
$this->ensureBucketExists(); |
208
|
|
|
$path = $this->computePath($key); |
209
|
|
|
|
210
|
|
|
$object = $this->getObjectData($path); |
211
|
|
|
|
212
|
|
|
return $object ? strtotime($object->getUpdated()) : false; |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* {@inheritdoc} |
217
|
|
|
*/ |
218
|
|
|
public function delete($key) |
219
|
|
|
{ |
220
|
|
|
$this->ensureBucketExists(); |
221
|
|
|
$path = $this->computePath($key); |
222
|
|
|
|
223
|
|
|
try { |
224
|
|
|
$this->service->objects->delete($this->bucket, $path); |
225
|
|
|
} catch (\Google_Service_Exception $e) { |
|
|
|
|
226
|
|
|
return false; |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
return true; |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
/** |
233
|
|
|
* {@inheritdoc} |
234
|
|
|
*/ |
235
|
|
|
public function rename($sourceKey, $targetKey) |
236
|
|
|
{ |
237
|
|
|
$this->ensureBucketExists(); |
238
|
|
|
$sourcePath = $this->computePath($sourceKey); |
239
|
|
|
$targetPath = $this->computePath($targetKey); |
240
|
|
|
|
241
|
|
|
$object = $this->getObjectData($sourcePath); |
242
|
|
|
if ($object === false) { |
243
|
|
|
return false; |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
try { |
247
|
|
|
$this->service->objects->copy($this->bucket, $sourcePath, $this->bucket, $targetPath, $object); |
248
|
|
|
$this->delete($sourcePath); |
249
|
|
|
} catch (\Google_Service_Exception $e) { |
|
|
|
|
250
|
|
|
return false; |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
return true; |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
/** |
257
|
|
|
* {@inheritdoc} |
258
|
|
|
*/ |
259
|
|
|
public function isDirectory($key) |
260
|
|
|
{ |
261
|
|
|
if ($this->exists($key.'/')) { |
262
|
|
|
return true; |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
return false; |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
/** |
269
|
|
|
* {@inheritdoc} |
270
|
|
|
*/ |
271
|
|
|
public function listKeys($prefix = '') |
272
|
|
|
{ |
273
|
|
|
$this->ensureBucketExists(); |
274
|
|
|
|
275
|
|
|
$options = array(); |
276
|
|
|
if ((string) $prefix != '') { |
277
|
|
|
$options['prefix'] = $this->computePath($prefix); |
278
|
|
|
} elseif (!empty($this->options['directory'])) { |
279
|
|
|
$options['prefix'] = $this->options['directory']; |
280
|
|
|
} |
281
|
|
|
|
282
|
|
|
$list = $this->service->objects->listObjects($this->bucket, $options); |
283
|
|
|
$keys = array(); |
284
|
|
|
|
285
|
|
|
// FIXME: Temporary workaround for google/google-api-php-client#375 |
286
|
|
|
$reflectionClass = new \ReflectionClass('Google_Service_Storage_Objects'); |
287
|
|
|
$reflectionProperty = $reflectionClass->getProperty('collection_key'); |
288
|
|
|
$reflectionProperty->setAccessible(true); |
289
|
|
|
$reflectionProperty->setValue($list, 'items'); |
290
|
|
|
|
291
|
|
|
/** @var \Google_Service_Storage_StorageObject $object */ |
292
|
|
|
foreach ($list as $object) { |
293
|
|
|
$keys[] = $object->name; |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
sort($keys); |
297
|
|
|
|
298
|
|
|
return $keys; |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
/** |
302
|
|
|
* {@inheritdoc} |
303
|
|
|
*/ |
304
|
|
|
public function setMetadata($key, $content) |
305
|
|
|
{ |
306
|
|
|
$path = $this->computePath($key); |
307
|
|
|
|
308
|
|
|
$this->metadata[$path] = $content; |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
/** |
312
|
|
|
* {@inheritdoc} |
313
|
|
|
*/ |
314
|
|
|
public function getMetadata($key) |
315
|
|
|
{ |
316
|
|
|
$path = $this->computePath($key); |
317
|
|
|
|
318
|
|
|
return isset($this->metadata[$path]) ? $this->metadata[$path] : array(); |
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
/** |
322
|
|
|
* Ensures the specified bucket exists. |
323
|
|
|
* |
324
|
|
|
* @throws \RuntimeException if the bucket does not exists |
325
|
|
|
*/ |
326
|
|
|
protected function ensureBucketExists() |
327
|
|
|
{ |
328
|
|
|
if ($this->bucketExists) { |
329
|
|
|
return; |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
try { |
333
|
|
|
$this->service->buckets->get($this->bucket); |
334
|
|
|
$this->bucketExists = true; |
335
|
|
|
|
336
|
|
|
return; |
337
|
|
|
} catch (\Google_Service_Exception $e) { |
|
|
|
|
338
|
|
|
$this->bucketExists = false; |
339
|
|
|
|
340
|
|
|
throw new \RuntimeException( |
341
|
|
|
sprintf( |
342
|
|
|
'The configured bucket "%s" does not exist.', |
343
|
|
|
$this->bucket |
344
|
|
|
) |
345
|
|
|
); |
346
|
|
|
} |
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
protected function computePath($key) |
350
|
|
|
{ |
351
|
|
|
if (empty($this->options['directory'])) { |
352
|
|
|
return $key; |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
return sprintf('%s/%s', $this->options['directory'], $key); |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
/** |
359
|
|
|
* @param string $path |
360
|
|
|
* @param array $options |
361
|
|
|
* |
362
|
|
|
* @return bool|\Google_Service_Storage_StorageObject |
363
|
|
|
*/ |
364
|
|
|
private function getObjectData($path, $options = array()) |
365
|
|
|
{ |
366
|
|
|
try { |
367
|
|
|
return $this->service->objects->get($this->bucket, $path, $options); |
368
|
|
|
} catch (\Google_Service_Exception $e) { |
|
|
|
|
369
|
|
|
return false; |
370
|
|
|
} |
371
|
|
|
} |
372
|
|
|
} |
373
|
|
|
|
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.