Completed
Push — master ( 8ae150...af2d08 )
by Timur
02:28
created

FluentFilesLoader::asFileObjects()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
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
     * 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']) {
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...
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']) {
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...
293
            $this->params['prefix'] = $this->params['path'].'/'.ltrim($this->params['prefix']);
294
            $this->params['path'] = '';
295
        }
296
297
        return $this->params;
298
    }
299
}
300