1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace ArgentCrusade\Selectel\CloudStorage; |
4
|
|
|
|
5
|
|
|
use ArgentCrusade\Selectel\CloudStorage\Collections\Collection; |
6
|
|
|
use ArgentCrusade\Selectel\CloudStorage\Contracts\Api\ApiClientContract; |
7
|
|
|
use ArgentCrusade\Selectel\CloudStorage\Contracts\FilesTransformerContract; |
8
|
|
|
use ArgentCrusade\Selectel\CloudStorage\Contracts\FluentFilesLoaderContract; |
9
|
|
|
use ArgentCrusade\Selectel\CloudStorage\Exceptions\ApiRequestFailedException; |
10
|
|
|
use ArgentCrusade\Selectel\CloudStorage\Exceptions\FileNotFoundException; |
11
|
|
|
use ArgentCrusade\Selectel\CloudStorage\Traits\FilesTransformer; |
12
|
|
|
|
13
|
|
|
class FluentFilesLoader implements FluentFilesLoaderContract, FilesTransformerContract |
14
|
|
|
{ |
15
|
|
|
use FilesTransformer; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* API Client. |
19
|
|
|
* |
20
|
|
|
* @var \ArgentCrusade\Selectel\CloudStorage\Contracts\Api\ApiClientContract |
21
|
|
|
*/ |
22
|
|
|
protected $api; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* Container name. |
26
|
|
|
* |
27
|
|
|
* @var string |
28
|
|
|
*/ |
29
|
|
|
protected $containerName = ''; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* Container URL. |
33
|
|
|
* |
34
|
|
|
* @var string |
35
|
|
|
*/ |
36
|
|
|
protected $containerUrl = ''; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* Default parameters. |
40
|
|
|
* |
41
|
|
|
* @var array |
42
|
|
|
*/ |
43
|
|
|
protected $params = [ |
44
|
|
|
'limit' => 10000, |
45
|
|
|
'marker' => '', |
46
|
|
|
'path' => '', |
47
|
|
|
'prefix' => '', |
48
|
|
|
'delimiter' => '', |
49
|
|
|
]; |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* Determines if resulting Collection should container File objects |
53
|
|
|
* instead of file arrays. |
54
|
|
|
* |
55
|
|
|
* @var bool |
56
|
|
|
*/ |
57
|
|
|
protected $asFileObjects = false; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* @param \ArgentCrusade\Selectel\CloudStorage\Contracts\Api\ApiClientContract $api |
61
|
|
|
* @param string $container |
62
|
|
|
* @param string $containerUrl |
63
|
|
|
*/ |
64
|
|
|
public function __construct(ApiClientContract $api, $container, $containerUrl) |
65
|
|
|
{ |
66
|
|
|
$this->api = $api; |
67
|
|
|
$this->containerName = $container; |
68
|
|
|
$this->containerUrl = $containerUrl; |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* Sets loader parameter. |
73
|
|
|
* |
74
|
|
|
* @param string $key |
75
|
|
|
* @param string|int $value |
76
|
|
|
* @param bool $trimLeadingSlashes = true |
77
|
|
|
* |
78
|
|
|
* @return \ArgentCrusade\Selectel\CloudStorage\Contracts\FluentFilesLoaderContract |
79
|
|
|
*/ |
80
|
|
|
protected function setParam($key, $value, $trimLeadingSlashes = true) |
81
|
|
|
{ |
82
|
|
|
$this->params[$key] = $trimLeadingSlashes ? ltrim($value, '/') : $value; |
83
|
|
|
|
84
|
|
|
return $this; |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* Container name. |
89
|
|
|
* |
90
|
|
|
* @return string |
91
|
|
|
*/ |
92
|
|
|
public function containerName() |
93
|
|
|
{ |
94
|
|
|
return $this->containerName; |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* Sets directory from where load files. This value may be overwritten |
99
|
|
|
* to empty string if you're loading prefixed files from directory. |
100
|
|
|
* |
101
|
|
|
* @param string $directory |
102
|
|
|
* |
103
|
|
|
* @return \ArgentCrusade\Selectel\CloudStorage\Contracts\FluentFilesLoaderContract |
104
|
|
|
*/ |
105
|
|
|
public function fromDirectory($directory) |
106
|
|
|
{ |
107
|
|
|
return $this->setParam('path', $directory); |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* Sets files prefix. If you're planning to find prefixed files from a directory |
112
|
|
|
* (using along with fromDirectory method), do not provide path to a directory |
113
|
|
|
* here, since it will be appended to final prefix (before sending request). |
114
|
|
|
* |
115
|
|
|
* @param string $prefix |
116
|
|
|
* |
117
|
|
|
* @return \ArgentCrusade\Selectel\CloudStorage\Contracts\FluentFilesLoaderContract |
118
|
|
|
*/ |
119
|
|
|
public function withPrefix($prefix) |
120
|
|
|
{ |
121
|
|
|
return $this->setParam('prefix', $prefix); |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* Sets files delimiter. |
126
|
|
|
* |
127
|
|
|
* @param string $delimiter |
128
|
|
|
* |
129
|
|
|
* @return \ArgentCrusade\Selectel\CloudStorage\Contracts\FluentFilesLoaderContract |
130
|
|
|
*/ |
131
|
|
|
public function withDelimiter($delimiter) |
132
|
|
|
{ |
133
|
|
|
return $this->setParam('delimiter', $delimiter, false); |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
/** |
137
|
|
|
* Sets files limit. If you need to paginate through results, pass markerFile |
138
|
|
|
* argument with latest filename from previous request as value. If you're |
139
|
|
|
* working within a directory, its path will be appended to markerFile. |
140
|
|
|
* |
141
|
|
|
* @param int $limit |
142
|
|
|
* @param string $markerFile = '' |
143
|
|
|
* |
144
|
|
|
* @return \ArgentCrusade\Selectel\CloudStorage\Contracts\FluentFilesLoaderContract |
145
|
|
|
*/ |
146
|
|
|
public function limit($limit, $markerFile = '') |
147
|
|
|
{ |
148
|
|
|
return $this->setParam('limit', intval($limit), false) |
149
|
|
|
->setParam('marker', $markerFile, false); |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* Tells builder to return Collection of File objects instead of arrays. |
154
|
|
|
* |
155
|
|
|
* @return \ArgentCrusade\Selectel\CloudStorage\Contracts\FluentFilesLoaderContract |
156
|
|
|
*/ |
157
|
|
|
public function asFileObjects() |
158
|
|
|
{ |
159
|
|
|
$this->asFileObjects = true; |
160
|
|
|
|
161
|
|
|
return $this; |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* Loads all available files from container. |
166
|
|
|
* |
167
|
|
|
* @throws \ArgentCrusade\Selectel\CloudStorage\Exceptions\ApiRequestFailedException |
168
|
|
|
* |
169
|
|
|
* @return \ArgentCrusade\Selectel\CloudStorage\Contracts\Collections\CollectionContract |
170
|
|
|
*/ |
171
|
|
|
public function all() |
172
|
|
|
{ |
173
|
|
|
return $this->fromDirectory('') |
174
|
|
|
->withPrefix('') |
175
|
|
|
->withDelimiter('') |
176
|
|
|
->limit(10000) |
177
|
|
|
->get(); |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
/** |
181
|
|
|
* Determines whether file exists or not. |
182
|
|
|
* |
183
|
|
|
* @param string $path File path. |
184
|
|
|
* |
185
|
|
|
* @return bool |
186
|
|
|
*/ |
187
|
|
|
public function exists($path) |
188
|
|
|
{ |
189
|
|
|
return !is_null($this->findFileAt($path)); |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* Finds single file at given path. |
194
|
|
|
* |
195
|
|
|
* @param string $path |
196
|
|
|
* |
197
|
|
|
* @throws \ArgentCrusade\Selectel\CloudStorage\Exceptions\FileNotFoundException |
198
|
|
|
* |
199
|
|
|
* @return \ArgentCrusade\Selectel\CloudStorage\Contracts\FileContract |
200
|
|
|
*/ |
201
|
|
|
public function find($path) |
202
|
|
|
{ |
203
|
|
|
$file = $this->findFileAt($path); |
204
|
|
|
|
205
|
|
|
if (is_null($file)) { |
206
|
|
|
throw new FileNotFoundException('File "'.$path.'" was not found.'); |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
return new File($this->api, $this->containerName(), $file); |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
/** |
213
|
|
|
* Loads file from path. |
214
|
|
|
* |
215
|
|
|
* @param string $path |
216
|
|
|
* |
217
|
|
|
* @return array|null |
218
|
|
|
*/ |
219
|
|
|
protected function findFileAt($path) |
220
|
|
|
{ |
221
|
|
|
try { |
222
|
|
|
$files = $this->fromDirectory('') |
223
|
|
|
->withPrefix($path) |
224
|
|
|
->withDelimiter('') |
225
|
|
|
->limit(1) |
226
|
|
|
->get(); |
227
|
|
|
} catch (ApiRequestFailedException $e) { |
228
|
|
|
return; |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
return $files->get(0); |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
/** |
235
|
|
|
* Loads files. |
236
|
|
|
* |
237
|
|
|
* @throws \ArgentCrusade\Selectel\CloudStorage\Exceptions\ApiRequestFailedException |
238
|
|
|
* |
239
|
|
|
* @return \ArgentCrusade\Selectel\CloudStorage\Contracts\Collections\CollectionContract |
240
|
|
|
*/ |
241
|
|
|
public function get() |
242
|
|
|
{ |
243
|
|
|
$response = $this->api->request('GET', $this->containerUrl, [ |
244
|
|
|
'query' => $this->buildParams(), |
245
|
|
|
]); |
246
|
|
|
|
247
|
|
|
if ($response->getStatusCode() !== 200) { |
248
|
|
|
throw new ApiRequestFailedException('Unable to list container files.', $response->getStatusCode()); |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
$files = json_decode($response->getBody(), true); |
252
|
|
|
|
253
|
|
|
if ($this->asFileObjects === true) { |
254
|
|
|
$this->asFileObjects = false; |
255
|
|
|
|
256
|
|
|
return $this->getFilesCollectionFromArrays($files); |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
// Add 'filename' attribute to each file, so users |
260
|
|
|
// can pass it to new loader instance as marker, |
261
|
|
|
// if they want to iterate inside a directory. |
262
|
|
|
|
263
|
|
|
$files = array_map(function ($file) { |
264
|
|
|
$path = explode('/', $file['name']); |
265
|
|
|
$file['filename'] = array_pop($path); |
266
|
|
|
|
267
|
|
|
return $file; |
268
|
|
|
}, $files); |
269
|
|
|
|
270
|
|
|
return new Collection($files); |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
/** |
274
|
|
|
* Builds query parameters. |
275
|
|
|
* |
276
|
|
|
* @return array |
277
|
|
|
*/ |
278
|
|
|
protected function buildParams() |
279
|
|
|
{ |
280
|
|
|
// If user wants to paginate files let's check if they're working |
281
|
|
|
// in a specific directory, so they can provide only filename, |
282
|
|
|
// instead of sending full directory path with file marker. |
283
|
|
|
|
284
|
|
View Code Duplication |
if ($this->params['marker'] && $this->params['path']) { |
|
|
|
|
285
|
|
|
$this->params['marker'] = $this->params['path'].'/'.ltrim($this->params['marker'], '/'); |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
// Also, if user is loading prefixed files from a directory |
289
|
|
|
// there's no need to send directory path with prefix. We |
290
|
|
|
// can append path to prefix and then reset path value. |
291
|
|
|
|
292
|
|
View Code Duplication |
if ($this->params['prefix'] && $this->params['path']) { |
|
|
|
|
293
|
|
|
$this->params['prefix'] = $this->params['path'].'/'.ltrim($this->params['prefix']); |
294
|
|
|
$this->params['path'] = ''; |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
return $this->params; |
298
|
|
|
} |
299
|
|
|
} |
300
|
|
|
|
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.