SchemaValidator::recursiveProcess()   F
last analyzed

Complexity

Conditions 60
Paths > 20000

Size

Total Lines 189

Duplication

Lines 17
Ratio 8.99 %

Code Coverage

Tests 124
CRAP Score 60

Importance

Changes 0
Metric Value
dl 17
loc 189
ccs 124
cts 124
cp 1
rs 0
c 0
b 0
f 0
cc 60
nc 32037
nop 4
crap 60

How to fix   Long Method    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
namespace GuzzleHttp\Command\Guzzle;
3
4
use GuzzleHttp\Command\ToArrayInterface;
5
6
/**
7
 * Default parameter validator
8
 */
9
class SchemaValidator
10
{
11
    /**
12
     * Whether or not integers are converted to strings when an integer is
13
     * received for a string input
14
     *
15
     * @var bool
16
     */
17
    protected $castIntegerToStringType;
18
19
    /** @var array Errors encountered while validating */
20
    protected $errors;
21
22
    /**
23
     * @param bool $castIntegerToStringType Set to true to convert integers
24
     *     into strings when a required type is a string and the input value is
25
     *     an integer. Defaults to true.
26
     */
27 17
    public function __construct($castIntegerToStringType = true)
28
    {
29 17
        $this->castIntegerToStringType = $castIntegerToStringType;
30 17
    }
31
32
    /**
33
     * @param Parameter $param
34
     * @param $value
35
     * @return bool
36
     */
37 15
    public function validate(Parameter $param, &$value)
38
    {
39 15
        $this->errors = [];
40 15
        $this->recursiveProcess($param, $value);
41
42 15
        if (empty($this->errors)) {
43 8
            return true;
44
        } else {
45 8
            sort($this->errors);
46 8
            return false;
47
        }
48
    }
49
50
    /**
51
     * Get the errors encountered while validating
52
     *
53
     * @return array
54
     */
55 8
    public function getErrors()
56
    {
57 8
        return $this->errors ?: [];
58
    }
59
60
    /**
61
     * From the allowable types, determine the type that the variable matches
62
     *
63
     * @param string|array $type Parameter type
64
     * @param mixed $value Value to determine the type
65
     *
66
     * @return string|false Returns the matching type on
67
     */
68 15
    protected function determineType($type, $value)
69
    {
70 15
        foreach ((array) $type as $t) {
71
            if ($t == 'string'
72 15
                && (is_string($value) || (is_object($value) && method_exists($value, '__toString')))
73 15
            ) {
74 5
                return 'string';
75 15
            } elseif ($t == 'object' && (is_array($value) || is_object($value))) {
76 11
                return 'object';
77 10
            } elseif ($t == 'array' && is_array($value)) {
78 7
                return 'array';
79 9
            } elseif ($t == 'integer' && is_integer($value)) {
80 2
                return 'integer';
81 9
            } elseif ($t == 'boolean' && is_bool($value)) {
82 3
                return 'boolean';
83 7
            } elseif ($t == 'number' && is_numeric($value)) {
84 1
                return 'number';
85 7
            } elseif ($t == 'numeric' && is_numeric($value)) {
86 1
                return 'numeric';
87 7
            } elseif ($t == 'null' && !$value) {
88 1
                return 'null';
89 7
            } elseif ($t == 'any') {
90 1
                return 'any';
91
            }
92 7
        }
93
94 6
        return false;
95
    }
96
97
    /**
98
     * Recursively validate a parameter
99
     *
100
     * @param Parameter $param  API parameter being validated
101
     * @param mixed     $value  Value to validate and validate. The value may
102
     *                          change during this validate.
103
     * @param string    $path   Current validation path (used for error reporting)
104
     * @param int       $depth  Current depth in the validation validate
105
     *
106
     * @return bool Returns true if valid, or false if invalid
107
     */
108 15
    protected function recursiveProcess(
109
        Parameter $param,
110
        &$value,
111
        $path = '',
112
        $depth = 0
113
    ) {
114
        // Update the value by adding default or static values
115 15
        $value = $param->getValue($value);
116
117 15
        $required = $param->isRequired();
118
        // if the value is null and the parameter is not required or is static,
119
        // then skip any further recursion
120 15
        if ((null === $value && !$required) || $param->isStatic()) {
121 6
            return true;
122
        }
123
124 15
        $type = $param->getType();
125
        // Attempt to limit the number of times is_array is called by tracking
126
        // if the value is an array
127 15
        $valueIsArray = is_array($value);
128
        // If a name is set then update the path so that validation messages
129
        // are more helpful
130 15
        if ($name = $param->getName()) {
0 ignored issues
show
Bug introduced by
Consider using $param->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
131 15
            $path .= "[{$name}]";
132 15
        }
133
134 15
        if ($type == 'object') {
135
            // Determine whether or not this "value" has properties and should
136
            // be traversed
137 12
            $traverse = $temporaryValue = false;
138
139
            // Convert the value to an array
140 12
            if (!$valueIsArray && $value instanceof ToArrayInterface) {
0 ignored issues
show
Bug introduced by
The class GuzzleHttp\Command\ToArrayInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
141 1
                $value = $value->toArray();
142 1
            }
143
144 12
            if ($valueIsArray) {
145
                // Ensure that the array is associative and not numerically
146
                // indexed
147 10
                if (isset($value[0])) {
148 1
                    $this->errors[] = "{$path} must be an array of properties. Got a numerically indexed array.";
149 1
                    return false;
150
                }
151 9
                $traverse = true;
152 11
            } elseif ($value === null) {
153
                // Attempt to let the contents be built up by default values if
154
                // possible
155 2
                $value = [];
156 2
                $temporaryValue = $valueIsArray = $traverse = true;
0 ignored issues
show
Unused Code introduced by
$valueIsArray 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...
157 2
            }
158
159 11
            if ($traverse) {
160 9
                if ($properties = $param->getProperties()) {
161
                    // if properties were found, validate each property
162 8
                    foreach ($properties as $property) {
163 8
                        $name = $property->getName();
0 ignored issues
show
Bug introduced by
Consider using $property->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
164 8
                        if (isset($value[$name])) {
165 3
                            $this->recursiveProcess($property, $value[$name], $path, $depth + 1);
166 3
                        } else {
167 8
                            $current = null;
168 8
                            $this->recursiveProcess($property, $current, $path, $depth + 1);
169
                            // Only set the value if it was populated
170 8
                            if (null !== $current) {
171 3
                                $value[$name] = $current;
172 3
                            }
173
                        }
174 8
                    }
175 8
                }
176
177 9
                $additional = $param->getAdditionalProperties();
178 9
                if ($additional !== true) {
179
                    // If additional properties were found, then validate each
180
                    // against the additionalProperties attr.
181 3
                    $keys = array_keys($value);
182
                    // Determine the keys that were specified that were not
183
                    // listed in the properties of the schema
184 3
                    $diff = array_diff($keys, array_keys($properties));
185 3
                    if (!empty($diff)) {
186
                        // Determine which keys are not in the properties
187 3
                        if ($additional instanceof Parameter) {
188 2
                            foreach ($diff as $key) {
189 2
                                $this->recursiveProcess($additional, $value[$key], "{$path}[{$key}]", $depth);
190 2
                            }
191 2
                        } else {
192
                            // if additionalProperties is set to false and there
193
                            // are additionalProperties in the values, then fail
194 1
                            foreach ($diff as $prop) {
195 1
                                $this->errors[] = sprintf('%s[%s] is not an allowed property', $path, $prop);
196 1
                            }
197
                        }
198 3
                    }
199 3
                }
200
201
                // A temporary value will be used to traverse elements that
202
                // have no corresponding input value. This allows nested
203
                // required parameters with default values to bubble up into the
204
                // input. Here we check if we used a temp value and nothing
205
                // bubbled up, then we need to remote the value.
206 9
                if ($temporaryValue && empty($value)) {
207 1
                    $value = null;
208 1
                    $valueIsArray = false;
0 ignored issues
show
Unused Code introduced by
$valueIsArray 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...
209 1
                }
210 9
            }
211
212 15
        } elseif ($type == 'array' && $valueIsArray && $param->getItems()) {
213 6
            foreach ($value as $i => &$item) {
214
                // Validate each item in an array against the items attribute of the schema
215 6
                $this->recursiveProcess($param->getItems(), $item, $path . "[{$i}]", $depth + 1);
216 6
            }
217 6
        }
218
219
        // If the value is required and the type is not null, then there is an
220
        // error if the value is not set
221 15
        if ($required && $value === null && $type != 'null') {
222 3
            $message = "{$path} is " . ($param->getType()
223 3
                ? ('a required ' . implode(' or ', (array) $param->getType()))
224 3
                : 'required');
225 3
            if ($param->has('description')) {
226 1
                $message .= ': ' . $param->getDescription();
227 1
            }
228 3
            $this->errors[] = $message;
229 3
            return false;
230
        }
231
232
        // Validate that the type is correct. If the type is string but an
233
        // integer was passed, the class can be instructed to cast the integer
234
        // to a string to pass validation. This is the default behavior.
235 14
        if ($type && (!$type = $this->determineType($type, $value))) {
236 5
            if ($this->castIntegerToStringType
237 5
                && $param->getType() == 'string'
238 5
                && is_integer($value)
239 5
            ) {
240 1
                $value = (string) $value;
241 1
            } else {
242 4
                $this->errors[] = "{$path} must be of type " . implode(' or ', (array) $param->getType());
243
            }
244 5
        }
245
246
        // Perform type specific validation for strings, arrays, and integers
247 14
        if ($type == 'string') {
248
            // Strings can have enums which are a list of predefined values
249 4
            if (($enum = $param->getEnum()) && !in_array($value, $enum)) {
250 1
                $this->errors[] = "{$path} must be one of " . implode(' or ', array_map(function ($s) {
251 1
                        return '"' . addslashes($s) . '"';
252 1
                }, $enum));
253 1
            }
254
            // Strings can have a regex pattern that the value must match
255 4
            if (($pattern  = $param->getPattern()) && !preg_match($pattern, $value)) {
256 1
                $this->errors[] = "{$path} must match the following regular expression: {$pattern}";
257 1
            }
258
259 4
            $strLen = null;
260 4 View Code Duplication
            if ($min = $param->getMinLength()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
261 1
                $strLen = strlen($value);
262 1
                if ($strLen < $min) {
263 1
                    $this->errors[] = "{$path} length must be greater than or equal to {$min}";
264 1
                }
265 1
            }
266 4 View Code Duplication
            if ($max = $param->getMaxLength()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
267 1
                if (($strLen ?: strlen($value)) > $max) {
268 1
                    $this->errors[] = "{$path} length must be less than or equal to {$max}";
269 1
                }
270 1
            }
271
272 14
        } elseif ($type == 'array') {
273 6
            $size = null;
274 6 View Code Duplication
            if ($min = $param->getMinItems()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
275 1
                $size = count($value);
276 1
                if ($size < $min) {
277 1
                    $this->errors[] = "{$path} must contain {$min} or more elements";
278 1
                }
279 1
            }
280 6
            if ($max = $param->getMaxItems()) {
281 1
                if (($size ?: count($value)) > $max) {
282 1
                    $this->errors[] = "{$path} must contain {$max} or fewer elements";
283 1
                }
284 1
            }
285
286 14
        } elseif ($type == 'integer' || $type == 'number' || $type == 'numeric') {
287 1
            if (($min = $param->getMinimum()) && $value < $min) {
288 1
                $this->errors[] = "{$path} must be greater than or equal to {$min}";
289 1
            }
290 1
            if (($max = $param->getMaximum()) && $value > $max) {
291 1
                $this->errors[] = "{$path} must be less than or equal to {$max}";
292 1
            }
293 1
        }
294
295 14
        return empty($this->errors);
296
    }
297
}
298