FileUploadValidation   B
last analyzed

Complexity

Total Complexity 41

Size/Duplication

Total Lines 235
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 41
lcom 1
cbo 3
dl 0
loc 235
rs 8.2769
c 3
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A isUploaded() 0 4 1
A getMaxServerFileSize() 0 14 2
C isBetween() 0 29 7
A checkIfMaximumUploadFileSizeHasBeenExceeded() 0 6 2
A getMimeType() 0 15 2
B isMimeType() 0 19 6
B hasFileNameFormat() 0 13 5
A hasValidUploadDirectory() 0 10 4
B notOverwritingExistingFile() 0 17 6
B hasLength() 0 10 5
A isImage() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like FileUploadValidation often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FileUploadValidation, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Author: Nil Portugués Calderó <[email protected]>
4
 * Date: 9/24/14
5
 * Time: 1:12 PM
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace NilPortugues\Validator\Validation\FileUpload;
12
13
use NilPortugues\Validator\AbstractValidator;
14
use NilPortugues\Validator\Validation\Integer\IntegerValidation;
15
16
/**
17
 * Class FileUploadValidation
18
 * @package NilPortugues\Validator\Validation\FileUploadAttribute
19
 */
20
class FileUploadValidation
21
{
22
    /**
23
     * @var array
24
     */
25
    private static $byte = [
26
        'K'  => 1000,
27
        'KB' => 1000,
28
        'M'  => 1000000,
29
        'MB' => 1000000,
30
        'G'  => 1000000000,
31
        'GB' => 1000000000,
32
        'T'  => 1000000000000,
33
        'TB' => 1000000000000,
34
    ];
35
36
    /**
37
     * Validates if the given data is a file that was uploaded
38
     *
39
     * @param string $uploadName
40
     *
41
     * @return bool
42
     */
43
    public static function isUploaded($uploadName)
0 ignored issues
show
Coding Style introduced by
isUploaded uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
44
    {
45
        return \array_key_exists($uploadName, $_FILES);
46
    }
47
48
    /**
49
     * @return int
50
     */
51
    private static function getMaxServerFileSize()
52
    {
53
        $maxFileSize     = \min(\ini_get('post_max_size'), \ini_get('upload_max_filesize'));
54
        $maxFileSizeUnit = \preg_replace('/\d/', '', $maxFileSize);
55
56
        $finalMaxFileSize = 0;
57
        if (\array_key_exists(\strtoupper($maxFileSizeUnit), self::$byte)) {
58
            $multiplier       = self::$byte[$maxFileSizeUnit];
59
            $finalMaxFileSize = \preg_replace("/[^0-9,.]/", "", $maxFileSize);
60
            $finalMaxFileSize = $finalMaxFileSize * $multiplier;
61
        }
62
63
        return (int) $finalMaxFileSize;
64
    }
65
66
    /**
67
     * @param string  $uploadName
68
     * @param integer $minSize
69
     * @param integer $maxSize
70
     * @param string  $format
71
     * @param bool    $inclusive
72
     *
73
     * @return bool
74
     * @throws FileUploadException
75
     */
76
    public static function isBetween($uploadName, $minSize, $maxSize, $format = 'B', $inclusive = false)
0 ignored issues
show
Coding Style introduced by
isBetween uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
77
    {
78
        $multiplier = 1;
79
        if (\array_key_exists(\strtoupper($format), self::$byte)) {
80
            $multiplier = self::$byte[$format];
81
        }
82
83
        $minSize = $minSize * $multiplier;
84
        $maxSize = $maxSize * $multiplier;
85
        $maxSize = \min(self::getMaxServerFileSize(), $maxSize);
86
87
        if (isset($_FILES[$uploadName]['size']) && \is_array($_FILES[$uploadName]['size'])) {
88
            $isValid = true;
89
            foreach ($_FILES[$uploadName]['size'] as $size) {
90
                self::checkIfMaximumUploadFileSizeHasBeenExceeded($uploadName, $maxSize, $size);
91
                $isValid = $isValid && IntegerValidation::isBetween($size, $minSize, $maxSize, $inclusive);
92
            }
93
94
            return $isValid;
95
        }
96
97
        if (!isset($_FILES[$uploadName]['size'])) {
98
            return false;
99
        }
100
101
        self::checkIfMaximumUploadFileSizeHasBeenExceeded($uploadName, $maxSize, $_FILES[$uploadName]['size']);
102
103
        return IntegerValidation::isBetween($_FILES[$uploadName]['size'], $minSize, $maxSize, $inclusive);
104
    }
105
106
    /**
107
     * @param string $uploadName
108
     * @param $size
109
     * @param $maxSize
110
     *
111
     * @throws FileUploadException
112
     */
113
    private static function checkIfMaximumUploadFileSizeHasBeenExceeded($uploadName, $size, $maxSize)
114
    {
115
        if ($size < $maxSize) {
116
            throw new FileUploadException($uploadName);
117
        }
118
    }
119
120
    /**
121
     * @param $filePath
122
     *
123
     * @return string
124
     */
125
    private static function getMimeType($filePath)
126
    {
127
        $currentErrorReporting = \error_reporting();
128
        \error_reporting(0);
129
130
        $mimeType = '';
131
        $fileInfo = \finfo_open(FILEINFO_MIME_TYPE);
132
        if (false !== $fileInfo) {
133
            $mimeType = (string) \finfo_file($fileInfo, $filePath);
134
            \finfo_close($fileInfo);
135
        }
136
        \error_reporting($currentErrorReporting);
137
138
        return $mimeType;
139
    }
140
141
    /**
142
     * @param string   $uploadName
143
     * @param string[] $allowedTypes
144
     *
145
     * @return bool
146
     */
147
    public static function isMimeType($uploadName, array $allowedTypes)
0 ignored issues
show
Coding Style introduced by
isMimeType uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
148
    {
149
        if (isset($_FILES[$uploadName]['tmp_name']) && \is_array($_FILES[$uploadName]['tmp_name'])) {
150
            $isValid = true;
151
152
            \array_filter($_FILES[$uploadName]['tmp_name']);
153
            foreach ($_FILES[$uploadName]['tmp_name'] as $name) {
154
                $isValid = $isValid && \in_array(self::getMimeType($name), $allowedTypes, true);
155
            }
156
157
            return $isValid;
158
        }
159
160
        if (!isset($_FILES[$uploadName]['tmp_name'])) {
161
            return false;
162
        }
163
164
        return \in_array(self::getMimeType($_FILES[$uploadName]['tmp_name']), $allowedTypes, true);
165
    }
166
167
    /**
168
     * @param string            $uploadName
169
     * @param AbstractValidator $validator
170
     *
171
     * @return bool
172
     */
173
    public static function hasFileNameFormat($uploadName, AbstractValidator $validator)
0 ignored issues
show
Coding Style introduced by
hasFileNameFormat uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
174
    {
175
        if (isset($_FILES[$uploadName]['name']) && \is_array($_FILES[$uploadName]['name'])) {
176
            $isValid = true;
177
            foreach ($_FILES[$uploadName]['name'] as $name) {
178
                $isValid = $isValid && $validator->validate($name);
179
            }
180
181
            return $isValid;
182
        }
183
184
        return $validator->validate($_FILES[$uploadName]['name']);
185
    }
186
187
    /**
188
     * @param string $uploadName
189
     * @param string $uploadDir
190
     *
191
     * @return bool
192
     */
193
    public static function hasValidUploadDirectory($uploadName, $uploadDir)
0 ignored issues
show
Coding Style introduced by
hasValidUploadDirectory uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
194
    {
195
        if (!isset($_FILES[$uploadName]['name'])) {
196
            return false;
197
        }
198
199
        return \file_exists($uploadDir)
200
        && \is_dir($uploadDir)
201
        && \is_writable($uploadDir);
202
    }
203
204
    /**
205
     * @param string $uploadName
206
     * @param string $uploadDir
207
     *
208
     * @return bool
209
     */
210
    public static function notOverwritingExistingFile($uploadName, $uploadDir)
0 ignored issues
show
Coding Style introduced by
notOverwritingExistingFile uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
211
    {
212
        if (isset($_FILES[$uploadName]['name']) && \is_array($_FILES[$uploadName]['name'])) {
213
            $isValid = true;
214
            foreach ($_FILES[$uploadName]['name'] as $name) {
215
                $isValid = $isValid && !file_exists($uploadDir.DIRECTORY_SEPARATOR.$name);
216
            }
217
218
            return $isValid;
219
        }
220
221
        if (!isset($_FILES[$uploadName]['name'])) {
222
            return false;
223
        }
224
225
        return !file_exists($uploadDir.DIRECTORY_SEPARATOR.$_FILES[$uploadName]['name']);
226
    }
227
228
    /**
229
     * @param string  $uploadName
230
     * @param integer $size
231
     *
232
     * @return bool
233
     */
234
    public static function hasLength($uploadName, $size)
0 ignored issues
show
Coding Style introduced by
hasLength uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
235
    {
236
        \settype($size, 'int');
237
238
        if (isset($_FILES[$uploadName]['name']) && \is_array($_FILES[$uploadName]['name']) && $size >= 0) {
239
            return $size == \count($_FILES[$uploadName]['name']);
240
        }
241
242
        return 1 == $size && isset($_FILES[$uploadName]['name']);
243
    }
244
245
    /**
246
     * @param string $uploadName
247
     *
248
     * @return bool
249
     */
250
    public static function isImage($uploadName)
251
    {
252
        return self::isMimeType($uploadName, ['image/gif', 'image/jpeg', 'image/png']);
253
    }
254
}
255