@@ -19,63 +19,63 @@ |
||
19 | 19 | */ |
20 | 20 | class NumberConstraint extends Constraint |
21 | 21 | { |
22 | - /** |
|
23 | - * {@inheritdoc} |
|
24 | - */ |
|
25 | - public function check(&$element, $schema = null, JsonPointer $path = null, $i = null) |
|
26 | - { |
|
27 | - // Verify minimum |
|
28 | - if (isset($schema->exclusiveMinimum)) { |
|
29 | - if (isset($schema->minimum)) { |
|
30 | - if ($schema->exclusiveMinimum && $element <= $schema->minimum) { |
|
31 | - $this->addError($path, 'Must have a minimum value of ' . $schema->minimum, 'exclusiveMinimum', array('minimum' => $schema->minimum)); |
|
32 | - } elseif ($element < $schema->minimum) { |
|
33 | - $this->addError($path, 'Must have a minimum value of ' . $schema->minimum, 'minimum', array('minimum' => $schema->minimum)); |
|
34 | - } |
|
35 | - } else { |
|
36 | - $this->addError($path, 'Use of exclusiveMinimum requires presence of minimum', 'missingMinimum'); |
|
37 | - } |
|
38 | - } elseif (isset($schema->minimum) && $element < $schema->minimum) { |
|
39 | - $this->addError($path, 'Must have a minimum value of ' . $schema->minimum, 'minimum', array('minimum' => $schema->minimum)); |
|
40 | - } |
|
22 | + /** |
|
23 | + * {@inheritdoc} |
|
24 | + */ |
|
25 | + public function check(&$element, $schema = null, JsonPointer $path = null, $i = null) |
|
26 | + { |
|
27 | + // Verify minimum |
|
28 | + if (isset($schema->exclusiveMinimum)) { |
|
29 | + if (isset($schema->minimum)) { |
|
30 | + if ($schema->exclusiveMinimum && $element <= $schema->minimum) { |
|
31 | + $this->addError($path, 'Must have a minimum value of ' . $schema->minimum, 'exclusiveMinimum', array('minimum' => $schema->minimum)); |
|
32 | + } elseif ($element < $schema->minimum) { |
|
33 | + $this->addError($path, 'Must have a minimum value of ' . $schema->minimum, 'minimum', array('minimum' => $schema->minimum)); |
|
34 | + } |
|
35 | + } else { |
|
36 | + $this->addError($path, 'Use of exclusiveMinimum requires presence of minimum', 'missingMinimum'); |
|
37 | + } |
|
38 | + } elseif (isset($schema->minimum) && $element < $schema->minimum) { |
|
39 | + $this->addError($path, 'Must have a minimum value of ' . $schema->minimum, 'minimum', array('minimum' => $schema->minimum)); |
|
40 | + } |
|
41 | 41 | |
42 | - // Verify maximum |
|
43 | - if (isset($schema->exclusiveMaximum)) { |
|
44 | - if (isset($schema->maximum)) { |
|
45 | - if ($schema->exclusiveMaximum && $element >= $schema->maximum) { |
|
46 | - $this->addError($path, 'Must have a maximum value of ' . $schema->maximum, 'exclusiveMaximum', array('maximum' => $schema->maximum)); |
|
47 | - } elseif ($element > $schema->maximum) { |
|
48 | - $this->addError($path, 'Must have a maximum value of ' . $schema->maximum, 'maximum', array('maximum' => $schema->maximum)); |
|
49 | - } |
|
50 | - } else { |
|
51 | - $this->addError($path, 'Use of exclusiveMaximum requires presence of maximum', 'missingMaximum'); |
|
52 | - } |
|
53 | - } elseif (isset($schema->maximum) && $element > $schema->maximum) { |
|
54 | - $this->addError($path, 'Must have a maximum value of ' . $schema->maximum, 'maximum', array('maximum' => $schema->maximum)); |
|
55 | - } |
|
42 | + // Verify maximum |
|
43 | + if (isset($schema->exclusiveMaximum)) { |
|
44 | + if (isset($schema->maximum)) { |
|
45 | + if ($schema->exclusiveMaximum && $element >= $schema->maximum) { |
|
46 | + $this->addError($path, 'Must have a maximum value of ' . $schema->maximum, 'exclusiveMaximum', array('maximum' => $schema->maximum)); |
|
47 | + } elseif ($element > $schema->maximum) { |
|
48 | + $this->addError($path, 'Must have a maximum value of ' . $schema->maximum, 'maximum', array('maximum' => $schema->maximum)); |
|
49 | + } |
|
50 | + } else { |
|
51 | + $this->addError($path, 'Use of exclusiveMaximum requires presence of maximum', 'missingMaximum'); |
|
52 | + } |
|
53 | + } elseif (isset($schema->maximum) && $element > $schema->maximum) { |
|
54 | + $this->addError($path, 'Must have a maximum value of ' . $schema->maximum, 'maximum', array('maximum' => $schema->maximum)); |
|
55 | + } |
|
56 | 56 | |
57 | - // Verify divisibleBy - Draft v3 |
|
58 | - if (isset($schema->divisibleBy) && $this->fmod($element, $schema->divisibleBy) != 0) { |
|
59 | - $this->addError($path, 'Is not divisible by ' . $schema->divisibleBy, 'divisibleBy', array('divisibleBy' => $schema->divisibleBy)); |
|
60 | - } |
|
57 | + // Verify divisibleBy - Draft v3 |
|
58 | + if (isset($schema->divisibleBy) && $this->fmod($element, $schema->divisibleBy) != 0) { |
|
59 | + $this->addError($path, 'Is not divisible by ' . $schema->divisibleBy, 'divisibleBy', array('divisibleBy' => $schema->divisibleBy)); |
|
60 | + } |
|
61 | 61 | |
62 | - // Verify multipleOf - Draft v4 |
|
63 | - if (isset($schema->multipleOf) && $this->fmod($element, $schema->multipleOf) != 0) { |
|
64 | - $this->addError($path, 'Must be a multiple of ' . $schema->multipleOf, 'multipleOf', array('multipleOf' => $schema->multipleOf)); |
|
65 | - } |
|
62 | + // Verify multipleOf - Draft v4 |
|
63 | + if (isset($schema->multipleOf) && $this->fmod($element, $schema->multipleOf) != 0) { |
|
64 | + $this->addError($path, 'Must be a multiple of ' . $schema->multipleOf, 'multipleOf', array('multipleOf' => $schema->multipleOf)); |
|
65 | + } |
|
66 | 66 | |
67 | - $this->checkFormat($element, $schema, $path, $i); |
|
68 | - } |
|
67 | + $this->checkFormat($element, $schema, $path, $i); |
|
68 | + } |
|
69 | 69 | |
70 | - private function fmod($number1, $number2) |
|
71 | - { |
|
72 | - $modulus = ($number1 - round($number1 / $number2) * $number2); |
|
73 | - $precision = 0.0000000001; |
|
70 | + private function fmod($number1, $number2) |
|
71 | + { |
|
72 | + $modulus = ($number1 - round($number1 / $number2) * $number2); |
|
73 | + $precision = 0.0000000001; |
|
74 | 74 | |
75 | - if (-$precision < $modulus && $modulus < $precision) { |
|
76 | - return 0.0; |
|
77 | - } |
|
75 | + if (-$precision < $modulus && $modulus < $precision) { |
|
76 | + return 0.0; |
|
77 | + } |
|
78 | 78 | |
79 | - return $modulus; |
|
80 | - } |
|
79 | + return $modulus; |
|
80 | + } |
|
81 | 81 | } |
@@ -21,194 +21,194 @@ |
||
21 | 21 | */ |
22 | 22 | class FormatConstraint extends Constraint |
23 | 23 | { |
24 | - /** |
|
25 | - * {@inheritdoc} |
|
26 | - */ |
|
27 | - public function check(&$element, $schema = null, JsonPointer $path = null, $i = null) |
|
28 | - { |
|
29 | - if (!isset($schema->format) || $this->factory->getConfig(self::CHECK_MODE_DISABLE_FORMAT)) { |
|
30 | - return; |
|
31 | - } |
|
32 | - |
|
33 | - switch ($schema->format) { |
|
34 | - case 'date': |
|
35 | - if (!$date = $this->validateDateTime($element, 'Y-m-d')) { |
|
36 | - $this->addError($path, sprintf('Invalid date %s, expected format YYYY-MM-DD', json_encode($element)), 'format', array('format' => $schema->format)); |
|
37 | - } |
|
38 | - break; |
|
39 | - |
|
40 | - case 'time': |
|
41 | - if (!$this->validateDateTime($element, 'H:i:s')) { |
|
42 | - $this->addError($path, sprintf('Invalid time %s, expected format hh:mm:ss', json_encode($element)), 'format', array('format' => $schema->format)); |
|
43 | - } |
|
44 | - break; |
|
45 | - |
|
46 | - case 'date-time': |
|
47 | - if (null === Rfc3339::createFromString($element)) { |
|
48 | - $this->addError($path, sprintf('Invalid date-time %s, expected format YYYY-MM-DDThh:mm:ssZ or YYYY-MM-DDThh:mm:ss+hh:mm', json_encode($element)), 'format', array('format' => $schema->format)); |
|
49 | - } |
|
50 | - break; |
|
51 | - |
|
52 | - case 'utc-millisec': |
|
53 | - if (!$this->validateDateTime($element, 'U')) { |
|
54 | - $this->addError($path, sprintf('Invalid time %s, expected integer of milliseconds since Epoch', json_encode($element)), 'format', array('format' => $schema->format)); |
|
55 | - } |
|
56 | - break; |
|
57 | - |
|
58 | - case 'regex': |
|
59 | - if (!$this->validateRegex($element)) { |
|
60 | - $this->addError($path, 'Invalid regex format ' . $element, 'format', array('format' => $schema->format)); |
|
61 | - } |
|
62 | - break; |
|
63 | - |
|
64 | - case 'color': |
|
65 | - if (!$this->validateColor($element)) { |
|
66 | - $this->addError($path, 'Invalid color', 'format', array('format' => $schema->format)); |
|
67 | - } |
|
68 | - break; |
|
69 | - |
|
70 | - case 'style': |
|
71 | - if (!$this->validateStyle($element)) { |
|
72 | - $this->addError($path, 'Invalid style', 'format', array('format' => $schema->format)); |
|
73 | - } |
|
74 | - break; |
|
75 | - |
|
76 | - case 'phone': |
|
77 | - if (!$this->validatePhone($element)) { |
|
78 | - $this->addError($path, 'Invalid phone number', 'format', array('format' => $schema->format)); |
|
79 | - } |
|
80 | - break; |
|
81 | - |
|
82 | - case 'uri': |
|
83 | - if (null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) { |
|
84 | - $this->addError($path, 'Invalid URL format', 'format', array('format' => $schema->format)); |
|
85 | - } |
|
86 | - break; |
|
87 | - |
|
88 | - case 'uriref': |
|
89 | - case 'uri-reference': |
|
90 | - if (null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) { |
|
91 | - // FILTER_VALIDATE_URL does not conform to RFC-3986, and cannot handle relative URLs, but |
|
92 | - // the json-schema spec uses RFC-3986, so need a bit of hackery to properly validate them. |
|
93 | - // See https://tools.ietf.org/html/rfc3986#section-4.2 for additional information. |
|
94 | - if (substr($element, 0, 2) === '//') { // network-path reference |
|
95 | - $validURL = filter_var('scheme:' . $element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE); |
|
96 | - } elseif (substr($element, 0, 1) === '/') { // absolute-path reference |
|
97 | - $validURL = filter_var('scheme://host' . $element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE); |
|
98 | - } elseif (strlen($element)) { // relative-path reference |
|
99 | - $pathParts = explode('/', $element, 2); |
|
100 | - if (strpos($pathParts[0], ':') !== false) { |
|
101 | - $validURL = null; |
|
102 | - } else { |
|
103 | - $validURL = filter_var('scheme://host/' . $element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE); |
|
104 | - } |
|
105 | - } else { |
|
106 | - $validURL = null; |
|
107 | - } |
|
108 | - if ($validURL === null) { |
|
109 | - $this->addError($path, 'Invalid URL format', 'format', array('format' => $schema->format)); |
|
110 | - } |
|
111 | - } |
|
112 | - break; |
|
113 | - |
|
114 | - case 'email': |
|
115 | - $filterFlags = FILTER_NULL_ON_FAILURE; |
|
116 | - if (defined('FILTER_FLAG_EMAIL_UNICODE')) { |
|
117 | - // Only available from PHP >= 7.1.0, so ignore it for coverage checks |
|
118 | - $filterFlags |= constant('FILTER_FLAG_EMAIL_UNICODE'); // @codeCoverageIgnore |
|
119 | - } |
|
120 | - if (null === filter_var($element, FILTER_VALIDATE_EMAIL, $filterFlags)) { |
|
121 | - $this->addError($path, 'Invalid email', 'format', array('format' => $schema->format)); |
|
122 | - } |
|
123 | - break; |
|
124 | - |
|
125 | - case 'ip-address': |
|
126 | - case 'ipv4': |
|
127 | - if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4)) { |
|
128 | - $this->addError($path, 'Invalid IP address', 'format', array('format' => $schema->format)); |
|
129 | - } |
|
130 | - break; |
|
131 | - |
|
132 | - case 'ipv6': |
|
133 | - if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV6)) { |
|
134 | - $this->addError($path, 'Invalid IP address', 'format', array('format' => $schema->format)); |
|
135 | - } |
|
136 | - break; |
|
137 | - |
|
138 | - case 'host-name': |
|
139 | - case 'hostname': |
|
140 | - if (!$this->validateHostname($element)) { |
|
141 | - $this->addError($path, 'Invalid hostname', 'format', array('format' => $schema->format)); |
|
142 | - } |
|
143 | - break; |
|
144 | - |
|
145 | - default: |
|
146 | - // Empty as it should be: |
|
147 | - // The value of this keyword is called a format attribute. It MUST be a string. |
|
148 | - // A format attribute can generally only validate a given set of instance types. |
|
149 | - // If the type of the instance to validate is not in this set, validation for |
|
150 | - // this format attribute and instance SHOULD succeed. |
|
151 | - // http://json-schema.org/latest/json-schema-validation.html#anchor105 |
|
152 | - break; |
|
153 | - } |
|
154 | - } |
|
155 | - |
|
156 | - protected function validateDateTime($datetime, $format) |
|
157 | - { |
|
158 | - $dt = \DateTime::createFromFormat($format, $datetime); |
|
159 | - |
|
160 | - if (!$dt) { |
|
161 | - return false; |
|
162 | - } |
|
163 | - |
|
164 | - if ($datetime === $dt->format($format)) { |
|
165 | - return true; |
|
166 | - } |
|
167 | - |
|
168 | - // handles the case where a non-6 digit microsecond datetime is passed |
|
169 | - // which will fail the above string comparison because the passed |
|
170 | - // $datetime may be '2000-05-01T12:12:12.123Z' but format() will return |
|
171 | - // '2000-05-01T12:12:12.123000Z' |
|
172 | - if ((strpos('u', $format) !== -1) && (preg_match('/\.\d+Z$/', $datetime))) { |
|
173 | - return true; |
|
174 | - } |
|
175 | - |
|
176 | - return false; |
|
177 | - } |
|
178 | - |
|
179 | - protected function validateRegex($regex) |
|
180 | - { |
|
181 | - return false !== @preg_match('/' . $regex . '/u', ''); |
|
182 | - } |
|
183 | - |
|
184 | - protected function validateColor($color) |
|
185 | - { |
|
186 | - if (in_array(strtolower($color), array('aqua', 'black', 'blue', 'fuchsia', |
|
187 | - 'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange', 'purple', |
|
188 | - 'red', 'silver', 'teal', 'white', 'yellow'))) { |
|
189 | - return true; |
|
190 | - } |
|
191 | - |
|
192 | - return preg_match('/^#([a-f0-9]{3}|[a-f0-9]{6})$/i', $color); |
|
193 | - } |
|
194 | - |
|
195 | - protected function validateStyle($style) |
|
196 | - { |
|
197 | - $properties = explode(';', rtrim($style, ';')); |
|
198 | - $invalidEntries = preg_grep('/^\s*[-a-z]+\s*:\s*.+$/i', $properties, PREG_GREP_INVERT); |
|
199 | - |
|
200 | - return empty($invalidEntries); |
|
201 | - } |
|
202 | - |
|
203 | - protected function validatePhone($phone) |
|
204 | - { |
|
205 | - return preg_match('/^\+?(\(\d{3}\)|\d{3}) \d{3} \d{4}$/', $phone); |
|
206 | - } |
|
207 | - |
|
208 | - protected function validateHostname($host) |
|
209 | - { |
|
210 | - $hostnameRegex = '/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/i'; |
|
211 | - |
|
212 | - return preg_match($hostnameRegex, $host); |
|
213 | - } |
|
24 | + /** |
|
25 | + * {@inheritdoc} |
|
26 | + */ |
|
27 | + public function check(&$element, $schema = null, JsonPointer $path = null, $i = null) |
|
28 | + { |
|
29 | + if (!isset($schema->format) || $this->factory->getConfig(self::CHECK_MODE_DISABLE_FORMAT)) { |
|
30 | + return; |
|
31 | + } |
|
32 | + |
|
33 | + switch ($schema->format) { |
|
34 | + case 'date': |
|
35 | + if (!$date = $this->validateDateTime($element, 'Y-m-d')) { |
|
36 | + $this->addError($path, sprintf('Invalid date %s, expected format YYYY-MM-DD', json_encode($element)), 'format', array('format' => $schema->format)); |
|
37 | + } |
|
38 | + break; |
|
39 | + |
|
40 | + case 'time': |
|
41 | + if (!$this->validateDateTime($element, 'H:i:s')) { |
|
42 | + $this->addError($path, sprintf('Invalid time %s, expected format hh:mm:ss', json_encode($element)), 'format', array('format' => $schema->format)); |
|
43 | + } |
|
44 | + break; |
|
45 | + |
|
46 | + case 'date-time': |
|
47 | + if (null === Rfc3339::createFromString($element)) { |
|
48 | + $this->addError($path, sprintf('Invalid date-time %s, expected format YYYY-MM-DDThh:mm:ssZ or YYYY-MM-DDThh:mm:ss+hh:mm', json_encode($element)), 'format', array('format' => $schema->format)); |
|
49 | + } |
|
50 | + break; |
|
51 | + |
|
52 | + case 'utc-millisec': |
|
53 | + if (!$this->validateDateTime($element, 'U')) { |
|
54 | + $this->addError($path, sprintf('Invalid time %s, expected integer of milliseconds since Epoch', json_encode($element)), 'format', array('format' => $schema->format)); |
|
55 | + } |
|
56 | + break; |
|
57 | + |
|
58 | + case 'regex': |
|
59 | + if (!$this->validateRegex($element)) { |
|
60 | + $this->addError($path, 'Invalid regex format ' . $element, 'format', array('format' => $schema->format)); |
|
61 | + } |
|
62 | + break; |
|
63 | + |
|
64 | + case 'color': |
|
65 | + if (!$this->validateColor($element)) { |
|
66 | + $this->addError($path, 'Invalid color', 'format', array('format' => $schema->format)); |
|
67 | + } |
|
68 | + break; |
|
69 | + |
|
70 | + case 'style': |
|
71 | + if (!$this->validateStyle($element)) { |
|
72 | + $this->addError($path, 'Invalid style', 'format', array('format' => $schema->format)); |
|
73 | + } |
|
74 | + break; |
|
75 | + |
|
76 | + case 'phone': |
|
77 | + if (!$this->validatePhone($element)) { |
|
78 | + $this->addError($path, 'Invalid phone number', 'format', array('format' => $schema->format)); |
|
79 | + } |
|
80 | + break; |
|
81 | + |
|
82 | + case 'uri': |
|
83 | + if (null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) { |
|
84 | + $this->addError($path, 'Invalid URL format', 'format', array('format' => $schema->format)); |
|
85 | + } |
|
86 | + break; |
|
87 | + |
|
88 | + case 'uriref': |
|
89 | + case 'uri-reference': |
|
90 | + if (null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) { |
|
91 | + // FILTER_VALIDATE_URL does not conform to RFC-3986, and cannot handle relative URLs, but |
|
92 | + // the json-schema spec uses RFC-3986, so need a bit of hackery to properly validate them. |
|
93 | + // See https://tools.ietf.org/html/rfc3986#section-4.2 for additional information. |
|
94 | + if (substr($element, 0, 2) === '//') { // network-path reference |
|
95 | + $validURL = filter_var('scheme:' . $element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE); |
|
96 | + } elseif (substr($element, 0, 1) === '/') { // absolute-path reference |
|
97 | + $validURL = filter_var('scheme://host' . $element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE); |
|
98 | + } elseif (strlen($element)) { // relative-path reference |
|
99 | + $pathParts = explode('/', $element, 2); |
|
100 | + if (strpos($pathParts[0], ':') !== false) { |
|
101 | + $validURL = null; |
|
102 | + } else { |
|
103 | + $validURL = filter_var('scheme://host/' . $element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE); |
|
104 | + } |
|
105 | + } else { |
|
106 | + $validURL = null; |
|
107 | + } |
|
108 | + if ($validURL === null) { |
|
109 | + $this->addError($path, 'Invalid URL format', 'format', array('format' => $schema->format)); |
|
110 | + } |
|
111 | + } |
|
112 | + break; |
|
113 | + |
|
114 | + case 'email': |
|
115 | + $filterFlags = FILTER_NULL_ON_FAILURE; |
|
116 | + if (defined('FILTER_FLAG_EMAIL_UNICODE')) { |
|
117 | + // Only available from PHP >= 7.1.0, so ignore it for coverage checks |
|
118 | + $filterFlags |= constant('FILTER_FLAG_EMAIL_UNICODE'); // @codeCoverageIgnore |
|
119 | + } |
|
120 | + if (null === filter_var($element, FILTER_VALIDATE_EMAIL, $filterFlags)) { |
|
121 | + $this->addError($path, 'Invalid email', 'format', array('format' => $schema->format)); |
|
122 | + } |
|
123 | + break; |
|
124 | + |
|
125 | + case 'ip-address': |
|
126 | + case 'ipv4': |
|
127 | + if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4)) { |
|
128 | + $this->addError($path, 'Invalid IP address', 'format', array('format' => $schema->format)); |
|
129 | + } |
|
130 | + break; |
|
131 | + |
|
132 | + case 'ipv6': |
|
133 | + if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV6)) { |
|
134 | + $this->addError($path, 'Invalid IP address', 'format', array('format' => $schema->format)); |
|
135 | + } |
|
136 | + break; |
|
137 | + |
|
138 | + case 'host-name': |
|
139 | + case 'hostname': |
|
140 | + if (!$this->validateHostname($element)) { |
|
141 | + $this->addError($path, 'Invalid hostname', 'format', array('format' => $schema->format)); |
|
142 | + } |
|
143 | + break; |
|
144 | + |
|
145 | + default: |
|
146 | + // Empty as it should be: |
|
147 | + // The value of this keyword is called a format attribute. It MUST be a string. |
|
148 | + // A format attribute can generally only validate a given set of instance types. |
|
149 | + // If the type of the instance to validate is not in this set, validation for |
|
150 | + // this format attribute and instance SHOULD succeed. |
|
151 | + // http://json-schema.org/latest/json-schema-validation.html#anchor105 |
|
152 | + break; |
|
153 | + } |
|
154 | + } |
|
155 | + |
|
156 | + protected function validateDateTime($datetime, $format) |
|
157 | + { |
|
158 | + $dt = \DateTime::createFromFormat($format, $datetime); |
|
159 | + |
|
160 | + if (!$dt) { |
|
161 | + return false; |
|
162 | + } |
|
163 | + |
|
164 | + if ($datetime === $dt->format($format)) { |
|
165 | + return true; |
|
166 | + } |
|
167 | + |
|
168 | + // handles the case where a non-6 digit microsecond datetime is passed |
|
169 | + // which will fail the above string comparison because the passed |
|
170 | + // $datetime may be '2000-05-01T12:12:12.123Z' but format() will return |
|
171 | + // '2000-05-01T12:12:12.123000Z' |
|
172 | + if ((strpos('u', $format) !== -1) && (preg_match('/\.\d+Z$/', $datetime))) { |
|
173 | + return true; |
|
174 | + } |
|
175 | + |
|
176 | + return false; |
|
177 | + } |
|
178 | + |
|
179 | + protected function validateRegex($regex) |
|
180 | + { |
|
181 | + return false !== @preg_match('/' . $regex . '/u', ''); |
|
182 | + } |
|
183 | + |
|
184 | + protected function validateColor($color) |
|
185 | + { |
|
186 | + if (in_array(strtolower($color), array('aqua', 'black', 'blue', 'fuchsia', |
|
187 | + 'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange', 'purple', |
|
188 | + 'red', 'silver', 'teal', 'white', 'yellow'))) { |
|
189 | + return true; |
|
190 | + } |
|
191 | + |
|
192 | + return preg_match('/^#([a-f0-9]{3}|[a-f0-9]{6})$/i', $color); |
|
193 | + } |
|
194 | + |
|
195 | + protected function validateStyle($style) |
|
196 | + { |
|
197 | + $properties = explode(';', rtrim($style, ';')); |
|
198 | + $invalidEntries = preg_grep('/^\s*[-a-z]+\s*:\s*.+$/i', $properties, PREG_GREP_INVERT); |
|
199 | + |
|
200 | + return empty($invalidEntries); |
|
201 | + } |
|
202 | + |
|
203 | + protected function validatePhone($phone) |
|
204 | + { |
|
205 | + return preg_match('/^\+?(\(\d{3}\)|\d{3}) \d{3} \d{4}$/', $phone); |
|
206 | + } |
|
207 | + |
|
208 | + protected function validateHostname($host) |
|
209 | + { |
|
210 | + $hostnameRegex = '/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/i'; |
|
211 | + |
|
212 | + return preg_match($hostnameRegex, $host); |
|
213 | + } |
|
214 | 214 | } |
@@ -19,42 +19,42 @@ |
||
19 | 19 | */ |
20 | 20 | class StringConstraint extends Constraint |
21 | 21 | { |
22 | - /** |
|
23 | - * {@inheritdoc} |
|
24 | - */ |
|
25 | - public function check(&$element, $schema = null, JsonPointer $path = null, $i = null) |
|
26 | - { |
|
27 | - // Verify maxLength |
|
28 | - if (isset($schema->maxLength) && $this->strlen($element) > $schema->maxLength) { |
|
29 | - $this->addError($path, 'Must be at most ' . $schema->maxLength . ' characters long', 'maxLength', array( |
|
30 | - 'maxLength' => $schema->maxLength, |
|
31 | - )); |
|
32 | - } |
|
33 | - |
|
34 | - //verify minLength |
|
35 | - if (isset($schema->minLength) && $this->strlen($element) < $schema->minLength) { |
|
36 | - $this->addError($path, 'Must be at least ' . $schema->minLength . ' characters long', 'minLength', array( |
|
37 | - 'minLength' => $schema->minLength, |
|
38 | - )); |
|
39 | - } |
|
40 | - |
|
41 | - // Verify a regex pattern |
|
42 | - if (isset($schema->pattern) && !preg_match('#' . str_replace('#', '\\#', $schema->pattern) . '#u', $element)) { |
|
43 | - $this->addError($path, 'Does not match the regex pattern ' . $schema->pattern, 'pattern', array( |
|
44 | - 'pattern' => $schema->pattern, |
|
45 | - )); |
|
46 | - } |
|
47 | - |
|
48 | - $this->checkFormat($element, $schema, $path, $i); |
|
49 | - } |
|
50 | - |
|
51 | - private function strlen($string) |
|
52 | - { |
|
53 | - if (extension_loaded('mbstring')) { |
|
54 | - return mb_strlen($string, mb_detect_encoding($string)); |
|
55 | - } |
|
56 | - |
|
57 | - // mbstring is present on all test platforms, so strlen() can be ignored for coverage |
|
58 | - return strlen($string); // @codeCoverageIgnore |
|
59 | - } |
|
22 | + /** |
|
23 | + * {@inheritdoc} |
|
24 | + */ |
|
25 | + public function check(&$element, $schema = null, JsonPointer $path = null, $i = null) |
|
26 | + { |
|
27 | + // Verify maxLength |
|
28 | + if (isset($schema->maxLength) && $this->strlen($element) > $schema->maxLength) { |
|
29 | + $this->addError($path, 'Must be at most ' . $schema->maxLength . ' characters long', 'maxLength', array( |
|
30 | + 'maxLength' => $schema->maxLength, |
|
31 | + )); |
|
32 | + } |
|
33 | + |
|
34 | + //verify minLength |
|
35 | + if (isset($schema->minLength) && $this->strlen($element) < $schema->minLength) { |
|
36 | + $this->addError($path, 'Must be at least ' . $schema->minLength . ' characters long', 'minLength', array( |
|
37 | + 'minLength' => $schema->minLength, |
|
38 | + )); |
|
39 | + } |
|
40 | + |
|
41 | + // Verify a regex pattern |
|
42 | + if (isset($schema->pattern) && !preg_match('#' . str_replace('#', '\\#', $schema->pattern) . '#u', $element)) { |
|
43 | + $this->addError($path, 'Does not match the regex pattern ' . $schema->pattern, 'pattern', array( |
|
44 | + 'pattern' => $schema->pattern, |
|
45 | + )); |
|
46 | + } |
|
47 | + |
|
48 | + $this->checkFormat($element, $schema, $path, $i); |
|
49 | + } |
|
50 | + |
|
51 | + private function strlen($string) |
|
52 | + { |
|
53 | + if (extension_loaded('mbstring')) { |
|
54 | + return mb_strlen($string, mb_detect_encoding($string)); |
|
55 | + } |
|
56 | + |
|
57 | + // mbstring is present on all test platforms, so strlen() can be ignored for coverage |
|
58 | + return strlen($string); // @codeCoverageIgnore |
|
59 | + } |
|
60 | 60 | } |
@@ -23,72 +23,72 @@ |
||
23 | 23 | */ |
24 | 24 | class SchemaConstraint extends Constraint |
25 | 25 | { |
26 | - const DEFAULT_SCHEMA_SPEC = 'http://json-schema.org/draft-04/schema#'; |
|
26 | + const DEFAULT_SCHEMA_SPEC = 'http://json-schema.org/draft-04/schema#'; |
|
27 | 27 | |
28 | - /** |
|
29 | - * {@inheritdoc} |
|
30 | - */ |
|
31 | - public function check(&$element, $schema = null, JsonPointer $path = null, $i = null) |
|
32 | - { |
|
33 | - if ($schema !== null) { |
|
34 | - // passed schema |
|
35 | - $validationSchema = $schema; |
|
36 | - } elseif ($this->getTypeCheck()->propertyExists($element, $this->inlineSchemaProperty)) { |
|
37 | - // inline schema |
|
38 | - $validationSchema = $this->getTypeCheck()->propertyGet($element, $this->inlineSchemaProperty); |
|
39 | - } else { |
|
40 | - throw new InvalidArgumentException('no schema found to verify against'); |
|
41 | - } |
|
28 | + /** |
|
29 | + * {@inheritdoc} |
|
30 | + */ |
|
31 | + public function check(&$element, $schema = null, JsonPointer $path = null, $i = null) |
|
32 | + { |
|
33 | + if ($schema !== null) { |
|
34 | + // passed schema |
|
35 | + $validationSchema = $schema; |
|
36 | + } elseif ($this->getTypeCheck()->propertyExists($element, $this->inlineSchemaProperty)) { |
|
37 | + // inline schema |
|
38 | + $validationSchema = $this->getTypeCheck()->propertyGet($element, $this->inlineSchemaProperty); |
|
39 | + } else { |
|
40 | + throw new InvalidArgumentException('no schema found to verify against'); |
|
41 | + } |
|
42 | 42 | |
43 | - // cast array schemas to object |
|
44 | - if (is_array($validationSchema)) { |
|
45 | - $validationSchema = BaseConstraint::arrayToObjectRecursive($validationSchema); |
|
46 | - } |
|
43 | + // cast array schemas to object |
|
44 | + if (is_array($validationSchema)) { |
|
45 | + $validationSchema = BaseConstraint::arrayToObjectRecursive($validationSchema); |
|
46 | + } |
|
47 | 47 | |
48 | - // validate schema against whatever is defined in $validationSchema->$schema. If no |
|
49 | - // schema is defined, assume self::DEFAULT_SCHEMA_SPEC (currently draft-04). |
|
50 | - if ($this->factory->getConfig(self::CHECK_MODE_VALIDATE_SCHEMA)) { |
|
51 | - if (!$this->getTypeCheck()->isObject($validationSchema)) { |
|
52 | - throw new RuntimeException('Cannot validate the schema of a non-object'); |
|
53 | - } |
|
54 | - if ($this->getTypeCheck()->propertyExists($validationSchema, '$schema')) { |
|
55 | - $schemaSpec = $this->getTypeCheck()->propertyGet($validationSchema, '$schema'); |
|
56 | - } else { |
|
57 | - $schemaSpec = self::DEFAULT_SCHEMA_SPEC; |
|
58 | - } |
|
48 | + // validate schema against whatever is defined in $validationSchema->$schema. If no |
|
49 | + // schema is defined, assume self::DEFAULT_SCHEMA_SPEC (currently draft-04). |
|
50 | + if ($this->factory->getConfig(self::CHECK_MODE_VALIDATE_SCHEMA)) { |
|
51 | + if (!$this->getTypeCheck()->isObject($validationSchema)) { |
|
52 | + throw new RuntimeException('Cannot validate the schema of a non-object'); |
|
53 | + } |
|
54 | + if ($this->getTypeCheck()->propertyExists($validationSchema, '$schema')) { |
|
55 | + $schemaSpec = $this->getTypeCheck()->propertyGet($validationSchema, '$schema'); |
|
56 | + } else { |
|
57 | + $schemaSpec = self::DEFAULT_SCHEMA_SPEC; |
|
58 | + } |
|
59 | 59 | |
60 | - // get the spec schema |
|
61 | - $schemaStorage = $this->factory->getSchemaStorage(); |
|
62 | - if (!$this->getTypeCheck()->isObject($schemaSpec)) { |
|
63 | - $schemaSpec = $schemaStorage->getSchema($schemaSpec); |
|
64 | - } |
|
60 | + // get the spec schema |
|
61 | + $schemaStorage = $this->factory->getSchemaStorage(); |
|
62 | + if (!$this->getTypeCheck()->isObject($schemaSpec)) { |
|
63 | + $schemaSpec = $schemaStorage->getSchema($schemaSpec); |
|
64 | + } |
|
65 | 65 | |
66 | - // save error count, config & subtract CHECK_MODE_VALIDATE_SCHEMA |
|
67 | - $initialErrorCount = $this->numErrors(); |
|
68 | - $initialConfig = $this->factory->getConfig(); |
|
69 | - $initialContext = $this->factory->getErrorContext(); |
|
70 | - $this->factory->removeConfig(self::CHECK_MODE_VALIDATE_SCHEMA | self::CHECK_MODE_APPLY_DEFAULTS); |
|
71 | - $this->factory->addConfig(self::CHECK_MODE_TYPE_CAST); |
|
72 | - $this->factory->setErrorContext(Validator::ERROR_SCHEMA_VALIDATION); |
|
66 | + // save error count, config & subtract CHECK_MODE_VALIDATE_SCHEMA |
|
67 | + $initialErrorCount = $this->numErrors(); |
|
68 | + $initialConfig = $this->factory->getConfig(); |
|
69 | + $initialContext = $this->factory->getErrorContext(); |
|
70 | + $this->factory->removeConfig(self::CHECK_MODE_VALIDATE_SCHEMA | self::CHECK_MODE_APPLY_DEFAULTS); |
|
71 | + $this->factory->addConfig(self::CHECK_MODE_TYPE_CAST); |
|
72 | + $this->factory->setErrorContext(Validator::ERROR_SCHEMA_VALIDATION); |
|
73 | 73 | |
74 | - // validate schema |
|
75 | - try { |
|
76 | - $this->check($validationSchema, $schemaSpec); |
|
77 | - } catch (\Exception $e) { |
|
78 | - if ($this->factory->getConfig(self::CHECK_MODE_EXCEPTIONS)) { |
|
79 | - throw new InvalidSchemaException('Schema did not pass validation', 0, $e); |
|
80 | - } |
|
81 | - } |
|
82 | - if ($this->numErrors() > $initialErrorCount) { |
|
83 | - $this->addError($path, 'Schema is not valid', 'schema'); |
|
84 | - } |
|
74 | + // validate schema |
|
75 | + try { |
|
76 | + $this->check($validationSchema, $schemaSpec); |
|
77 | + } catch (\Exception $e) { |
|
78 | + if ($this->factory->getConfig(self::CHECK_MODE_EXCEPTIONS)) { |
|
79 | + throw new InvalidSchemaException('Schema did not pass validation', 0, $e); |
|
80 | + } |
|
81 | + } |
|
82 | + if ($this->numErrors() > $initialErrorCount) { |
|
83 | + $this->addError($path, 'Schema is not valid', 'schema'); |
|
84 | + } |
|
85 | 85 | |
86 | - // restore the initial config |
|
87 | - $this->factory->setConfig($initialConfig); |
|
88 | - $this->factory->setErrorContext($initialContext); |
|
89 | - } |
|
86 | + // restore the initial config |
|
87 | + $this->factory->setConfig($initialConfig); |
|
88 | + $this->factory->setErrorContext($initialContext); |
|
89 | + } |
|
90 | 90 | |
91 | - // validate element against $validationSchema |
|
92 | - $this->checkUndefined($element, $validationSchema, $path, $i); |
|
93 | - } |
|
91 | + // validate element against $validationSchema |
|
92 | + $this->checkUndefined($element, $validationSchema, $path, $i); |
|
93 | + } |
|
94 | 94 | } |
@@ -19,174 +19,174 @@ |
||
19 | 19 | */ |
20 | 20 | class ObjectConstraint extends Constraint |
21 | 21 | { |
22 | - /** |
|
23 | - * @var array List of properties to which a default value has been applied |
|
24 | - */ |
|
25 | - protected $appliedDefaults = array(); |
|
26 | - |
|
27 | - /** |
|
28 | - * {@inheritdoc} |
|
29 | - */ |
|
30 | - public function check(&$element, $schema = null, JsonPointer $path = null, $properties = null, |
|
31 | - $additionalProp = null, $patternProperties = null, $appliedDefaults = array()) |
|
32 | - { |
|
33 | - if ($element instanceof UndefinedConstraint) { |
|
34 | - return; |
|
35 | - } |
|
36 | - |
|
37 | - $this->appliedDefaults = $appliedDefaults; |
|
38 | - |
|
39 | - $matches = array(); |
|
40 | - if ($patternProperties) { |
|
41 | - // validate the element pattern properties |
|
42 | - $matches = $this->validatePatternProperties($element, $path, $patternProperties); |
|
43 | - } |
|
44 | - |
|
45 | - if ($properties) { |
|
46 | - // validate the element properties |
|
47 | - $this->validateProperties($element, $properties, $path); |
|
48 | - } |
|
49 | - |
|
50 | - // validate additional element properties & constraints |
|
51 | - $this->validateElement($element, $matches, $schema, $path, $properties, $additionalProp); |
|
52 | - } |
|
53 | - |
|
54 | - public function validatePatternProperties($element, JsonPointer $path = null, $patternProperties) |
|
55 | - { |
|
56 | - $try = array('/', '#', '+', '~', '%'); |
|
57 | - $matches = array(); |
|
58 | - foreach ($patternProperties as $pregex => $schema) { |
|
59 | - $delimiter = '/'; |
|
60 | - // Choose delimiter. Necessary for patterns like ^/ , otherwise you get error |
|
61 | - foreach ($try as $delimiter) { |
|
62 | - if (strpos($pregex, $delimiter) === false) { // safe to use |
|
63 | - break; |
|
64 | - } |
|
65 | - } |
|
66 | - |
|
67 | - // Validate the pattern before using it to test for matches |
|
68 | - if (@preg_match($delimiter . $pregex . $delimiter . 'u', '') === false) { |
|
69 | - $this->addError($path, 'The pattern "' . $pregex . '" is invalid', 'pregex', array('pregex' => $pregex)); |
|
70 | - continue; |
|
71 | - } |
|
72 | - foreach ($element as $i => $value) { |
|
73 | - if (preg_match($delimiter . $pregex . $delimiter . 'u', $i)) { |
|
74 | - $matches[] = $i; |
|
75 | - $this->checkUndefined($value, $schema ?: new \stdClass(), $path, $i, in_array($i, $this->appliedDefaults)); |
|
76 | - } |
|
77 | - } |
|
78 | - } |
|
79 | - |
|
80 | - return $matches; |
|
81 | - } |
|
82 | - |
|
83 | - /** |
|
84 | - * Validates the element properties |
|
85 | - * |
|
86 | - * @param \StdClass $element Element to validate |
|
87 | - * @param array $matches Matches from patternProperties (if any) |
|
88 | - * @param \StdClass $schema ObjectConstraint definition |
|
89 | - * @param JsonPointer|null $path Current test path |
|
90 | - * @param \StdClass $properties Properties |
|
91 | - * @param mixed $additionalProp Additional properties |
|
92 | - */ |
|
93 | - public function validateElement($element, $matches, $schema = null, JsonPointer $path = null, |
|
94 | - $properties = null, $additionalProp = null) |
|
95 | - { |
|
96 | - $this->validateMinMaxConstraint($element, $schema, $path); |
|
97 | - |
|
98 | - foreach ($element as $i => $value) { |
|
99 | - $definition = $this->getProperty($properties, $i); |
|
100 | - |
|
101 | - // no additional properties allowed |
|
102 | - if (!in_array($i, $matches) && $additionalProp === false && $this->inlineSchemaProperty !== $i && !$definition) { |
|
103 | - $this->addError($path, 'The property ' . $i . ' is not defined and the definition does not allow additional properties', 'additionalProp'); |
|
104 | - } |
|
105 | - |
|
106 | - // additional properties defined |
|
107 | - if (!in_array($i, $matches) && $additionalProp && !$definition) { |
|
108 | - if ($additionalProp === true) { |
|
109 | - $this->checkUndefined($value, null, $path, $i, in_array($i, $this->appliedDefaults)); |
|
110 | - } else { |
|
111 | - $this->checkUndefined($value, $additionalProp, $path, $i, in_array($i, $this->appliedDefaults)); |
|
112 | - } |
|
113 | - } |
|
114 | - |
|
115 | - // property requires presence of another |
|
116 | - $require = $this->getProperty($definition, 'requires'); |
|
117 | - if ($require && !$this->getProperty($element, $require)) { |
|
118 | - $this->addError($path, 'The presence of the property ' . $i . ' requires that ' . $require . ' also be present', 'requires'); |
|
119 | - } |
|
120 | - |
|
121 | - $property = $this->getProperty($element, $i, $this->factory->createInstanceFor('undefined')); |
|
122 | - if (is_object($property)) { |
|
123 | - $this->validateMinMaxConstraint(!($property instanceof UndefinedConstraint) ? $property : $element, $definition, $path); |
|
124 | - } |
|
125 | - } |
|
126 | - } |
|
127 | - |
|
128 | - /** |
|
129 | - * Validates the definition properties |
|
130 | - * |
|
131 | - * @param \stdClass $element Element to validate |
|
132 | - * @param \stdClass $properties Property definitions |
|
133 | - * @param JsonPointer|null $path Path? |
|
134 | - */ |
|
135 | - public function validateProperties(&$element, $properties = null, JsonPointer $path = null) |
|
136 | - { |
|
137 | - $undefinedConstraint = $this->factory->createInstanceFor('undefined'); |
|
138 | - |
|
139 | - foreach ($properties as $i => $value) { |
|
140 | - $property = &$this->getProperty($element, $i, $undefinedConstraint); |
|
141 | - $definition = $this->getProperty($properties, $i); |
|
142 | - |
|
143 | - if (is_object($definition)) { |
|
144 | - // Undefined constraint will check for is_object() and quit if is not - so why pass it? |
|
145 | - $this->checkUndefined($property, $definition, $path, $i, in_array($i, $this->appliedDefaults)); |
|
146 | - } |
|
147 | - } |
|
148 | - } |
|
149 | - |
|
150 | - /** |
|
151 | - * retrieves a property from an object or array |
|
152 | - * |
|
153 | - * @param mixed $element Element to validate |
|
154 | - * @param string $property Property to retrieve |
|
155 | - * @param mixed $fallback Default value if property is not found |
|
156 | - * |
|
157 | - * @return mixed |
|
158 | - */ |
|
159 | - protected function &getProperty(&$element, $property, $fallback = null) |
|
160 | - { |
|
161 | - if (is_array($element) && (isset($element[$property]) || array_key_exists($property, $element)) /*$this->checkMode == self::CHECK_MODE_TYPE_CAST*/) { |
|
162 | - return $element[$property]; |
|
163 | - } elseif (is_object($element) && property_exists($element, $property)) { |
|
164 | - return $element->$property; |
|
165 | - } |
|
166 | - |
|
167 | - return $fallback; |
|
168 | - } |
|
169 | - |
|
170 | - /** |
|
171 | - * validating minimum and maximum property constraints (if present) against an element |
|
172 | - * |
|
173 | - * @param \stdClass $element Element to validate |
|
174 | - * @param \stdClass $objectDefinition ObjectConstraint definition |
|
175 | - * @param JsonPointer|null $path Path to test? |
|
176 | - */ |
|
177 | - protected function validateMinMaxConstraint($element, $objectDefinition, JsonPointer $path = null) |
|
178 | - { |
|
179 | - // Verify minimum number of properties |
|
180 | - if (isset($objectDefinition->minProperties) && !is_object($objectDefinition->minProperties)) { |
|
181 | - if ($this->getTypeCheck()->propertyCount($element) < $objectDefinition->minProperties) { |
|
182 | - $this->addError($path, 'Must contain a minimum of ' . $objectDefinition->minProperties . ' properties', 'minProperties', array('minProperties' => $objectDefinition->minProperties)); |
|
183 | - } |
|
184 | - } |
|
185 | - // Verify maximum number of properties |
|
186 | - if (isset($objectDefinition->maxProperties) && !is_object($objectDefinition->maxProperties)) { |
|
187 | - if ($this->getTypeCheck()->propertyCount($element) > $objectDefinition->maxProperties) { |
|
188 | - $this->addError($path, 'Must contain no more than ' . $objectDefinition->maxProperties . ' properties', 'maxProperties', array('maxProperties' => $objectDefinition->maxProperties)); |
|
189 | - } |
|
190 | - } |
|
191 | - } |
|
22 | + /** |
|
23 | + * @var array List of properties to which a default value has been applied |
|
24 | + */ |
|
25 | + protected $appliedDefaults = array(); |
|
26 | + |
|
27 | + /** |
|
28 | + * {@inheritdoc} |
|
29 | + */ |
|
30 | + public function check(&$element, $schema = null, JsonPointer $path = null, $properties = null, |
|
31 | + $additionalProp = null, $patternProperties = null, $appliedDefaults = array()) |
|
32 | + { |
|
33 | + if ($element instanceof UndefinedConstraint) { |
|
34 | + return; |
|
35 | + } |
|
36 | + |
|
37 | + $this->appliedDefaults = $appliedDefaults; |
|
38 | + |
|
39 | + $matches = array(); |
|
40 | + if ($patternProperties) { |
|
41 | + // validate the element pattern properties |
|
42 | + $matches = $this->validatePatternProperties($element, $path, $patternProperties); |
|
43 | + } |
|
44 | + |
|
45 | + if ($properties) { |
|
46 | + // validate the element properties |
|
47 | + $this->validateProperties($element, $properties, $path); |
|
48 | + } |
|
49 | + |
|
50 | + // validate additional element properties & constraints |
|
51 | + $this->validateElement($element, $matches, $schema, $path, $properties, $additionalProp); |
|
52 | + } |
|
53 | + |
|
54 | + public function validatePatternProperties($element, JsonPointer $path = null, $patternProperties) |
|
55 | + { |
|
56 | + $try = array('/', '#', '+', '~', '%'); |
|
57 | + $matches = array(); |
|
58 | + foreach ($patternProperties as $pregex => $schema) { |
|
59 | + $delimiter = '/'; |
|
60 | + // Choose delimiter. Necessary for patterns like ^/ , otherwise you get error |
|
61 | + foreach ($try as $delimiter) { |
|
62 | + if (strpos($pregex, $delimiter) === false) { // safe to use |
|
63 | + break; |
|
64 | + } |
|
65 | + } |
|
66 | + |
|
67 | + // Validate the pattern before using it to test for matches |
|
68 | + if (@preg_match($delimiter . $pregex . $delimiter . 'u', '') === false) { |
|
69 | + $this->addError($path, 'The pattern "' . $pregex . '" is invalid', 'pregex', array('pregex' => $pregex)); |
|
70 | + continue; |
|
71 | + } |
|
72 | + foreach ($element as $i => $value) { |
|
73 | + if (preg_match($delimiter . $pregex . $delimiter . 'u', $i)) { |
|
74 | + $matches[] = $i; |
|
75 | + $this->checkUndefined($value, $schema ?: new \stdClass(), $path, $i, in_array($i, $this->appliedDefaults)); |
|
76 | + } |
|
77 | + } |
|
78 | + } |
|
79 | + |
|
80 | + return $matches; |
|
81 | + } |
|
82 | + |
|
83 | + /** |
|
84 | + * Validates the element properties |
|
85 | + * |
|
86 | + * @param \StdClass $element Element to validate |
|
87 | + * @param array $matches Matches from patternProperties (if any) |
|
88 | + * @param \StdClass $schema ObjectConstraint definition |
|
89 | + * @param JsonPointer|null $path Current test path |
|
90 | + * @param \StdClass $properties Properties |
|
91 | + * @param mixed $additionalProp Additional properties |
|
92 | + */ |
|
93 | + public function validateElement($element, $matches, $schema = null, JsonPointer $path = null, |
|
94 | + $properties = null, $additionalProp = null) |
|
95 | + { |
|
96 | + $this->validateMinMaxConstraint($element, $schema, $path); |
|
97 | + |
|
98 | + foreach ($element as $i => $value) { |
|
99 | + $definition = $this->getProperty($properties, $i); |
|
100 | + |
|
101 | + // no additional properties allowed |
|
102 | + if (!in_array($i, $matches) && $additionalProp === false && $this->inlineSchemaProperty !== $i && !$definition) { |
|
103 | + $this->addError($path, 'The property ' . $i . ' is not defined and the definition does not allow additional properties', 'additionalProp'); |
|
104 | + } |
|
105 | + |
|
106 | + // additional properties defined |
|
107 | + if (!in_array($i, $matches) && $additionalProp && !$definition) { |
|
108 | + if ($additionalProp === true) { |
|
109 | + $this->checkUndefined($value, null, $path, $i, in_array($i, $this->appliedDefaults)); |
|
110 | + } else { |
|
111 | + $this->checkUndefined($value, $additionalProp, $path, $i, in_array($i, $this->appliedDefaults)); |
|
112 | + } |
|
113 | + } |
|
114 | + |
|
115 | + // property requires presence of another |
|
116 | + $require = $this->getProperty($definition, 'requires'); |
|
117 | + if ($require && !$this->getProperty($element, $require)) { |
|
118 | + $this->addError($path, 'The presence of the property ' . $i . ' requires that ' . $require . ' also be present', 'requires'); |
|
119 | + } |
|
120 | + |
|
121 | + $property = $this->getProperty($element, $i, $this->factory->createInstanceFor('undefined')); |
|
122 | + if (is_object($property)) { |
|
123 | + $this->validateMinMaxConstraint(!($property instanceof UndefinedConstraint) ? $property : $element, $definition, $path); |
|
124 | + } |
|
125 | + } |
|
126 | + } |
|
127 | + |
|
128 | + /** |
|
129 | + * Validates the definition properties |
|
130 | + * |
|
131 | + * @param \stdClass $element Element to validate |
|
132 | + * @param \stdClass $properties Property definitions |
|
133 | + * @param JsonPointer|null $path Path? |
|
134 | + */ |
|
135 | + public function validateProperties(&$element, $properties = null, JsonPointer $path = null) |
|
136 | + { |
|
137 | + $undefinedConstraint = $this->factory->createInstanceFor('undefined'); |
|
138 | + |
|
139 | + foreach ($properties as $i => $value) { |
|
140 | + $property = &$this->getProperty($element, $i, $undefinedConstraint); |
|
141 | + $definition = $this->getProperty($properties, $i); |
|
142 | + |
|
143 | + if (is_object($definition)) { |
|
144 | + // Undefined constraint will check for is_object() and quit if is not - so why pass it? |
|
145 | + $this->checkUndefined($property, $definition, $path, $i, in_array($i, $this->appliedDefaults)); |
|
146 | + } |
|
147 | + } |
|
148 | + } |
|
149 | + |
|
150 | + /** |
|
151 | + * retrieves a property from an object or array |
|
152 | + * |
|
153 | + * @param mixed $element Element to validate |
|
154 | + * @param string $property Property to retrieve |
|
155 | + * @param mixed $fallback Default value if property is not found |
|
156 | + * |
|
157 | + * @return mixed |
|
158 | + */ |
|
159 | + protected function &getProperty(&$element, $property, $fallback = null) |
|
160 | + { |
|
161 | + if (is_array($element) && (isset($element[$property]) || array_key_exists($property, $element)) /*$this->checkMode == self::CHECK_MODE_TYPE_CAST*/) { |
|
162 | + return $element[$property]; |
|
163 | + } elseif (is_object($element) && property_exists($element, $property)) { |
|
164 | + return $element->$property; |
|
165 | + } |
|
166 | + |
|
167 | + return $fallback; |
|
168 | + } |
|
169 | + |
|
170 | + /** |
|
171 | + * validating minimum and maximum property constraints (if present) against an element |
|
172 | + * |
|
173 | + * @param \stdClass $element Element to validate |
|
174 | + * @param \stdClass $objectDefinition ObjectConstraint definition |
|
175 | + * @param JsonPointer|null $path Path to test? |
|
176 | + */ |
|
177 | + protected function validateMinMaxConstraint($element, $objectDefinition, JsonPointer $path = null) |
|
178 | + { |
|
179 | + // Verify minimum number of properties |
|
180 | + if (isset($objectDefinition->minProperties) && !is_object($objectDefinition->minProperties)) { |
|
181 | + if ($this->getTypeCheck()->propertyCount($element) < $objectDefinition->minProperties) { |
|
182 | + $this->addError($path, 'Must contain a minimum of ' . $objectDefinition->minProperties . ' properties', 'minProperties', array('minProperties' => $objectDefinition->minProperties)); |
|
183 | + } |
|
184 | + } |
|
185 | + // Verify maximum number of properties |
|
186 | + if (isset($objectDefinition->maxProperties) && !is_object($objectDefinition->maxProperties)) { |
|
187 | + if ($this->getTypeCheck()->propertyCount($element) > $objectDefinition->maxProperties) { |
|
188 | + $this->addError($path, 'Must contain no more than ' . $objectDefinition->maxProperties . ' properties', 'maxProperties', array('maxProperties' => $objectDefinition->maxProperties)); |
|
189 | + } |
|
190 | + } |
|
191 | + } |
|
192 | 192 | } |
@@ -20,129 +20,129 @@ |
||
20 | 20 | */ |
21 | 21 | class BaseConstraint |
22 | 22 | { |
23 | - /** |
|
24 | - * @var array Errors |
|
25 | - */ |
|
26 | - protected $errors = array(); |
|
27 | - |
|
28 | - /** |
|
29 | - * @var int All error types which have occurred |
|
30 | - */ |
|
31 | - protected $errorMask = Validator::ERROR_NONE; |
|
32 | - |
|
33 | - /** |
|
34 | - * @var Factory |
|
35 | - */ |
|
36 | - protected $factory; |
|
37 | - |
|
38 | - /** |
|
39 | - * @param Factory $factory |
|
40 | - */ |
|
41 | - public function __construct(Factory $factory = null) |
|
42 | - { |
|
43 | - $this->factory = $factory ?: new Factory(); |
|
44 | - } |
|
45 | - |
|
46 | - public function addError(JsonPointer $path = null, $message, $constraint = '', array $more = null) |
|
47 | - { |
|
48 | - $error = array( |
|
49 | - 'property' => $this->convertJsonPointerIntoPropertyPath($path ?: new JsonPointer('')), |
|
50 | - 'pointer' => ltrim(strval($path ?: new JsonPointer('')), '#'), |
|
51 | - 'message' => $message, |
|
52 | - 'constraint' => $constraint, |
|
53 | - 'context' => $this->factory->getErrorContext(), |
|
54 | - ); |
|
55 | - |
|
56 | - if ($this->factory->getConfig(Constraint::CHECK_MODE_EXCEPTIONS)) { |
|
57 | - throw new ValidationException(sprintf('Error validating %s: %s', $error['pointer'], $error['message'])); |
|
58 | - } |
|
59 | - |
|
60 | - if (is_array($more) && count($more) > 0) { |
|
61 | - $error += $more; |
|
62 | - } |
|
63 | - |
|
64 | - $this->errors[] = $error; |
|
65 | - $this->errorMask |= $error['context']; |
|
66 | - } |
|
67 | - |
|
68 | - public function addErrors(array $errors) |
|
69 | - { |
|
70 | - if ($errors) { |
|
71 | - $this->errors = array_merge($this->errors, $errors); |
|
72 | - $errorMask = &$this->errorMask; |
|
73 | - array_walk($errors, function ($error) use (&$errorMask) { |
|
74 | - if (isset($error['context'])) { |
|
75 | - $errorMask |= $error['context']; |
|
76 | - } |
|
77 | - }); |
|
78 | - } |
|
79 | - } |
|
80 | - |
|
81 | - public function getErrors($errorContext = Validator::ERROR_ALL) |
|
82 | - { |
|
83 | - if ($errorContext === Validator::ERROR_ALL) { |
|
84 | - return $this->errors; |
|
85 | - } |
|
86 | - |
|
87 | - return array_filter($this->errors, function ($error) use ($errorContext) { |
|
88 | - if ($errorContext & $error['context']) { |
|
89 | - return true; |
|
90 | - } |
|
91 | - }); |
|
92 | - } |
|
93 | - |
|
94 | - public function numErrors($errorContext = Validator::ERROR_ALL) |
|
95 | - { |
|
96 | - if ($errorContext === Validator::ERROR_ALL) { |
|
97 | - return count($this->errors); |
|
98 | - } |
|
99 | - |
|
100 | - return count($this->getErrors($errorContext)); |
|
101 | - } |
|
102 | - |
|
103 | - public function isValid() |
|
104 | - { |
|
105 | - return !$this->getErrors(); |
|
106 | - } |
|
107 | - |
|
108 | - /** |
|
109 | - * Clears any reported errors. Should be used between |
|
110 | - * multiple validation checks. |
|
111 | - */ |
|
112 | - public function reset() |
|
113 | - { |
|
114 | - $this->errors = array(); |
|
115 | - $this->errorMask = Validator::ERROR_NONE; |
|
116 | - } |
|
117 | - |
|
118 | - /** |
|
119 | - * Get the error mask |
|
120 | - * |
|
121 | - * @return int |
|
122 | - */ |
|
123 | - public function getErrorMask() |
|
124 | - { |
|
125 | - return $this->errorMask; |
|
126 | - } |
|
127 | - |
|
128 | - /** |
|
129 | - * Recursively cast an associative array to an object |
|
130 | - * |
|
131 | - * @param array $array |
|
132 | - * |
|
133 | - * @return object |
|
134 | - */ |
|
135 | - public static function arrayToObjectRecursive($array) |
|
136 | - { |
|
137 | - $json = json_encode($array); |
|
138 | - if (json_last_error() !== \JSON_ERROR_NONE) { |
|
139 | - $message = 'Unable to encode schema array as JSON'; |
|
140 | - if (function_exists('json_last_error_msg')) { |
|
141 | - $message .= ': ' . json_last_error_msg(); |
|
142 | - } |
|
143 | - throw new InvalidArgumentException($message); |
|
144 | - } |
|
145 | - |
|
146 | - return (object) json_decode($json); |
|
147 | - } |
|
23 | + /** |
|
24 | + * @var array Errors |
|
25 | + */ |
|
26 | + protected $errors = array(); |
|
27 | + |
|
28 | + /** |
|
29 | + * @var int All error types which have occurred |
|
30 | + */ |
|
31 | + protected $errorMask = Validator::ERROR_NONE; |
|
32 | + |
|
33 | + /** |
|
34 | + * @var Factory |
|
35 | + */ |
|
36 | + protected $factory; |
|
37 | + |
|
38 | + /** |
|
39 | + * @param Factory $factory |
|
40 | + */ |
|
41 | + public function __construct(Factory $factory = null) |
|
42 | + { |
|
43 | + $this->factory = $factory ?: new Factory(); |
|
44 | + } |
|
45 | + |
|
46 | + public function addError(JsonPointer $path = null, $message, $constraint = '', array $more = null) |
|
47 | + { |
|
48 | + $error = array( |
|
49 | + 'property' => $this->convertJsonPointerIntoPropertyPath($path ?: new JsonPointer('')), |
|
50 | + 'pointer' => ltrim(strval($path ?: new JsonPointer('')), '#'), |
|
51 | + 'message' => $message, |
|
52 | + 'constraint' => $constraint, |
|
53 | + 'context' => $this->factory->getErrorContext(), |
|
54 | + ); |
|
55 | + |
|
56 | + if ($this->factory->getConfig(Constraint::CHECK_MODE_EXCEPTIONS)) { |
|
57 | + throw new ValidationException(sprintf('Error validating %s: %s', $error['pointer'], $error['message'])); |
|
58 | + } |
|
59 | + |
|
60 | + if (is_array($more) && count($more) > 0) { |
|
61 | + $error += $more; |
|
62 | + } |
|
63 | + |
|
64 | + $this->errors[] = $error; |
|
65 | + $this->errorMask |= $error['context']; |
|
66 | + } |
|
67 | + |
|
68 | + public function addErrors(array $errors) |
|
69 | + { |
|
70 | + if ($errors) { |
|
71 | + $this->errors = array_merge($this->errors, $errors); |
|
72 | + $errorMask = &$this->errorMask; |
|
73 | + array_walk($errors, function ($error) use (&$errorMask) { |
|
74 | + if (isset($error['context'])) { |
|
75 | + $errorMask |= $error['context']; |
|
76 | + } |
|
77 | + }); |
|
78 | + } |
|
79 | + } |
|
80 | + |
|
81 | + public function getErrors($errorContext = Validator::ERROR_ALL) |
|
82 | + { |
|
83 | + if ($errorContext === Validator::ERROR_ALL) { |
|
84 | + return $this->errors; |
|
85 | + } |
|
86 | + |
|
87 | + return array_filter($this->errors, function ($error) use ($errorContext) { |
|
88 | + if ($errorContext & $error['context']) { |
|
89 | + return true; |
|
90 | + } |
|
91 | + }); |
|
92 | + } |
|
93 | + |
|
94 | + public function numErrors($errorContext = Validator::ERROR_ALL) |
|
95 | + { |
|
96 | + if ($errorContext === Validator::ERROR_ALL) { |
|
97 | + return count($this->errors); |
|
98 | + } |
|
99 | + |
|
100 | + return count($this->getErrors($errorContext)); |
|
101 | + } |
|
102 | + |
|
103 | + public function isValid() |
|
104 | + { |
|
105 | + return !$this->getErrors(); |
|
106 | + } |
|
107 | + |
|
108 | + /** |
|
109 | + * Clears any reported errors. Should be used between |
|
110 | + * multiple validation checks. |
|
111 | + */ |
|
112 | + public function reset() |
|
113 | + { |
|
114 | + $this->errors = array(); |
|
115 | + $this->errorMask = Validator::ERROR_NONE; |
|
116 | + } |
|
117 | + |
|
118 | + /** |
|
119 | + * Get the error mask |
|
120 | + * |
|
121 | + * @return int |
|
122 | + */ |
|
123 | + public function getErrorMask() |
|
124 | + { |
|
125 | + return $this->errorMask; |
|
126 | + } |
|
127 | + |
|
128 | + /** |
|
129 | + * Recursively cast an associative array to an object |
|
130 | + * |
|
131 | + * @param array $array |
|
132 | + * |
|
133 | + * @return object |
|
134 | + */ |
|
135 | + public static function arrayToObjectRecursive($array) |
|
136 | + { |
|
137 | + $json = json_encode($array); |
|
138 | + if (json_last_error() !== \JSON_ERROR_NONE) { |
|
139 | + $message = 'Unable to encode schema array as JSON'; |
|
140 | + if (function_exists('json_last_error_msg')) { |
|
141 | + $message .= ': ' . json_last_error_msg(); |
|
142 | + } |
|
143 | + throw new InvalidArgumentException($message); |
|
144 | + } |
|
145 | + |
|
146 | + return (object) json_decode($json); |
|
147 | + } |
|
148 | 148 | } |
@@ -18,48 +18,48 @@ |
||
18 | 18 | */ |
19 | 19 | interface ConstraintInterface |
20 | 20 | { |
21 | - /** |
|
22 | - * returns all collected errors |
|
23 | - * |
|
24 | - * @return array |
|
25 | - */ |
|
26 | - public function getErrors(); |
|
21 | + /** |
|
22 | + * returns all collected errors |
|
23 | + * |
|
24 | + * @return array |
|
25 | + */ |
|
26 | + public function getErrors(); |
|
27 | 27 | |
28 | - /** |
|
29 | - * adds errors to this validator |
|
30 | - * |
|
31 | - * @param array $errors |
|
32 | - */ |
|
33 | - public function addErrors(array $errors); |
|
28 | + /** |
|
29 | + * adds errors to this validator |
|
30 | + * |
|
31 | + * @param array $errors |
|
32 | + */ |
|
33 | + public function addErrors(array $errors); |
|
34 | 34 | |
35 | - /** |
|
36 | - * adds an error |
|
37 | - * |
|
38 | - * @param JsonPointer|null $path |
|
39 | - * @param string $message |
|
40 | - * @param string $constraint the constraint/rule that is broken, e.g.: 'minLength' |
|
41 | - * @param array $more more array elements to add to the error |
|
42 | - */ |
|
43 | - public function addError(JsonPointer $path = null, $message, $constraint='', array $more = null); |
|
35 | + /** |
|
36 | + * adds an error |
|
37 | + * |
|
38 | + * @param JsonPointer|null $path |
|
39 | + * @param string $message |
|
40 | + * @param string $constraint the constraint/rule that is broken, e.g.: 'minLength' |
|
41 | + * @param array $more more array elements to add to the error |
|
42 | + */ |
|
43 | + public function addError(JsonPointer $path = null, $message, $constraint='', array $more = null); |
|
44 | 44 | |
45 | - /** |
|
46 | - * checks if the validator has not raised errors |
|
47 | - * |
|
48 | - * @return bool |
|
49 | - */ |
|
50 | - public function isValid(); |
|
45 | + /** |
|
46 | + * checks if the validator has not raised errors |
|
47 | + * |
|
48 | + * @return bool |
|
49 | + */ |
|
50 | + public function isValid(); |
|
51 | 51 | |
52 | - /** |
|
53 | - * invokes the validation of an element |
|
54 | - * |
|
55 | - * @abstract |
|
56 | - * |
|
57 | - * @param mixed $value |
|
58 | - * @param mixed $schema |
|
59 | - * @param JsonPointer|null $path |
|
60 | - * @param mixed $i |
|
61 | - * |
|
62 | - * @throws \JsonSchema\Exception\ExceptionInterface |
|
63 | - */ |
|
64 | - public function check(&$value, $schema = null, JsonPointer $path = null, $i = null); |
|
52 | + /** |
|
53 | + * invokes the validation of an element |
|
54 | + * |
|
55 | + * @abstract |
|
56 | + * |
|
57 | + * @param mixed $value |
|
58 | + * @param mixed $schema |
|
59 | + * @param JsonPointer|null $path |
|
60 | + * @param mixed $i |
|
61 | + * |
|
62 | + * @throws \JsonSchema\Exception\ExceptionInterface |
|
63 | + */ |
|
64 | + public function check(&$value, $schema = null, JsonPointer $path = null, $i = null); |
|
65 | 65 | } |
@@ -21,200 +21,200 @@ |
||
21 | 21 | */ |
22 | 22 | class Factory |
23 | 23 | { |
24 | - /** |
|
25 | - * @var SchemaStorage |
|
26 | - */ |
|
27 | - protected $schemaStorage; |
|
28 | - |
|
29 | - /** |
|
30 | - * @var UriRetriever |
|
31 | - */ |
|
32 | - protected $uriRetriever; |
|
33 | - |
|
34 | - /** |
|
35 | - * @var int |
|
36 | - */ |
|
37 | - private $checkMode = Constraint::CHECK_MODE_NORMAL; |
|
38 | - |
|
39 | - /** |
|
40 | - * @var TypeCheck\TypeCheckInterface[] |
|
41 | - */ |
|
42 | - private $typeCheck = array(); |
|
43 | - |
|
44 | - /** |
|
45 | - * @var int Validation context |
|
46 | - */ |
|
47 | - protected $errorContext = Validator::ERROR_DOCUMENT_VALIDATION; |
|
48 | - |
|
49 | - /** |
|
50 | - * @var array |
|
51 | - */ |
|
52 | - protected $constraintMap = array( |
|
53 | - 'array' => 'JsonSchema\Constraints\CollectionConstraint', |
|
54 | - 'collection' => 'JsonSchema\Constraints\CollectionConstraint', |
|
55 | - 'object' => 'JsonSchema\Constraints\ObjectConstraint', |
|
56 | - 'type' => 'JsonSchema\Constraints\TypeConstraint', |
|
57 | - 'undefined' => 'JsonSchema\Constraints\UndefinedConstraint', |
|
58 | - 'string' => 'JsonSchema\Constraints\StringConstraint', |
|
59 | - 'number' => 'JsonSchema\Constraints\NumberConstraint', |
|
60 | - 'enum' => 'JsonSchema\Constraints\EnumConstraint', |
|
61 | - 'format' => 'JsonSchema\Constraints\FormatConstraint', |
|
62 | - 'schema' => 'JsonSchema\Constraints\SchemaConstraint', |
|
63 | - 'validator' => 'JsonSchema\Validator' |
|
64 | - ); |
|
65 | - |
|
66 | - /** |
|
67 | - * @var array<ConstraintInterface> |
|
68 | - */ |
|
69 | - private $instanceCache = array(); |
|
70 | - |
|
71 | - /** |
|
72 | - * @param SchemaStorage $schemaStorage |
|
73 | - * @param UriRetrieverInterface $uriRetriever |
|
74 | - * @param int $checkMode |
|
75 | - */ |
|
76 | - public function __construct( |
|
77 | - SchemaStorageInterface $schemaStorage = null, |
|
78 | - UriRetrieverInterface $uriRetriever = null, |
|
79 | - $checkMode = Constraint::CHECK_MODE_NORMAL |
|
80 | - ) { |
|
81 | - // set provided config options |
|
82 | - $this->setConfig($checkMode); |
|
83 | - |
|
84 | - $this->uriRetriever = $uriRetriever ?: new UriRetriever(); |
|
85 | - $this->schemaStorage = $schemaStorage ?: new SchemaStorage($this->uriRetriever); |
|
86 | - } |
|
87 | - |
|
88 | - /** |
|
89 | - * Set config values |
|
90 | - * |
|
91 | - * @param int $checkMode Set checkMode options - does not preserve existing flags |
|
92 | - */ |
|
93 | - public function setConfig($checkMode = Constraint::CHECK_MODE_NORMAL) |
|
94 | - { |
|
95 | - $this->checkMode = $checkMode; |
|
96 | - } |
|
97 | - |
|
98 | - /** |
|
99 | - * Enable checkMode flags |
|
100 | - * |
|
101 | - * @param int $options |
|
102 | - */ |
|
103 | - public function addConfig($options) |
|
104 | - { |
|
105 | - $this->checkMode |= $options; |
|
106 | - } |
|
107 | - |
|
108 | - /** |
|
109 | - * Disable checkMode flags |
|
110 | - * |
|
111 | - * @param int $options |
|
112 | - */ |
|
113 | - public function removeConfig($options) |
|
114 | - { |
|
115 | - $this->checkMode &= ~$options; |
|
116 | - } |
|
117 | - |
|
118 | - /** |
|
119 | - * Get checkMode option |
|
120 | - * |
|
121 | - * @param int $options Options to get, if null then return entire bitmask |
|
122 | - * |
|
123 | - * @return int |
|
124 | - */ |
|
125 | - public function getConfig($options = null) |
|
126 | - { |
|
127 | - if ($options === null) { |
|
128 | - return $this->checkMode; |
|
129 | - } |
|
130 | - |
|
131 | - return $this->checkMode & $options; |
|
132 | - } |
|
133 | - |
|
134 | - /** |
|
135 | - * @return UriRetrieverInterface |
|
136 | - */ |
|
137 | - public function getUriRetriever() |
|
138 | - { |
|
139 | - return $this->uriRetriever; |
|
140 | - } |
|
141 | - |
|
142 | - public function getSchemaStorage() |
|
143 | - { |
|
144 | - return $this->schemaStorage; |
|
145 | - } |
|
146 | - |
|
147 | - public function getTypeCheck() |
|
148 | - { |
|
149 | - if (!isset($this->typeCheck[$this->checkMode])) { |
|
150 | - $this->typeCheck[$this->checkMode] = ($this->checkMode & Constraint::CHECK_MODE_TYPE_CAST) |
|
151 | - ? new TypeCheck\LooseTypeCheck() |
|
152 | - : new TypeCheck\StrictTypeCheck(); |
|
153 | - } |
|
154 | - |
|
155 | - return $this->typeCheck[$this->checkMode]; |
|
156 | - } |
|
157 | - |
|
158 | - /** |
|
159 | - * @param string $name |
|
160 | - * @param string $class |
|
161 | - * |
|
162 | - * @return Factory |
|
163 | - */ |
|
164 | - public function setConstraintClass($name, $class) |
|
165 | - { |
|
166 | - // Ensure class exists |
|
167 | - if (!class_exists($class)) { |
|
168 | - throw new InvalidArgumentException('Unknown constraint ' . $name); |
|
169 | - } |
|
170 | - // Ensure class is appropriate |
|
171 | - if (!in_array('JsonSchema\Constraints\ConstraintInterface', class_implements($class))) { |
|
172 | - throw new InvalidArgumentException('Invalid class ' . $name); |
|
173 | - } |
|
174 | - $this->constraintMap[$name] = $class; |
|
175 | - |
|
176 | - return $this; |
|
177 | - } |
|
178 | - |
|
179 | - /** |
|
180 | - * Create a constraint instance for the given constraint name. |
|
181 | - * |
|
182 | - * @param string $constraintName |
|
183 | - * |
|
184 | - * @throws InvalidArgumentException if is not possible create the constraint instance |
|
185 | - * |
|
186 | - * @return ConstraintInterface|ObjectConstraint |
|
187 | - */ |
|
188 | - public function createInstanceFor($constraintName) |
|
189 | - { |
|
190 | - if (!isset($this->constraintMap[$constraintName])) { |
|
191 | - throw new InvalidArgumentException('Unknown constraint ' . $constraintName); |
|
192 | - } |
|
193 | - |
|
194 | - if (!isset($this->instanceCache[$constraintName])) { |
|
195 | - $this->instanceCache[$constraintName] = new $this->constraintMap[$constraintName]($this); |
|
196 | - } |
|
197 | - |
|
198 | - return clone $this->instanceCache[$constraintName]; |
|
199 | - } |
|
200 | - |
|
201 | - /** |
|
202 | - * Get the error context |
|
203 | - * |
|
204 | - * @return string |
|
205 | - */ |
|
206 | - public function getErrorContext() |
|
207 | - { |
|
208 | - return $this->errorContext; |
|
209 | - } |
|
210 | - |
|
211 | - /** |
|
212 | - * Set the error context |
|
213 | - * |
|
214 | - * @param string $validationContext |
|
215 | - */ |
|
216 | - public function setErrorContext($errorContext) |
|
217 | - { |
|
218 | - $this->errorContext = $errorContext; |
|
219 | - } |
|
24 | + /** |
|
25 | + * @var SchemaStorage |
|
26 | + */ |
|
27 | + protected $schemaStorage; |
|
28 | + |
|
29 | + /** |
|
30 | + * @var UriRetriever |
|
31 | + */ |
|
32 | + protected $uriRetriever; |
|
33 | + |
|
34 | + /** |
|
35 | + * @var int |
|
36 | + */ |
|
37 | + private $checkMode = Constraint::CHECK_MODE_NORMAL; |
|
38 | + |
|
39 | + /** |
|
40 | + * @var TypeCheck\TypeCheckInterface[] |
|
41 | + */ |
|
42 | + private $typeCheck = array(); |
|
43 | + |
|
44 | + /** |
|
45 | + * @var int Validation context |
|
46 | + */ |
|
47 | + protected $errorContext = Validator::ERROR_DOCUMENT_VALIDATION; |
|
48 | + |
|
49 | + /** |
|
50 | + * @var array |
|
51 | + */ |
|
52 | + protected $constraintMap = array( |
|
53 | + 'array' => 'JsonSchema\Constraints\CollectionConstraint', |
|
54 | + 'collection' => 'JsonSchema\Constraints\CollectionConstraint', |
|
55 | + 'object' => 'JsonSchema\Constraints\ObjectConstraint', |
|
56 | + 'type' => 'JsonSchema\Constraints\TypeConstraint', |
|
57 | + 'undefined' => 'JsonSchema\Constraints\UndefinedConstraint', |
|
58 | + 'string' => 'JsonSchema\Constraints\StringConstraint', |
|
59 | + 'number' => 'JsonSchema\Constraints\NumberConstraint', |
|
60 | + 'enum' => 'JsonSchema\Constraints\EnumConstraint', |
|
61 | + 'format' => 'JsonSchema\Constraints\FormatConstraint', |
|
62 | + 'schema' => 'JsonSchema\Constraints\SchemaConstraint', |
|
63 | + 'validator' => 'JsonSchema\Validator' |
|
64 | + ); |
|
65 | + |
|
66 | + /** |
|
67 | + * @var array<ConstraintInterface> |
|
68 | + */ |
|
69 | + private $instanceCache = array(); |
|
70 | + |
|
71 | + /** |
|
72 | + * @param SchemaStorage $schemaStorage |
|
73 | + * @param UriRetrieverInterface $uriRetriever |
|
74 | + * @param int $checkMode |
|
75 | + */ |
|
76 | + public function __construct( |
|
77 | + SchemaStorageInterface $schemaStorage = null, |
|
78 | + UriRetrieverInterface $uriRetriever = null, |
|
79 | + $checkMode = Constraint::CHECK_MODE_NORMAL |
|
80 | + ) { |
|
81 | + // set provided config options |
|
82 | + $this->setConfig($checkMode); |
|
83 | + |
|
84 | + $this->uriRetriever = $uriRetriever ?: new UriRetriever(); |
|
85 | + $this->schemaStorage = $schemaStorage ?: new SchemaStorage($this->uriRetriever); |
|
86 | + } |
|
87 | + |
|
88 | + /** |
|
89 | + * Set config values |
|
90 | + * |
|
91 | + * @param int $checkMode Set checkMode options - does not preserve existing flags |
|
92 | + */ |
|
93 | + public function setConfig($checkMode = Constraint::CHECK_MODE_NORMAL) |
|
94 | + { |
|
95 | + $this->checkMode = $checkMode; |
|
96 | + } |
|
97 | + |
|
98 | + /** |
|
99 | + * Enable checkMode flags |
|
100 | + * |
|
101 | + * @param int $options |
|
102 | + */ |
|
103 | + public function addConfig($options) |
|
104 | + { |
|
105 | + $this->checkMode |= $options; |
|
106 | + } |
|
107 | + |
|
108 | + /** |
|
109 | + * Disable checkMode flags |
|
110 | + * |
|
111 | + * @param int $options |
|
112 | + */ |
|
113 | + public function removeConfig($options) |
|
114 | + { |
|
115 | + $this->checkMode &= ~$options; |
|
116 | + } |
|
117 | + |
|
118 | + /** |
|
119 | + * Get checkMode option |
|
120 | + * |
|
121 | + * @param int $options Options to get, if null then return entire bitmask |
|
122 | + * |
|
123 | + * @return int |
|
124 | + */ |
|
125 | + public function getConfig($options = null) |
|
126 | + { |
|
127 | + if ($options === null) { |
|
128 | + return $this->checkMode; |
|
129 | + } |
|
130 | + |
|
131 | + return $this->checkMode & $options; |
|
132 | + } |
|
133 | + |
|
134 | + /** |
|
135 | + * @return UriRetrieverInterface |
|
136 | + */ |
|
137 | + public function getUriRetriever() |
|
138 | + { |
|
139 | + return $this->uriRetriever; |
|
140 | + } |
|
141 | + |
|
142 | + public function getSchemaStorage() |
|
143 | + { |
|
144 | + return $this->schemaStorage; |
|
145 | + } |
|
146 | + |
|
147 | + public function getTypeCheck() |
|
148 | + { |
|
149 | + if (!isset($this->typeCheck[$this->checkMode])) { |
|
150 | + $this->typeCheck[$this->checkMode] = ($this->checkMode & Constraint::CHECK_MODE_TYPE_CAST) |
|
151 | + ? new TypeCheck\LooseTypeCheck() |
|
152 | + : new TypeCheck\StrictTypeCheck(); |
|
153 | + } |
|
154 | + |
|
155 | + return $this->typeCheck[$this->checkMode]; |
|
156 | + } |
|
157 | + |
|
158 | + /** |
|
159 | + * @param string $name |
|
160 | + * @param string $class |
|
161 | + * |
|
162 | + * @return Factory |
|
163 | + */ |
|
164 | + public function setConstraintClass($name, $class) |
|
165 | + { |
|
166 | + // Ensure class exists |
|
167 | + if (!class_exists($class)) { |
|
168 | + throw new InvalidArgumentException('Unknown constraint ' . $name); |
|
169 | + } |
|
170 | + // Ensure class is appropriate |
|
171 | + if (!in_array('JsonSchema\Constraints\ConstraintInterface', class_implements($class))) { |
|
172 | + throw new InvalidArgumentException('Invalid class ' . $name); |
|
173 | + } |
|
174 | + $this->constraintMap[$name] = $class; |
|
175 | + |
|
176 | + return $this; |
|
177 | + } |
|
178 | + |
|
179 | + /** |
|
180 | + * Create a constraint instance for the given constraint name. |
|
181 | + * |
|
182 | + * @param string $constraintName |
|
183 | + * |
|
184 | + * @throws InvalidArgumentException if is not possible create the constraint instance |
|
185 | + * |
|
186 | + * @return ConstraintInterface|ObjectConstraint |
|
187 | + */ |
|
188 | + public function createInstanceFor($constraintName) |
|
189 | + { |
|
190 | + if (!isset($this->constraintMap[$constraintName])) { |
|
191 | + throw new InvalidArgumentException('Unknown constraint ' . $constraintName); |
|
192 | + } |
|
193 | + |
|
194 | + if (!isset($this->instanceCache[$constraintName])) { |
|
195 | + $this->instanceCache[$constraintName] = new $this->constraintMap[$constraintName]($this); |
|
196 | + } |
|
197 | + |
|
198 | + return clone $this->instanceCache[$constraintName]; |
|
199 | + } |
|
200 | + |
|
201 | + /** |
|
202 | + * Get the error context |
|
203 | + * |
|
204 | + * @return string |
|
205 | + */ |
|
206 | + public function getErrorContext() |
|
207 | + { |
|
208 | + return $this->errorContext; |
|
209 | + } |
|
210 | + |
|
211 | + /** |
|
212 | + * Set the error context |
|
213 | + * |
|
214 | + * @param string $validationContext |
|
215 | + */ |
|
216 | + public function setErrorContext($errorContext) |
|
217 | + { |
|
218 | + $this->errorContext = $errorContext; |
|
219 | + } |
|
220 | 220 | } |
@@ -19,65 +19,65 @@ |
||
19 | 19 | */ |
20 | 20 | class Curl extends AbstractRetriever |
21 | 21 | { |
22 | - protected $messageBody; |
|
22 | + protected $messageBody; |
|
23 | 23 | |
24 | - public function __construct() |
|
25 | - { |
|
26 | - if (!function_exists('curl_init')) { |
|
27 | - // Cannot test this, because curl_init is present on all test platforms plus mock |
|
28 | - throw new RuntimeException('cURL not installed'); // @codeCoverageIgnore |
|
29 | - } |
|
30 | - } |
|
24 | + public function __construct() |
|
25 | + { |
|
26 | + if (!function_exists('curl_init')) { |
|
27 | + // Cannot test this, because curl_init is present on all test platforms plus mock |
|
28 | + throw new RuntimeException('cURL not installed'); // @codeCoverageIgnore |
|
29 | + } |
|
30 | + } |
|
31 | 31 | |
32 | - /** |
|
33 | - * {@inheritdoc} |
|
34 | - * |
|
35 | - * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve() |
|
36 | - */ |
|
37 | - public function retrieve($uri) |
|
38 | - { |
|
39 | - $ch = curl_init(); |
|
32 | + /** |
|
33 | + * {@inheritdoc} |
|
34 | + * |
|
35 | + * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve() |
|
36 | + */ |
|
37 | + public function retrieve($uri) |
|
38 | + { |
|
39 | + $ch = curl_init(); |
|
40 | 40 | |
41 | - curl_setopt($ch, CURLOPT_URL, $uri); |
|
42 | - curl_setopt($ch, CURLOPT_HEADER, true); |
|
43 | - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); |
|
44 | - curl_setopt($ch, CURLOPT_HTTPHEADER, array('Accept: ' . Validator::SCHEMA_MEDIA_TYPE)); |
|
41 | + curl_setopt($ch, CURLOPT_URL, $uri); |
|
42 | + curl_setopt($ch, CURLOPT_HEADER, true); |
|
43 | + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); |
|
44 | + curl_setopt($ch, CURLOPT_HTTPHEADER, array('Accept: ' . Validator::SCHEMA_MEDIA_TYPE)); |
|
45 | 45 | |
46 | - $response = curl_exec($ch); |
|
47 | - if (false === $response) { |
|
48 | - throw new \JsonSchema\Exception\ResourceNotFoundException('JSON schema not found'); |
|
49 | - } |
|
46 | + $response = curl_exec($ch); |
|
47 | + if (false === $response) { |
|
48 | + throw new \JsonSchema\Exception\ResourceNotFoundException('JSON schema not found'); |
|
49 | + } |
|
50 | 50 | |
51 | - $this->fetchMessageBody($response); |
|
52 | - $this->fetchContentType($response); |
|
51 | + $this->fetchMessageBody($response); |
|
52 | + $this->fetchContentType($response); |
|
53 | 53 | |
54 | - curl_close($ch); |
|
54 | + curl_close($ch); |
|
55 | 55 | |
56 | - return $this->messageBody; |
|
57 | - } |
|
56 | + return $this->messageBody; |
|
57 | + } |
|
58 | 58 | |
59 | - /** |
|
60 | - * @param string $response cURL HTTP response |
|
61 | - */ |
|
62 | - private function fetchMessageBody($response) |
|
63 | - { |
|
64 | - preg_match("/(?:\r\n){2}(.*)$/ms", $response, $match); |
|
65 | - $this->messageBody = $match[1]; |
|
66 | - } |
|
59 | + /** |
|
60 | + * @param string $response cURL HTTP response |
|
61 | + */ |
|
62 | + private function fetchMessageBody($response) |
|
63 | + { |
|
64 | + preg_match("/(?:\r\n){2}(.*)$/ms", $response, $match); |
|
65 | + $this->messageBody = $match[1]; |
|
66 | + } |
|
67 | 67 | |
68 | - /** |
|
69 | - * @param string $response cURL HTTP response |
|
70 | - * |
|
71 | - * @return bool Whether the Content-Type header was found or not |
|
72 | - */ |
|
73 | - protected function fetchContentType($response) |
|
74 | - { |
|
75 | - if (0 < preg_match("/Content-Type:(\V*)/ims", $response, $match)) { |
|
76 | - $this->contentType = trim($match[1]); |
|
68 | + /** |
|
69 | + * @param string $response cURL HTTP response |
|
70 | + * |
|
71 | + * @return bool Whether the Content-Type header was found or not |
|
72 | + */ |
|
73 | + protected function fetchContentType($response) |
|
74 | + { |
|
75 | + if (0 < preg_match("/Content-Type:(\V*)/ims", $response, $match)) { |
|
76 | + $this->contentType = trim($match[1]); |
|
77 | 77 | |
78 | - return true; |
|
79 | - } |
|
78 | + return true; |
|
79 | + } |
|
80 | 80 | |
81 | - return false; |
|
82 | - } |
|
81 | + return false; |
|
82 | + } |
|
83 | 83 | } |