UploadManager   A
last analyzed

Complexity

Total Complexity 25

Size/Duplication

Total Lines 322
Duplicated Lines 8.7 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 100%

Importance

Changes 6
Bugs 0 Features 4
Metric Value
wmc 25
c 6
b 0
f 4
lcom 1
cbo 5
dl 28
loc 322
ccs 77
cts 77
cp 1
rs 10

15 Methods

Rating   Name   Duplication   Size   Complexity  
A getPartitionName() 0 4 1
A getPartitionedPath() 0 7 1
A getPrefixedPath() 0 10 2
A addIndexToName() 0 10 2
C createFilePath() 0 36 7
A getUrl() 0 4 1
A getAbsolutePath() 0 4 1
A createPath() 0 12 3
A createPartitionedPath() 0 6 1
A exists() 0 6 1
A saveContent() 10 10 1
A saveUpload() 9 9 1
A saveFileInternal() 9 9 1
A saveFile() 0 4 1
A moveFile() 0 4 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
/**
3
 * Upload Manager.
4
 *
5
 * This file contains upload manager.
6
 *
7
 * @author  Martin Stolz <[email protected]>
8
 */
9
10
namespace herroffizier\yii2um;
11
12
use Yii;
13
use yii\base\Component;
14
use yii\base\InvalidParamException;
15
use yii\web\UploadedFile;
16
use yii\helpers\FileHelper;
17
18
class UploadManager extends Component
19
{
20
    /**
21
     * Throw exception when trying to overwrite existing file.
22
     */
23
    const STRATEGY_KEEP = 0;
24
25
    /**
26
     * Overwrite existing file silently.
27
     */
28
    const STRATEGY_OVERWRITE = 1;
29
30
    /**
31
     * Rename new file if file with same name exists.
32
     */
33
    const STRATEGY_RENAME = 2;
34
35
    /**
36
     * Path to upload folder.
37
     *
38
     * @var string
39
     */
40
    public $uploadDir = '@webroot/upload';
41
42
    /**
43
     * URL to upload folder.
44
     *
45
     * @var string
46
     */
47
    public $uploadUrl = '@web/upload';
48
49
    /**
50
     * Generate partition name based on file name.
51
     *
52
     * @param string $name
53
     *
54
     * @return string
55
     */
56 20
    protected function getPartitionName($name)
57
    {
58 20
        return substr(md5(mb_substr($name, 0, 2)), 0, 2);
59
    }
60
61
    /**
62
     * Add partition folder to given path.
63
     *
64
     * @param string $path
65
     * @param string $name
66
     *
67
     * @return string
68
     */
69 20
    protected function getPartitionedPath($path, $name)
70
    {
71 20
        $subfolder = $this->getPartitionName($name);
72 20
        $path = FileHelper::normalizePath($path).'/'.$subfolder;
73
74 20
        return $path;
75
    }
76
77
    /**
78
     * Get prefixed path.
79
     *
80
     * @param string $path
81
     * @param string $prefix
82
     *
83
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
84
     */
85 36
    protected function getPrefixedPath($path, $prefix)
86
    {
87 36
        if (substr($path, 0, 1) !== '/') {
88 36
            $path = '/'.$path;
89 27
        }
90 36
        $path = FileHelper::normalizePath($path);
91 36
        $prefixedPath = Yii::getAlias($prefix.$path);
92
93 36
        return $prefixedPath;
94
    }
95
96
    /**
97
     * Add index to file name.
98
     *
99
     * @param string $name
100
     * @param int    $index
101
     *
102
     * @return string
103
     */
104 4
    protected function addIndexToName($name, $index)
105
    {
106 4
        $pathinfo = pathinfo($name);
107
108 4
        if (empty($pathinfo['extension'])) {
109 4
            return $pathinfo['basename'].'-'.$index;
110
        } else {
111 4
            return $pathinfo['filename'].'-'.$index.'.'.$pathinfo['extension'];
112
        }
113
    }
114
115
    /**
116
     * Pick up file name according to overwrite strategy and create path.
117
     *
118
     * @throws InvalidParamException when file cannot be created.
119
     *
120
     * @param string $path
121
     * @param string $name
122
     * @param int    $overwriteStrategy
123
     *
124
     * @return string
125
     */
126 16
    protected function createFilePath($path, $name, $overwriteStrategy)
127
    {
128 16
        $partitionedPath = $this->getPartitionedPath($path, $name);
129 16
        $absolutePath = $this->getAbsolutePath($partitionedPath);
130
131 16
        if (file_exists($absolutePath.'/'.$name)) {
132
            switch ($overwriteStrategy) {
133 4
                case self::STRATEGY_KEEP:
134
                    // File overwrtiting is forbidden.
135 4
                    throw new InvalidParamException('File '.$name.' already exists in '.$path.'.');
136
137 4
                case self::STRATEGY_RENAME:
138 4
                    $index = 0;
139
                    do {
140 4
                        ++$index;
141 4
                        $indexedName = $this->addIndexToName($name, $index);
142
143 4
                        $partitionedPath = $this->getPartitionedPath($path, $name);
144 4
                        $absolutePath = $this->getAbsolutePath($partitionedPath);
145 4
                    } while (file_exists($absolutePath.'/'.$indexedName));
146 4
                    $name = $indexedName;
147 4
                    break;
148
149 4
                case self::STRATEGY_OVERWRITE:
150 4
                    if (is_dir($absolutePath.'/'.$name)) {
151
                        // Cannot overwrtite folder.
152 4
                        throw new InvalidParamException($path.'/'.$name.' is a directory and cannot be overwritten.');
153
                    }
154 4
                    break;
155
            }
156 3
        }
157
158 16
        $this->createPath($partitionedPath);
159
160 16
        return $partitionedPath.'/'.$name;
161
    }
162
163
    /**
164
     * Get relative URL for relative path.
165
     *
166
     * @param string $path
167
     *
168
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
169
     */
170 4
    public function getUrl($path)
171
    {
172 4
        return $this->getPrefixedPath($path, $this->uploadUrl);
173
    }
174
175
    /**
176
     * Get absolute path for relative path.
177
     *
178
     * @param string $path
179
     *
180
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
181
     */
182 32
    public function getAbsolutePath($path)
183
    {
184 32
        return $this->getPrefixedPath($path, $this->uploadDir);
185
    }
186
187
    /**
188
     * Create given folder tree in upload folder.
189
     *
190
     * Returns absolute path of given path.
191
     *
192
     * @throws InvalidParamException when file cannot be created.
193
     *
194
     * @param string $path
195
     *
196
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
197
     */
198 24
    public function createPath($path)
199
    {
200 24
        $absolutePath = $this->getAbsolutePath($path);
201 24
        if (!file_exists($absolutePath)) {
202
            // FIXME Check return value.
0 ignored issues
show
Coding Style introduced by
Comment refers to a FIXME task "Check return value"
Loading history...
203 24
            FileHelper::createDirectory($absolutePath);
0 ignored issues
show
Bug introduced by
It seems like $absolutePath defined by $this->getAbsolutePath($path) on line 200 can also be of type boolean; however, yii\helpers\BaseFileHelper::createDirectory() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
204 22
        } elseif (!is_dir($absolutePath)) {
205 4
            throw new InvalidParamException($path.' is a file, cannot create folder with the same name.');
206
        }
207
208 24
        return $absolutePath;
209
    }
210
211
    /**
212
     * Create folder tree appended with partition folder in upload folder.
213
     *
214
     * Partition folder name depends on given file name.
215
     *
216
     * Returns absolute path of given path.
217
     *
218
     * @param string $path
219
     * @param string $name
220
     *
221
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
222
     */
223 4
    public function createPartitionedPath($path, $name)
224
    {
225 4
        $path = $this->getPartitionedPath($path, $name);
226
227 4
        return $this->createPath($path);
228
    }
229
230
    /**
231
     * Whether file with given relative path exists.
232
     *
233
     * @param string $filePath
234
     *
235
     * @return bool
236
     */
237 4
    public function exists($filePath)
238
    {
239 4
        $absoluteFilePath = $this->getAbsolutePath($filePath);
240
241 4
        return file_exists($absoluteFilePath);
242
    }
243
244
    /**
245
     * Save data stored in $content as file to $path/$name in upload folder.
246
     *
247
     * Returns relative path with partition folder.
248
     *
249
     * @param string        $path
250
     * @param string        $name
251
     * @param string        $content
252
     * @param int[optional] $overwriteStrategy
253
     *
254
     * @return string
255
     */
256 4 View Code Duplication
    public function saveContent($path, $name, $content, $overwriteStrategy = self::STRATEGY_KEEP)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
257
    {
258 4
        $filePath = $this->createFilePath($path, $name, $overwriteStrategy);
259 4
        $absoluteFilePath = $this->getAbsolutePath($filePath);
260
261 4
        file_put_contents($absoluteFilePath, $content);
262 4
        unset($content);
263
264 4
        return $filePath;
265
    }
266
267
    /**
268
     * Save $upload file to $path in upload folder.
269
     *
270
     * Returns relative path with partition folder.
271
     *
272
     * @param string        $path
273
     * @param UploadedFile  $upload
274
     * @param int[optional] $overwriteStrategy
275
     *
276
     * @return string
277
     */
278 4 View Code Duplication
    public function saveUpload($path, UploadedFile $upload, $overwriteStrategy = self::STRATEGY_KEEP)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
279
    {
280 4
        $filePath = $this->createFilePath($path, $upload->name, $overwriteStrategy);
281 4
        $absoluteFilePath = $this->getAbsolutePath($filePath);
282
283 4
        $upload->saveAs($absoluteFilePath);
0 ignored issues
show
Bug introduced by
It seems like $absoluteFilePath defined by $this->getAbsolutePath($filePath) on line 281 can also be of type boolean; however, yii\web\UploadedFile::saveAs() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
284
285 4
        return $filePath;
286
    }
287
288
    /**
289
     * Move or copy file.
290
     *
291
     * @param string        $path
292
     * @param string        $absoluteFilePath
293
     * @param int[optional] $overwriteStrategy
294
     * @param string        $function
295
     *
296
     * @return string
297
     */
298 8 View Code Duplication
    protected function saveFileInternal($path, $absoluteFilePath, $overwriteStrategy, $function)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
299
    {
300 8
        $filePath = $this->createFilePath($path, pathinfo($absoluteFilePath, PATHINFO_BASENAME), $overwriteStrategy);
301 8
        $newAbsoluteFilePath = $this->getAbsolutePath($filePath);
302
303 8
        $function($absoluteFilePath, $newAbsoluteFilePath);
304
305 8
        return $filePath;
306
    }
307
308
    /**
309
     * Save $absoluteFilePath file to $path in upload folder.
310
     *
311
     * Returns relative path with partition folder.
312
     *
313
     * @param string        $path
314
     * @param string        $absoluteFilePath
315
     * @param int[optional] $overwriteStrategy
316
     *
317
     * @return string
318
     */
319 8
    public function saveFile($path, $absoluteFilePath, $overwriteStrategy = self::STRATEGY_KEEP)
320
    {
321 8
        return $this->saveFileInternal($path, $absoluteFilePath, $overwriteStrategy, 'copy');
322
    }
323
324
    /**
325
     * Move $absoluteFilePath file to $path in upload folder.
326
     *
327
     * Returns relative path with partition folder.
328
     *
329
     * @param string        $path
330
     * @param string        $absoluteFilePath
331
     * @param int[optional] $overwriteStrategy
332
     *
333
     * @return string
334
     */
335 4
    public function moveFile($path, $absoluteFilePath, $overwriteStrategy = self::STRATEGY_KEEP)
336
    {
337 4
        return $this->saveFileInternal($path, $absoluteFilePath, $overwriteStrategy, 'rename');
338
    }
339
}
340