Completed
Push — master ( 0f12bf...94f146 )
by Oscar
02:51
created

Uploader::save()   C

Complexity

Conditions 11
Paths 17

Size

Total Lines 35
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 7
Bugs 1 Features 1
Metric Value
c 7
b 1
f 1
dl 0
loc 35
rs 5.2653
cc 11
eloc 18
nc 17
nop 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Uploader;
4
5
use Closure;
6
7
class Uploader
8
{
9
    protected $cwd;
10
    protected $adapter;
11
    protected $original;
12
13
    protected $adapters = [
14
        'Uploader\\Adapters\\Base64',
15
        'Uploader\\Adapters\\Psr7',
16
        'Uploader\\Adapters\\Upload',
17
        'Uploader\\Adapters\\Url',
18
    ];
19
20
    protected $callbacks = [
21
        'destination' => null,
22
        'prefix' => null,
23
        'overwrite' => null,
24
        'directory' => null,
25
        'filename' => null,
26
        'extension' => null,
27
    ];
28
29
    protected $options = [
30
        'prefix' => null,
31
        'overwrite' => false,
32
        'create_dir' => false,
33
        'directory' => null,
34
        'filename' => null,
35
        'extension' => null,
36
    ];
37
38
    /**
39
     * @param string $cwd The current working directory used to save the file
40
     */
41
    public function __construct($cwd)
42
    {
43
        $this->setCwd($cwd);
44
    }
45
46
    /**
47
     * Execute a upload.
48
     *
49
     * @param mixed       $original
50
     * @param null|string $adapter
51
     *
52
     * @throws \InvalidArgumentException On error
53
     *
54
     * @return string The file destination
55
     */
56
    public function __invoke($original, $adapter = null)
57
    {
58
        return $this
59
            ->with($original, $adapter)
60
            ->save()
61
            ->getDestination();
62
    }
63
64
    /**
65
     * Set the current working directory.
66
     *
67
     * @param string $cwd
68
     *
69
     * @return $this
70
     */
71
    public function setCwd($cwd)
72
    {
73
        $this->cwd = $cwd;
74
75
        return $this;
76
    }
77
78
    /**
79
     * Set a prefix for the filenames.
80
     *
81
     * @return string|null
82
     */
83
    public function getCwd()
84
    {
85
        return $this->cwd;
86
    }
87
88
    /**
89
     * Set a prefix for the filenames.
90
     *
91
     * @param string|Closure $prefix
92
     *
93
     * @return $this
94
     */
95
    public function setPrefix($prefix)
96
    {
97
        return $this->setOption('prefix', $prefix);
98
    }
99
100
    /**
101
     * Set a prefix for the filenames.
102
     *
103
     * @return string|null
104
     */
105
    public function getPrefix()
106
    {
107
        return empty($this->options['prefix']) ? null : $this->options['prefix'];
108
    }
109
110
    /**
111
     * Set the overwrite configuration.
112
     *
113
     * @param bool|Closure $overwrite
114
     *
115
     * @return $this
116
     */
117
    public function setOverwrite($overwrite)
118
    {
119
        return $this->setOption('overwrite', $overwrite);
120
    }
121
122
    /**
123
     * Get the overwrite configuration.
124
     *
125
     * @return bool
126
     */
127
    public function getOverwrite()
128
    {
129
        return (boolean) $this->options['overwrite'];
130
    }
131
132
    /**
133
     * Set the create_dir configuration.
134
     *
135
     * @param bool|Closure $create_dir
136
     *
137
     * @return $this
138
     */
139
    public function setCreateDir($create_dir)
140
    {
141
        return $this->setOption('create_dir', $create_dir);
142
    }
143
144
    /**
145
     * Get the create_dir configuration.
146
     *
147
     * @return bool
148
     */
149
    public function getCreateDir()
150
    {
151
        return (boolean) $this->options['create_dir'];
152
    }
153
154
    /**
155
     * Set the destination of the file. It includes the directory, filename and extension.
156
     *
157
     * @param string|Closure $destination
158
     *
159
     * @return $this
160
     */
161
    public function setDestination($destination)
162
    {
163
        if ($destination instanceof Closure) {
164
            $this->callbacks['destination'] = $destination;
165
        } else {
166
            $this->options = self::parsePath($destination) + $this->options;
167
        }
168
169
        return $this;
170
    }
171
172
    /**
173
     * Returns the file destination.
174
     *
175
     * @param bool $absolute Whether or not returns the cwd
176
     *
177
     * @return string
178
     */
179
    public function getDestination($absolute = false)
180
    {
181
        return self::fixPath(($absolute ? '/'.$this->cwd : ''), $this->getDirectory(), $this->getPrefix().$this->getFilename().'.'.$this->getExtension());
182
    }
183
184
    /**
185
     * Set only the directory of the destination.
186
     *
187
     * @param string|Closure $directory
188
     *
189
     * @return $this
190
     */
191
    public function setDirectory($directory)
192
    {
193
        return $this->setOption('directory', $directory);
194
    }
195
196
    /**
197
     * Get the directory of the destination.
198
     *
199
     * @return null|string
200
     */
201
    public function getDirectory()
202
    {
203
        return empty($this->options['directory']) ? null : $this->options['directory'];
204
    }
205
206
    /**
207
     * Set only the filename of the destination.
208
     *
209
     * @param string|Closure $filename
210
     *
211
     * @return $this
212
     */
213
    public function setFilename($filename)
214
    {
215
        return $this->setOption('filename', $filename);
216
    }
217
218
    /**
219
     * Get the filename of the destination.
220
     *
221
     * @return null|string
222
     */
223
    public function getFilename()
224
    {
225
        return empty($this->options['filename']) ? null : $this->options['filename'];
226
    }
227
228
    /**
229
     * Set only the file extension of the destination.
230
     *
231
     * @param string|Closure $extension
232
     *
233
     * @return $this
234
     */
235
    public function setExtension($extension)
236
    {
237
        return $this->setOption('extension', $extension);
238
    }
239
240
    /**
241
     * Get the extension of the destination.
242
     *
243
     * @return null|string
244
     */
245
    public function getExtension()
246
    {
247
        return empty($this->options['extension']) ? null : strtolower($this->options['extension']);
248
    }
249
250
    /**
251
     * Set the original source.
252
     *
253
     * @param mixed       $original
254
     * @param null|string $adapter
255
     *
256
     * @throws \InvalidArgumentException On error
257
     *
258
     * @return Uploader A new cloned copy with the source and adapter configured
259
     */
260
    public function with($original, $adapter = null)
261
    {
262
        $new = clone $this;
263
        $new->original = $original;
264
        $new->adapter = $adapter;
265
266
        if ($new->adapter === null) {
267
            foreach ($new->adapters as $each) {
268
                if ($each::check($original)) {
269
                    $new->adapter = $each;
270
                    break;
271
                }
272
            }
273
        }
274
275
        if ($new->adapter === null || !class_exists($new->adapter)) {
276
            throw new \InvalidArgumentException('No valid adapter found');
277
        }
278
279
        return $new;
280
    }
281
282
    /**
283
     * Save the file.
284
     *
285
     * @throws \Exception On error
286
     *
287
     * @return $this
288
     */
289
    public function save()
290
    {
291
        if (!$this->original || empty($this->adapter)) {
292
            throw new \Exception('Original source is not defined');
293
        }
294
295
        call_user_func("{$this->adapter}::fixDestination", $this, $this->original);
296
297
        //Execute callbacks
298
        foreach ($this->callbacks as $name => $callback) {
299
            if ($callback) {
300
                if ($name === 'destination') {
301
                    $this->setDestination($callback($this));
302
                } else {
303
                    $this->options[$name] = $callback($this);
304
                }
305
            }
306
        }
307
308
        $destination = $this->getDestination(true);
309
310
        if (!$this->getOverwrite() && is_file($destination)) {
311
            throw new \RuntimeException(sprintf('Cannot override the file "%s"', $destination));
312
        }
313
314
        if ($this->getCreateDir() && !is_dir(dirname($destination))) {
315
            if (mkdir(dirname($destination), 0777, true) === false) {
316
                throw new \RuntimeException(sprintf('Unable to create the directory "%s"', dirname($destination)));
317
            }
318
        }
319
320
        call_user_func("{$this->adapter}::save", $this->original, $destination);
321
322
        return $this;
323
    }
324
325
    /**
326
     * Saves an option.
327
     *
328
     * @param string $name
329
     * @param mixed  $value
330
     *
331
     * @return $this
332
     */
333
    protected function setOption($name, $value)
334
    {
335
        if ($value instanceof Closure) {
336
            $this->callbacks[$name] = $value;
337
        } else {
338
            $this->options[$name] = $value;
339
        }
340
341
        return $this;
342
    }
343
344
    /**
345
     * Helper function used to parse a path.
346
     *
347
     * @param string $path
348
     *
349
     * @return array With 3 keys: directory, filename and extension
350
     */
351
    public static function parsePath($path)
352
    {
353
        $components = pathinfo($path);
354
355
        return [
356
            'directory' => isset($components['dirname']) ? self::fixPath($components['dirname']) : null,
357
            'filename' => isset($components['filename']) ? $components['filename'] : null,
358
            'extension' => isset($components['extension']) ? $components['extension'] : null,
359
        ];
360
    }
361
362
    /**
363
     * Resolve paths with ../, //, etc...
364
     *
365
     * @param string $path
366
     *
367
     * @return string
368
     */
369
    private static function fixPath($path)
370
    {
371
        if (func_num_args() > 1) {
372
            return self::fixPath(implode('/', func_get_args()));
373
        }
374
375
        $replace = ['#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#'];
376
377
        do {
378
            $path = preg_replace($replace, '/', $path, -1, $n);
379
        } while ($n > 0);
380
381
        return $path;
382
    }
383
}
384