Completed
Push — master ( e38728...bf5343 )
by
unknown
02:21
created

Validator   C

Complexity

Total Complexity 63

Size/Duplication

Total Lines 432
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 1

Test Coverage

Coverage 78.99%

Importance

Changes 0
Metric Value
wmc 63
lcom 2
cbo 1
dl 0
loc 432
ccs 109
cts 138
cp 0.7899
rs 5.8893
c 0
b 0
f 0

30 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A isSuccess() 0 4 1
A customErrors() 0 12 3
A getDefaultLang() 0 4 1
A getErrorFilePath() 0 4 1
A getDefaultErrorTexts() 0 9 2
A getCustomErrorTexts() 0 9 2
A handleNaming() 0 9 2
A handleParameterNaming() 0 13 4
D getErrors() 0 43 10
A has() 0 6 2
A getResults() 0 4 1
A getParams() 0 13 2
A getParamValues() 0 9 3
C validate() 0 52 10
A required() 0 4 2
A numeric() 0 4 1
A email() 0 4 1
A integer() 0 4 2
A float() 0 4 2
A alpha() 0 4 1
A alpha_numeric() 0 4 1
A ip() 0 4 1
A url() 0 4 1
A max_length() 0 4 1
A min_length() 0 4 1
A exact_length() 0 4 1
A equals() 0 4 1
A is_path() 0 4 1
A is_filename() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Validator 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 Validator, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Validoo;
4
5
/**
6
 * Validator Class
7
 * @author Alessandro Manno <[email protected]>
8
 * @author Chiara Ferrazza <[email protected]>
9
 * @copyright (c) 2016, Facile.it
10
 * @license https://github.com/facile-it/validoo/blob/master/LICENSE MIT Licence
11
 * @link https://github.com/facile-it/validoo
12
 */
13
14
/**
15
 * TODO: Exception handling for rules with parameters
16
 * TODO: unit tests for numeric, float, alpha_numeric, max_length, min_length, exact_length
17
 * TODO: add protection filters for several input vulnerabilities.
18
 */
