1 | <?php |
||
10 | final class Validator |
||
11 | { |
||
12 | /** |
||
13 | * @var array |
||
14 | */ |
||
15 | private $errors = []; |
||
16 | |||
17 | /** |
||
18 | * @var mixed |
||
19 | */ |
||
20 | private $data; |
||
21 | |||
22 | /** |
||
23 | * @var object |
||
24 | */ |
||
25 | private $schema; |
||
26 | |||
27 | /** |
||
28 | * @var string |
||
29 | */ |
||
30 | private $dataPath = '/'; |
||
31 | |||
32 | /** |
||
33 | * @var string |
||
34 | */ |
||
35 | private $baseSchemaPath = '/'; |
||
36 | |||
37 | /** |
||
38 | * The maximum depth the validator should recurse into $data |
||
39 | * before throwing an exception. |
||
40 | * |
||
41 | * @var int |
||
42 | */ |
||
43 | private $maxDepth = 50; |
||
44 | |||
45 | /** |
||
46 | * The depth the validator has reached in the data. |
||
47 | * |
||
48 | * @var int |
||
49 | */ |
||
50 | private $depth = 0; |
||
51 | |||
52 | /** |
||
53 | * @var \Psr\Container\ContainerInterface |
||
54 | */ |
||
55 | private $ruleSet; |
||
56 | |||
57 | /** |
||
58 | * @var bool |
||
59 | */ |
||
60 | private $hasValidated; |
||
61 | |||
62 | /** |
||
63 | * @var string |
||
64 | */ |
||
65 | private $currentKeyword; |
||
66 | |||
67 | /** |
||
68 | * @var mixed |
||
69 | */ |
||
70 | private $currentParameter; |
||
71 | |||
72 | /** |
||
73 | * @param mixed $data |
||
74 | * @param object $schema |
||
75 | * @param ContainerInterface|null $ruleSet |
||
76 | */ |
||
77 | 240 | public function __construct($data, $schema, ContainerInterface $ruleSet = null) |
|
78 | { |
||
79 | 240 | if (!is_object($schema)) { |
|
80 | 2 | throw new \InvalidArgumentException( |
|
81 | 2 | sprintf('The schema should be an object from a json_decode call, got "%s"', gettype($schema)) |
|
82 | ); |
||
83 | } |
||
84 | |||
85 | 238 | while ($schema instanceof Reference) { |
|
86 | 16 | $schema = $schema->resolve(); |
|
87 | } |
||
88 | |||
89 | 238 | $this->data = $data; |
|
90 | 238 | $this->schema = $schema; |
|
91 | 238 | $this->ruleSet = $ruleSet ?: new DraftFour(); |
|
92 | 238 | } |
|
93 | |||
94 | /** |
||
95 | * @return boolean |
||
96 | * |
||
97 | * @throws \League\JsonGuard\Exception\InvalidSchemaException |
||
98 | * @throws \League\JsonGuard\Exception\MaximumDepthExceededException |
||
99 | */ |
||
100 | 82 | public function fails() |
|
104 | |||
105 | /** |
||
106 | * @return boolean |
||
107 | * |
||
108 | * @throws \League\JsonGuard\Exception\InvalidSchemaException |
||
109 | * @throws \League\JsonGuard\Exception\MaximumDepthExceededException |
||
110 | */ |
||
111 | 82 | public function passes() |
|
115 | |||
116 | /** |
||
117 | * Get a collection of errors. |
||
118 | * |
||
119 | * @return ValidationError[] |
||
120 | * |
||
121 | * @throws \League\JsonGuard\Exception\InvalidSchemaException |
||
122 | * @throws \League\JsonGuard\Exception\MaximumDepthExceededException |
||
123 | */ |
||
124 | 150 | public function errors() |
|
130 | |||
131 | /** |
||
132 | * Set the maximum allowed depth data will be validated until. |
||
133 | * If the data exceeds the stack depth an exception is thrown. |
||
134 | * |
||
135 | * @param int $maxDepth |
||
136 | * |
||
137 | * @return $this |
||
138 | */ |
||
139 | 4 | public function setMaxDepth($maxDepth) |
|
145 | |||
146 | /** |
||
147 | * @return \Psr\Container\ContainerInterface |
||
148 | */ |
||
149 | 4 | public function getRuleSet() |
|
153 | |||
154 | /** |
||
155 | * @return string |
||
156 | */ |
||
157 | 100 | public function getDataPath() |
|
161 | |||
162 | /** |
||
163 | * @return mixed |
||
164 | */ |
||
165 | 94 | public function getData() |
|
169 | |||
170 | /** |
||
171 | * @return object |
||
172 | */ |
||
173 | 96 | public function getSchema() |
|
177 | |||
178 | /** |
||
179 | * @return string |
||
180 | */ |
||
181 | 238 | public function getSchemaPath() |
|
185 | |||
186 | /** |
||
187 | * @return string |
||
188 | */ |
||
189 | 94 | public function getCurrentKeyword() |
|
193 | |||
194 | /** |
||
195 | * @return mixed |
||
196 | */ |
||
197 | 94 | public function getCurrentParameter() |
|
201 | |||
202 | /** |
||
203 | * Create a new sub-validator. |
||
204 | * |
||
205 | * @param mixed $data |
||
206 | * @param object $schema |
||
207 | * @param string|null $dataPath |
||
208 | * @param string|null $schemaPath |
||
209 | * |
||
210 | * @return Validator |
||
211 | */ |
||
212 | 48 | public function makeSubSchemaValidator($data, $schema, $dataPath = null, $schemaPath = null) |
|
223 | |||
224 | /** |
||
225 | * Validate the data and collect the errors. |
||
226 | */ |
||
227 | 150 | private function validate() |
|
228 | { |
||
229 | 150 | if ($this->hasValidated) { |
|
230 | 30 | return; |
|
231 | } |
||
232 | |||
233 | 150 | $this->checkDepth(); |
|
234 | |||
235 | 150 | foreach ($this->schema as $rule => $parameter) { |
|
236 | 150 | $this->currentKeyword = $rule; |
|
|
|||
237 | 150 | $this->currentParameter = $parameter; |
|
238 | 150 | $this->mergeErrors($this->validateRule($rule, $parameter)); |
|
239 | 80 | $this->currentKeyword = $this->currentParameter = null; |
|
240 | } |
||
241 | |||
242 | 80 | $this->hasValidated = true; |
|
243 | 80 | } |
|
244 | |||
245 | /** |
||
246 | * Keep track of how many levels deep we have validated. |
||
247 | * This is to prevent a really deeply nested JSON |
||
248 | * structure from causing the validator to continue |
||
249 | * validating for an incredibly long time. |
||
250 | * |
||
251 | * @throws \League\JsonGuard\Exception\MaximumDepthExceededException |
||
252 | */ |
||
253 | 150 | private function checkDepth() |
|
259 | |||
260 | /** |
||
261 | * Validate the data using the given rule and parameter. |
||
262 | * |
||
263 | * @param string $keyword |
||
264 | * @param mixed $parameter |
||
265 | * |
||
266 | * @return null|ValidationError|ValidationError[] |
||
267 | */ |
||
268 | 150 | private function validateRule($keyword, $parameter) |
|
276 | |||
277 | /** |
||
278 | * Merge the errors with our error collection. |
||
279 | * |
||
280 | * @param ValidationError[]|ValidationError|null $errors |
||
281 | */ |
||
282 | 80 | private function mergeErrors($errors) |
|
291 | } |
||
292 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.