Completed
Push — master ( af2d08...69b46f )
by Timur
02:12
created

FluentFilesLoader::apiClient()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
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
     * API Client.
89
     *
90
     * @return \ArgentCrusade\Selectel\CloudStorage\Contracts\Api\ApiClientContract
91
     */
92
    public function apiClient()
93
    {
94
        return $this->api;
95
    }
96
97
    /**
98
     * Container name.
99
     *
100
     * @return string
101
     */
102
    public function containerName()
103
    {
104
        return $this->containerName;
105
    }
106
107
    /**
108
     * Sets directory from where load files. This value may be overwritten
109
     * to empty string if you're loading prefixed files from directory.
110
     *
111
     * @param string $directory
112
     *
113
     * @return \ArgentCrusade\Selectel\CloudStorage\Contracts\FluentFilesLoaderContract
114
     */
115
    public function fromDirectory($directory)
116
    {
117
        return $this->setParam('path', $directory);
118
    }
119
120
    /**
121
     * Sets files prefix. If you're planning to find prefixed files from a directory
122
     * (using along with fromDirectory method), do not provide path to a directory
123
     * here, since it will be appended to final prefix (before sending request).
124
     *
125
     * @param string $prefix
126
     *
127
     * @return \ArgentCrusade\Selectel\CloudStorage\Contracts\FluentFilesLoaderContract
128
     */
129
    public function withPrefix($prefix)
130
    {
131
        return $this->setParam('prefix', $prefix);
132
    }
133
134
    /**
135
     * Sets files delimiter.
136
     *
137
     * @param string $delimiter
138
     *
139
     * @return \ArgentCrusade\Selectel\CloudStorage\Contracts\FluentFilesLoaderContract
140
     */
141
    public function withDelimiter($delimiter)
142
    {
143
        return $this->setParam('delimiter', $delimiter, false);
144
    }
145
146
    /**
147
     * Sets files limit. If you need to paginate through results, pass markerFile
148
     * argument with latest filename from previous request as value. If you're
149
     * working within a directory, its path will be appended to markerFile.
150
     *
151
     * @param int    $limit
152
     * @param string $markerFile = ''
153
     *
154
     * @return \ArgentCrusade\Selectel\CloudStorage\Contracts\FluentFilesLoaderContract
155
     */
156
    public function limit($limit, $markerFile = '')
157
    {
158
        return $this->setParam('limit', intval($limit), false)
159
            ->setParam('marker', $markerFile, false);
160
    }
161
162
    /**
163
     * Tells builder to return Collection of File objects instead of arrays.
164
     *
165
     * @return \ArgentCrusade\Selectel\CloudStorage\Contracts\FluentFilesLoaderContract
166
     */
167
    public function asFileObjects()
168
    {
169
        $this->asFileObjects = true;
170
171
        return $this;
172
    }
173
174
    /**
175
     * Loads all available files from container.
176
     *
177
     * @throws \ArgentCrusade\Selectel\CloudStorage\Exceptions\ApiRequestFailedException
178
     *
179
     * @return \ArgentCrusade\Selectel\CloudStorage\Contracts\Collections\CollectionContract
180
     */
181
    public function all()
182
    {
183
        return $this->fromDirectory('')
184
            ->withPrefix('')
185
            ->withDelimiter('')
186
            ->limit(10000)
187
            ->get();
188
    }
189
190
    /**
191
     * Determines whether file exists or not.
192
     *
193
     * @param string $path File path.
194
     *
195
     * @return bool
196
     */
197
    public function exists($path)
198
    {
199
        return !is_null($this->findFileAt($path));
200
    }
201
202
    /**
203
     * Finds single file at given path.
204
     *
205
     * @param string $path
206
     *
207
     * @throws \ArgentCrusade\Selectel\CloudStorage\Exceptions\FileNotFoundException
208
     *
209
     * @return \ArgentCrusade\Selectel\CloudStorage\Contracts\FileContract
210
     */
211
    public function find($path)
212
    {
213
        $file = $this->findFileAt($path);
214
215
        if (is_null($file)) {
216
            throw new FileNotFoundException('File "'.$path.'" was not found.');
217
        }
218
219
        return new File($this->api, $this->containerName(), $file);
220
    }
221
222
    /**
223
     * Loads file from path.
224
     *
225
     * @param string $path
226
     *
227
     * @return array|null
228
     */
229
    protected function findFileAt($path)
230
    {
231
        try {
232
            $files = $this->fromDirectory('')
233
                ->withPrefix($path)
234
                ->withDelimiter('')
235
                ->limit(1)
236
                ->get();
237
        } catch (ApiRequestFailedException $e) {
238
            return;
239
        }
240
241
        return $files->get(0);
242
    }
243
244
    /**
245
     * Loads files.
246
     *
247
     * @throws \ArgentCrusade\Selectel\CloudStorage\Exceptions\ApiRequestFailedException
248
     *
249
     * @return \ArgentCrusade\Selectel\CloudStorage\Contracts\Collections\CollectionContract
250
     */
251
    public function get()
252
    {
253
        $response = $this->api->request('GET', $this->containerUrl, [
254
            'query' => $this->buildParams(),
255
        ]);
256
257
        if ($response->getStatusCode() !== 200) {
258
            throw new ApiRequestFailedException('Unable to list container files.', $response->getStatusCode());
259
        }
260
261
        $files = json_decode($response->getBody(), true);
262
263
        if ($this->asFileObjects === true) {
264
            $this->asFileObjects = false;
265
266
            return $this->getFilesCollectionFromArrays($files);
267
        }
268
269
        // Add 'filename' attribute to each file, so users
270
        // can pass it to new loader instance as marker,
271
        // if they want to iterate inside a directory.
272
273
        $files = array_map(function ($file) {
274
            $path = explode('/', $file['name']);
275
            $file['filename'] = array_pop($path);
276
277
            return $file;
278
        }, $files);
279
280
        return new Collection($files);
281
    }
282
283
    /**
284
     * Builds query parameters.
285
     *
286
     * @return array
287
     */
288
    protected function buildParams()
289
    {
290
        // If user wants to paginate files let's check if they're working
291
        // in a specific directory, so they can provide only filename,
292
        // instead of sending full directory path with file marker.
293
294
        // Also, if user is loading prefixed files from a directory
295
        // there's no need to send directory path with prefix. We
296
        // can append path to prefix and then reset path value.
297
298
        $this->appendPathToParam('marker')
299
            ->appendPathToParam('prefix', true);
300
301
        return $this->params;
302
    }
303
304
    /**
305
     * @param string $key
306
     * @param bool   $dropPath = false
307
     *
308
     * @return \ArgentCrusade\Selectel\CloudStorage\Contracts\FluentFilesLoaderContract
309
     */
310
    protected function appendPathToParam($key, $dropPath = false)
311
    {
312
        if (empty($this->params['path']) || empty($this->params[$key])) {
313
            return $this;
314
        }
315
316
        $this->params[$key] = $this->params['path'].'/'.ltrim($this->params[$key], '/');
317
318
        if ($dropPath) {
319
            $this->params['path'] = '';
320
        }
321
322
        return $this;
323
    }
324
}
325