19
class Validator
20
{
21
22
    /** @var array */
23
    private $errors = [];
24
    /** @var array */
25
    private $namings = [];
26
    /** @var array */
27
    private $customErrorsWithInputName = [];
28
    /** @var array */
29
    private $customErrors = [];
30
31
    /**
32
     * Constructor is not allowed because Validoo uses its own
33
     * static method to instantiate the validation
34
     */
35 46
    private function __construct($errors, $namings)
36
    {
37 46
        $this->errors = $errors;
38 46
        $this->namings = $namings;
39 46
    }
40
41
    /**
42
     * @return bool
43
     */
44 44
    public function isSuccess()
45
    {
46 44
        return empty($this->errors);
47
    }
48
49
    /**
50
     * @param array $errors_array
51
     */
52 1
    public function customErrors(array $errors_array)
53
    {
54 1
        foreach ($errors_array as $key => $value) {
55
            // handle input.rule eg (name.required)
56 1
            if (preg_match("#^(.+?)\.(.+?)$#", $key, $matches)) {
57
                // $this->customErrorsWithInputName[name][required] = error message
58
                $this->customErrorsWithInputName[(string)$matches[1]][(string)$matches[2]] = $value;
59
            } else {
60 1
                $this->customErrors[(string)$key] = $value;
61
            }
62
        }
63 1
    }
64
65
    /**
66
     * @return string
67
     */
68 3
    protected function getDefaultLang(): string
69
    {
70 3
        return "en";
71
    }
72
73
    /**
74
     * @param string $lang
75
     * @return null
76
     */
77 3
    protected function getErrorFilePath(string $lang)
0 ignored issues
show
Unused Code introduced by
The parameter $lang is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
78
    {
79 3
        return null;
80
    }
81
82
    /**
83
     * @param string|null $lang
84
     * @return array|mixed
85
     */
86 3
    protected function getDefaultErrorTexts(string $lang = null)
87
    {
88
        /* handle default error text file */
89 3
        $default_error_texts = [];
90 3
        if (file_exists(__DIR__ . "/errors/" . $lang . ".php")) {
91 3
            $default_error_texts = include(__DIR__ . "/errors/" . $lang . ".php");
92
        }
93 3
        return $default_error_texts;
94
    }
95
96
    /**
97
     * @param string|null $lang
98
     * @return array|mixed
99
     */
100 3
    protected function getCustomErrorTexts(string $lang = null)
101
    {
102
        /* handle error text file for custom validators */
103 3
        $custom_error_texts = [];
104 3
        if (file_exists($this->getErrorFilePath($lang))) {
105
            $custom_error_texts = include($this->getErrorFilePath($lang));
106
        }
107 3
        return $custom_error_texts;
108
    }
109
110
    /**
111
     * @param string $input_name
112
     * @return mixed|string
113
     */
114 3
    protected function handleNaming(string $input_name)
115
    {
116 3
        if (isset($this->namings[$input_name])) {
117
            $named_input = $this->namings[$input_name];
118
        } else {
119 3
            $named_input = $input_name;
120
        }
121 3
        return $named_input;
122
    }
123
124
    /**
125
     * @param array $params
126
     * @return array
127
     */
128 3
    protected function handleParameterNaming(array $params)
129
    {
130 3
        foreach ($params as $key => $param) {
131 1
            if (preg_match("#^:([a-zA-Z0-9_]+)$#", $param, $param_type)) {
132 1
                if (isset($this->namings[(string)$param_type[1]])) {
133 1
                    $params[$key] = $this->namings[(string)$param_type[1]];
134
                } else {
135 1
                    $params[$key] = $param_type[1];
136
                }
137
            }
138
        }
139 3
        return $params;
140
    }
141
142
    /**
143
     * @param string|null $lang
144
     * @return array
145
     * @throws ValidooException
146
     */
147 3
    public function getErrors(string $lang = null): array
148
    {
149 3
        if (null === $lang)
0 ignored issues
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
150 3
            $lang = $this->getDefaultLang();
151
152 3
        $error_results = [];
153 3
        $default_error_texts = $this->getDefaultErrorTexts($lang);
154 3
        $custom_error_texts = $this->getCustomErrorTexts($lang);
155
156 3
        foreach ($this->errors as $input_name => $results) {
157 3
            foreach ($results as $rule => $result) {
158 3
                $named_input = $this->handleNaming($input_name);
159
                /**
160
                 * if parameters are input name they should be named as well
161
                 */
162 3
                $result['params'] = $this->handleParameterNaming($result['params']);
163
                // if there is a custom message with input name, apply it
164 3
                if (isset($this->customErrorsWithInputName[(string)$input_name][(string)$rule])) {
165
                    $error_message = $this->customErrorsWithInputName[(string)$input_name][(string)$rule];
166
                } // if there is a custom message for the rule, apply it
167 3
                else if (isset($this->customErrors[(string)$rule])) {
168 1
                    $error_message = $this->customErrors[(string)$rule];
169
                } // if there is a custom validator try to fetch from its error file
170 2
                else if (isset($custom_error_texts[(string)$rule])) {
171
                    $error_message = $custom_error_texts[(string)$rule];
172
                } // if none try to fetch from default error file
173 2
                else if (isset($default_error_texts[(string)$rule])) {
174 2
                    $error_message = $default_error_texts[(string)$rule];
175
                } else {
176
                    throw new ValidooException(ValidooException::NO_ERROR_TEXT, $rule);
177
                }
178
                /**
179
                 * handle :params(..)
180
                 */
181 3
                if (preg_match_all("#:params\((.+?)\)#", $error_message, $param_indexes))
0 ignored issues
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
182 1
                    foreach ($param_indexes[1] as $param_index) {
183 1
                        $error_message = str_replace(":params(" . $param_index . ")", $result['params'][$param_index], $error_message);
184
                    }
185 3
                $error_results[] = str_replace(":attribute", $named_input, $error_message);
186
            }
187
        }
188 3
        return $error_results;
189
    }
190
191
    /**
192
     * @param string $input_name
193
     * @param string|null $rule_name
194
     * @return bool
195
     */
196
    public function has(string $input_name, string $rule_name = null): bool
197
    {
198
        if (null !== $rule_name)
0 ignored issues
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
199
            return isset($this->errors[$input_name][$rule_name]);
200
        return isset($this->errors[$input_name]);
201
    }
202
203
    /**
204
     * @return array
205
     */
206
    final public function getResults(): array
207
    {
208
        return $this->errors;
209
    }
210
211
    /**
212
     * Gets the parameter names of a rule
213
     * @param $rule
214
     * @return mixed
215
     */
216 46
    private static function getParams($rule)
217
    {
218 46
        if (preg_match("#^([a-zA-Z0-9_]+)\((.+?)\)$#", $rule, $matches)) {
219
            return [
220 6
                'rule' => $matches[1],
221 6
                'params' => explode(",", $matches[2])
222
            ];
223
        }
224
        return [
225 40
            'rule' => $rule,
226
            'params' => []
227
        ];
228
    }
229
230
    /**
231
     * Handle parameter with input name
232
     * eg: equals(:name)
233
     * @param mixed $params
234
     * @return mixed
235
     */
236 46
    private static function getParamValues($params, $inputs)
237
    {
238 46
        foreach ($params as $key => $param) {
239 6
            if (preg_match("#^:([a-zA-Z0-9_]+)$#", $param, $param_type)) {
240 6
                $params[$key] = @$inputs[(string)$param_type[1]];
241
            }
242
        }
243 46
        return $params;
244
    }
245
246
    /**
247
     * @param mixed $inputs
248
     * @param array $rules
249
     * @param array|null $naming
250
     * @return Validator
251
     * @throws ValidooException
252
     */
253 46
    public static function validate($inputs, array $rules, array $naming = null): self
254
    {
255 46
        $errors = null;
256 46
        foreach ($rules as $input => $input_rules) {
257 46
            if (is_array($input_rules)) {
258 46
                foreach ($input_rules as $rule => $closure) {
259 46
                    if (!isset($inputs[(string)$input]))
0 ignored issues
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
260 7
                        $input_value = null;
261
                    else
0 ignored issues
show
Coding Style introduced by
Expected 1 space after ELSE keyword; newline found
Loading history...
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
262 39
                        $input_value = $inputs[(string)$input];
263
                    /**
264
                     * if the key of the $input_rules is numeric that means
265
                     * it's neither an anonymous nor an user function.
266
                     */
267 46
                    if (is_numeric($rule)) {
268 44
                        $rule = $closure;
269
                    }
270 46
                    $rule_and_params = static::getParams($rule);
0 ignored issues
show
Bug introduced by
Since getParams() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of getParams() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
271 46
                    $params = $real_params = $rule_and_params['params'];
272 46
                    $rule = $rule_and_params['rule'];
273 46
                    $params = static::getParamValues($params, $inputs);
0 ignored issues
show
Bug introduced by
Since getParamValues() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of getParamValues() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
274 46
                    array_unshift($params, $input_value);
275
                    /**
276
                     * Handle anonymous functions
277
                     */
278 46
                    if (@get_class($closure) == 'Closure') {
279 2
                        $refl_func = new \ReflectionFunction($closure);
280 2
                        $validation = $refl_func->invokeArgs($params);
281
                    } /**
282
                     * handle class methods
283 44
                     */ else if (@method_exists(get_called_class(), $rule)) {
284 44
                        $refl = new \ReflectionMethod(get_called_class(), $rule);
285 44
                        if ($refl->isStatic()) {
286 44
                            $refl->setAccessible(true);
287 44
                            $validation = $refl->invokeArgs(null, $params);
288
                        } else {
289 44
                            throw new ValidooException(ValidooException::STATIC_METHOD, $rule);
290
                        }
291
                    } else {
0 ignored issues
show
Coding Style introduced by
Closing brace indented incorrectly; expected 0 spaces, found 20
Loading history...
292
                        throw new ValidooException(ValidooException::UNKNOWN_RULE, $rule);
293
                    }
294 46
                    if ($validation == false) {
295 27
                        $errors[(string)$input][(string)$rule]['result'] = false;
296 46
                        $errors[(string)$input][(string)$rule]['params'] = $real_params;
297
                    }
298
                }
299
            } else {
300 46
                throw new ValidooException(ValidooException::ARRAY_EXPECTED, $input);
301
            }
302
        }
303 46
        return new self($errors, $naming);
304
    }
305
306
    /**
307
     * @param null $input
308
     * @return bool
309
     */
310 9
    protected static function required($input = null): bool
311
    {
312 9
        return (!is_null($input) && (trim($input) != ''));
313
    }
314
315
    /**
316
     * @param $input
317
     * @return bool
318
     */
319
    protected static function numeric($input): bool
320
    {
321
        return is_numeric($input);
322
    }
323
324
    /**
325
     * @param $input
326
     * @return bool
327
     */
328 4
    protected static function email($input): bool
329
    {
330 4
        return filter_var($input, FILTER_VALIDATE_EMAIL);
331
    }
332
333
    /**
334
     * @param $input
335
     * @return bool
336
     */
337 11
    protected static function integer($input): bool
338
    {
339 11
        return is_int($input) || ($input == (string)(int)$input);
340
    }
341
342
    /**
343
     * @param $input
344
     * @return bool
345
     */
346
    protected static function float($input): bool
347
    {
348
        return is_float($input) || ($input == (string)(float)$input);
349
    }
350
351
    /**
352
     * @param $input
353
     * @return bool
354
     */
355 3
    protected static function alpha($input): bool
356
    {
357 3
        return (preg_match("#^[a-zA-ZÀ-ÿ]+$#", $input) == 1);
358
    }
359
360
    /**
361
     * @param $input
362
     * @return bool
363
     */
364
    protected static function alpha_numeric($input): bool
0 ignored issues
show
Coding Style introduced by
Method name "Validator::alpha_numeric" is not in camel caps format
Loading history...
365
    {
366
        return (preg_match("#^[a-zA-ZÀ-ÿ0-9]+$#", $input) == 1);
367
    }
368
369
    /**
370
     * @param $input
371
     * @return bool
372
     */
373 4
    protected static function ip($input): bool
374
    {
375 4
        return filter_var($input, FILTER_VALIDATE_IP);
376
    }
377
378
    /*
379
     * TODO: need improvements for tel and urn urls.
380
     * check out url.test.php for the test result
381
     * urn syntax: http://www.faqs.org/rfcs/rfc2141.html
382
     *
383
     */
384
    /**
385
     * @param $input
386
     * @return bool
387
     */
388 9
    protected static function url($input): bool
389
    {
390 9
        return filter_var($input, FILTER_VALIDATE_URL);
391
    }
392
393
    /**
394
     * @param $input
395
     * @param $length
396
     * @return bool
397
     */
398
    protected static function max_length($input, $length): bool
0 ignored issues
show
Coding Style introduced by
Method name "Validator::max_length" is not in camel caps format
Loading history...
399
    {
400
        return (strlen($input) <= $length);
401
    }
402
403
    /**
404
     * @param $input
405
     * @param $length
406
     * @return bool
407
     */
408
    protected static function min_length($input, $length): bool
0 ignored issues
show
Coding Style introduced by
Method name "Validator::min_length" is not in camel caps format
Loading history...
409
    {
410
        return (strlen($input) >= $length);
411
    }
412
413
    /**
414
     * @param $input
415
     * @param $length
416
     * @return bool
417
     */
418
    protected static function exact_length($input, $length): bool
0 ignored issues
show
Coding Style introduced by
Method name "Validator::exact_length" is not in camel caps format
Loading history...
419
    {
420
        return (strlen($input) == $length);
421
    }
422
423
    /**
424
     * @param $input
425
     * @param $param
426
     * @return bool
427
     */
428 4
    protected static function equals($input, $param): bool
429
    {
430 4
        return ($input == $param);
431
    }
432
433
    /**
434
     * @param $input
435
     * @return bool
436
     */
437
    protected static function is_path($input): bool
0 ignored issues
show
Coding Style introduced by
Method name "Validator::is_path" is not in camel caps format
Loading history...
438
    {
439
        return is_dir($input);
440
    }
441
442
    /**
443
     * @param $input
444
     * @return bool
445
     */
446
    protected static function is_filename($input): bool
0 ignored issues
show
Coding Style introduced by
Method name "Validator::is_filename" is not in camel caps format
Loading history...
447
    {
448
        return preg_match('/^[A-Za-z0-9-_]+[.]{1}[A-Za-z]+$/', $input);
449
    }
450
}
451