These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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
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
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 dependenciesPHP Analyzer uses your Are you sure this class is defined by one of your dependencies, or did you maybe
not list a dependency in either the 2. Missing use statementPHP does not complain about undefined classes in 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
$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
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
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
$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
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
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
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
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 |