Completed
Push — master ( 00fa31...f65284 )
by Mihail
02:46
created

ModelValidator   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 269
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 46
c 2
b 0
f 0
lcom 1
cbo 9
dl 0
loc 269
rs 8.4

11 Methods

Rating   Name   Duplication   Size   Complexity  
D runValidate() 0 37 9
A getBadAttribute() 0 4 1
A setSubmitMethod() 0 4 1
A getSubmitMethod() 0 4 1
A send() 0 8 2
A getFormName() 0 9 2
F validateRecursive() 0 96 21
A getInput() 0 4 1
A getFile() 0 4 1
B getRequest() 0 26 6
A inputTypes() 0 4 1

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
namespace Ffcms\Core\Traits;
4
5
6
use Ffcms\Core\App;
7
use Ffcms\Core\Exception\SyntaxException;
8
use Ffcms\Core\Filter\Native;
9
use Ffcms\Core\Helper\Type\Obj;
10
use Ffcms\Core\Helper\Type\Str;
11
use Dflydev\DotAccessData\Data as DotData;
12
13
trait ModelValidator
14
{
15
    protected $_badAttr;
16
    protected $_sendMethod = 'POST';
17
18
    protected $_formName;
19
20
    public function runValidate(array $rules = null)
21
    {
22
        // skip validation on empty rules
23
        if ($rules === null) {
24
            return true;
25
        }
26
27
        $success = true;
28
        // each
29
        foreach ($rules as $rule) {
30
            // 0 = name, 1 = filter name, 2 = filter value
31
            if ($rule[0] === null || $rule[1] === null) {
32
                continue;
33
            }
34
35
            if (Obj::isArray($rule[0])) {
36
                $validate_foreach = true;
37
                foreach ($rule[0] as $field_name) {
38
                    // end false condition
39
                    if (!$this->validateRecursive($field_name, $rule[1], $rule[2], $rule[3], $rule[4])) {
40
                        $validate_foreach = false;
41
                    }
42
                }
43
                // assign total
44
                $validate = $validate_foreach;
45
            } else {
46
                $validate = $this->validateRecursive($rule[0], $rule[1], $rule[2], $rule[3], $rule[4]);
47
            }
48
49
            // do not change condition on "true" check's (end-false-condition)
50
            if ($validate === false) {
51
                $success = false;
52
            }
53
        }
54
55
        return $success;
56
    }
57
58
    /**
59
     * @param $field_name
60
     * @param $filter_name
61
     * @param $filter_argv
62
     * @param bool $html
63
     * @param bool $secure
64
     * @return bool
65
     * @throws SyntaxException
66
     */
67
    public function validateRecursive($field_name, $filter_name, $filter_argv, $html = false, $secure = false)
68
    {
69
        // check if we got it from form defined request method
70
        if (App::$Request->getMethod() !== $this->_sendMethod) {
71
            return false;
72
        }
73
74
        $inputTypes = [];
75
        // check input data type. Maybe file or input (text)
76
        if (method_exists($this, 'inputTypes')) {
77
            $inputTypes = $this->inputTypes();
78
        }
79
80
        // sounds like file
81
        if ($inputTypes[$field_name] === 'file') {
82
            $field_value = $this->getRequest($field_name, 'file');
83
        } else { // sounds like plain post data
84
            $field_value = $this->getRequest($field_name, $this->_sendMethod);
85
            // remove or safe use html
86
            if ($html === false) {
87
                $field_value = App::$Security->strip_tags($field_value);
0 ignored issues
show
Bug introduced by
It seems like $field_value can also be of type null; however, Ffcms\Core\Helper\Security::strip_tags() does only seem to accept string|array, 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...
88
            } else {
89
                if ($secure !== true) {
90
                    $field_value = App::$Security->secureHtml($field_value);
0 ignored issues
show
Bug introduced by
It seems like $field_value defined by \Ffcms\Core\App::$Securi...ecureHtml($field_value) on line 90 can also be of type null; however, Ffcms\Core\Helper\Security::secureHtml() does only seem to accept string|array, 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...
91
                }
92
            }
93
        }
94
95
        $check = false;
0 ignored issues
show
Unused Code introduced by
$check is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
96
        // maybe no filter required?
97
        if ($filter_name === 'used') {
98
            $check = true;
99
        } elseif (Str::contains('::', $filter_name)) { // sounds like a callback class::method::method
100
            // string to array via delimiter ::
101
            $callbackArray = explode('::', $filter_name);
102
            // first item is a class name
103
            $class = array_shift($callbackArray);
104
            // last item its a function
105
            $method = array_pop($callbackArray);
106
            // left any items? maybe post-static callbacks?
107
            if (count($callbackArray) > 0) {
108
                foreach ($callbackArray as $obj) {
109
                    if (Str::startsWith('$', $obj) && property_exists($class, ltrim($obj, '$'))) { // sounds like a variable
110
                        $obj = ltrim($obj, '$'); // trim variable symbol '$'
111
                        $class = $class::$$obj; // make magic :)
112
                    } elseif (method_exists($class, $obj)) { // maybe its a function?
113
                        $class = $class::$obj; // call function
114
                    } else {
115
                        throw new SyntaxException('Filter callback execution failed: ' . $filter_name);
116
                    }
117
118
                }
119
            }
120
121
            // check is endpoint method exist
122
            if (method_exists($class, $method)) {
123
                $check = @$class::$method($field_value, $filter_argv);
124
            } else {
125
                throw new SyntaxException('Filter callback execution failed: ' . $filter_name);
126
            }
127
        } elseif (method_exists('Ffcms\Core\Filter\Native', $filter_name)) { // only full namespace\class path based :(
128
            if ($filter_argv != null) {
129
                $check = Native::$filter_name($field_value, $filter_argv);
130
            } else {
131
                $check = Native::$filter_name($field_value);
132
            }
133
        } else {
134
            throw new SyntaxException('Filter "' . $filter_name . '" is not exist');
135
        }
136
        if ($check !== true) { // switch only on fail check.
137
            $this->_badAttr[] = $field_name;
138
        } else {
139
            $field_set_name = $field_name;
140
            // prevent array-type setting
141
            if (Str::contains('.', $field_set_name)) {
142
                $field_set_name = strstr($field_set_name, '.', true);
143
            }
144
            if (property_exists($this, $field_set_name)) {
145
                if ($field_name !== $field_set_name) { // array-based property
146
                    $dot_path = trim(strstr($field_name, '.'), '.');
147
                    // prevent throws any exceptions for null and false objects
148
                    if (!Obj::isArray($this->{$field_set_name})) {
149
                        $this->{$field_set_name} = [];
150
                    }
151
                    // use dot-data provider to compile output array
152
                    $dotData = new DotData($this->{$field_set_name});
153
                    $dotData->set($dot_path, $field_value); // todo: check me!!! bug here
154
                    // export data from dot-data lib to model property
155
                    $this->{$field_set_name} = $dotData->export();
156
                } else { // just single property
157
                    $this->{$field_name} = $field_value; // refresh model property's from post data
158
                }
159
            }
160
        }
161
        return $check;
162
    }
163
164
    /**
165
     * Get fail validation attributes as array if exist
166
     * @return null|array
167
     */
168
    public function getBadAttribute()
169
    {
170
        return $this->_badAttr;
171
    }
172
173
    /**
174
     * Set model send method type. Allowed: post, get
175
     * @param string $acceptMethod
176
     */
177
    final public function setSubmitMethod($acceptMethod)
178
    {
179
        $this->_sendMethod = Str::upperCase($acceptMethod);
180
    }
181
182
    /**
183
     * Get model submit method. Allowed: post, get
184
     * @return string
185
     */
186
    final public function getSubmitMethod()
187
    {
188
        return $this->_sendMethod;
189
    }
190
191
    /**
192
     * Check if model get POST-based request as submit of SEND data
193
     * @return bool
194
     */
195
    final public function send()
196
    {
197
        if (App::$Request->getMethod() !== $this->_sendMethod) {
198
            return false;
199
        }
200
201
        return $this->getRequest('submit', $this->_sendMethod) !== null;
202
    }
203
204
    /**
205
     * Form default name (used in field building)
206
     * @return string
207
     */
208
    public function getFormName()
209
    {
210
        if ($this->_formName === null) {
211
            $cname = get_class($this);
212
            $this->_formName = substr($cname, strrpos($cname, '\\') + 1);
213
        }
214
215
        return $this->_formName;
216
    }
217
218
    /**
219
     * @deprecated
220
     * Get input params GET/POST/PUT method
221
     * @param string $param
222
     * @return string|null
223
     */
224
    public function getInput($param)
225
    {
226
        return $this->getRequest($param, $this->_sendMethod);
227
    }
228
229
    /**
230
     * @deprecated
231
     * Get uploaded file from user via POST request
232
     * @param string $param
233
     * @return \Symfony\Component\HttpFoundation\File\UploadedFile|null
234
     */
235
    public function getFile($param)
236
    {
237
        return $this->getRequest($param, 'file');
238
    }
239
240
    /**
241
     * Get input param for current model form based on param name and request method
242
     * @param string $param
243
     * @param string $method
244
     * @return string|null|array
245
     */
246
    public function getRequest($param, $method = 'get')
247
    {
248
        // build param query for http foundation request
249
        $paramQuery = $this->getFormName();
250
        if (Str::contains('.', $param)) {
251
            foreach (explode('.', $param) as $item) {
252
                $paramQuery .= '[' . $item . ']';
253
            }
254
        } else {
255
            $paramQuery .= '[' . $param . ']';
256
        }
257
258
        // get request based on method and param query
259
        $method = Str::lowerCase($method);
260
        switch ($method) {
261
            case 'get':
262
                return App::$Request->query->get($paramQuery, null, true);
263
            case 'post':
264
                return App::$Request->request->get($paramQuery, null, true);
265
            case 'file':
266
                return App::$Request->files->get($paramQuery, null, true);
267
            default:
268
                return App::$Request->get($paramQuery, null, true);
269
270
        }
271
    }
272
273
    /**
274
     * Set special type for input data. Example: ['avatar' => 'file', 'login' => 'input']
275
     * @return array
276
     */
277
    public function inputTypes()
278
    {
279
        return [];
280
    }
281
}