1: <?php
2:
3: namespace Guzzle\Service\Description;
4:
5: use Guzzle\Common\ToArrayInterface;
6:
7: 8: 9:
10: class SchemaValidator implements ValidatorInterface
11: {
12: 13: 14:
15: protected static $instance;
16:
17: 18: 19:
20: protected $castIntegerToStringType;
21:
22: 23: 24:
25: protected $errors;
26:
27: 28: 29: 30: 31: 32:
33: public static function getInstance()
34: {
35: if (!self::$instance) {
36: self::$instance = new self();
37: }
38:
39: return self::$instance;
40: }
41:
42: 43: 44: 45:
46: public function __construct($castIntegerToStringType = true)
47: {
48: $this->castIntegerToStringType = $castIntegerToStringType;
49: }
50:
51: 52: 53:
54: public function validate(Parameter $param, &$value)
55: {
56: $this->errors = array();
57: $this->recursiveProcess($param, $value);
58:
59: if (empty($this->errors)) {
60: return true;
61: } else {
62: sort($this->errors);
63: return false;
64: }
65: }
66:
67: 68: 69: 70: 71:
72: public function getErrors()
73: {
74: return $this->errors ?: array();
75: }
76:
77: 78: 79: 80: 81: 82: 83: 84: 85: 86:
87: protected function recursiveProcess(Parameter $param, &$value, $path = '', $depth = 0)
88: {
89:
90: $value = $param->getValue($value);
91:
92: $required = $param->getRequired();
93:
94: if ((null === $value && !$required) || $param->getStatic()) {
95: return true;
96: }
97:
98: $type = $param->getType();
99:
100: $valueIsArray = is_array($value);
101:
102: if ($name = $param->getName()) {
103: $path .= "[{$name}]";
104: }
105:
106: if ($type == 'object') {
107:
108:
109: if ($param->getInstanceOf()) {
110: $instance = $param->getInstanceOf();
111: if (!($value instanceof $instance)) {
112: $this->errors[] = "{$path} must be an instance of {$instance}";
113: return false;
114: }
115: }
116:
117:
118: $traverse = $temporaryValue = false;
119:
120:
121: if (!$valueIsArray && $value instanceof ToArrayInterface) {
122: $value = $value->toArray();
123: }
124:
125: if ($valueIsArray) {
126:
127: if (isset($value[0])) {
128: $this->errors[] = "{$path} must be an array of properties. Got a numerically indexed array.";
129: return false;
130: }
131: $traverse = true;
132: } elseif ($value === null) {
133:
134: $value = array();
135: $temporaryValue = $valueIsArray = $traverse = true;
136: }
137:
138: if ($traverse) {
139:
140: if ($properties = $param->getProperties()) {
141:
142: foreach ($properties as $property) {
143: $name = $property->getName();
144: if (isset($value[$name])) {
145: $this->recursiveProcess($property, $value[$name], $path, $depth + 1);
146: } else {
147: $current = null;
148: $this->recursiveProcess($property, $current, $path, $depth + 1);
149:
150: if ($current) {
151: $value[$name] = $current;
152: }
153: }
154: }
155: }
156:
157: $additional = $param->getAdditionalProperties();
158: if ($additional !== true) {
159:
160: $keys = array_keys($value);
161:
162: $diff = array_diff($keys, array_keys($properties));
163: if (!empty($diff)) {
164:
165: if ($additional instanceOf Parameter) {
166: foreach ($diff as $key) {
167: $this->recursiveProcess($additional, $value[$key], "{$path}[{$key}]", $depth);
168: }
169: } else {
170:
171: $keys = array_keys($value);
172: $this->errors[] = sprintf('%s[%s] is not an allowed property', $path, reset($keys));
173: }
174: }
175: }
176:
177:
178:
179:
180: if ($temporaryValue && empty($value)) {
181: $value = null;
182: $valueIsArray = false;
183: }
184: }
185:
186: } elseif ($type == 'array' && $valueIsArray && $param->getItems()) {
187: foreach ($value as $i => &$item) {
188:
189: $this->recursiveProcess($param->getItems(), $item, $path . "[{$i}]", $depth + 1);
190: }
191: }
192:
193:
194: if ($required && $value === null && $type != 'null') {
195: $message = "{$path} is " . ($param->getType() ? ('a required ' . implode(' or ', (array) $param->getType())) : 'required');
196: if ($param->getDescription()) {
197: $message .= ': ' . $param->getDescription();
198: }
199: $this->errors[] = $message;
200: return false;
201: }
202:
203:
204:
205: if ($type && (!$type = $this->determineType($type, $value))) {
206: if ($this->castIntegerToStringType && $param->getType() == 'string' && is_integer($value)) {
207: $value = (string) $value;
208: } else {
209: $this->errors[] = "{$path} must be of type " . implode(' or ', (array) $param->getType());
210: }
211: }
212:
213:
214: if ($type == 'string') {
215:
216:
217: if (($enum = $param->getEnum()) && !in_array($value, $enum)) {
218: $this->errors[] = "{$path} must be one of " . implode(' or ', array_map(function ($s) {
219: return '"' . addslashes($s) . '"';
220: }, $enum));
221: }
222:
223: if (($pattern = $param->getPattern()) && !preg_match($pattern, $value)) {
224: $this->errors[] = "{$path} must match the following regular expression: {$pattern}";
225: }
226:
227: $strLen = null;
228: if ($min = $param->getMinLength()) {
229: $strLen = strlen($value);
230: if ($strLen < $min) {
231: $this->errors[] = "{$path} length must be greater than or equal to {$min}";
232: }
233: }
234: if ($max = $param->getMaxLength()) {
235: if (($strLen ?: strlen($value)) > $max) {
236: $this->errors[] = "{$path} length must be less than or equal to {$max}";
237: }
238: }
239:
240: } elseif ($type == 'array') {
241:
242: $size = null;
243: if ($min = $param->getMinItems()) {
244: $size = count($value);
245: if ($size < $min) {
246: $this->errors[] = "{$path} must contain {$min} or more elements";
247: }
248: }
249: if ($max = $param->getMaxItems()) {
250: if (($size ?: count($value)) > $max) {
251: $this->errors[] = "{$path} must contain {$max} or fewer elements";
252: }
253: }
254:
255: } elseif ($type == 'integer' || $type == 'number' || $type == 'numeric') {
256: if (($min = $param->getMinimum()) && $value < $min) {
257: $this->errors[] = "{$path} must be greater than or equal to {$min}";
258: }
259: if (($max = $param->getMaximum()) && $value > $max) {
260: $this->errors[] = "{$path} must be less than or equal to {$max}";
261: }
262: }
263:
264: return empty($this->errors);
265: }
266:
267: 268: 269: 270: 271: 272: 273: 274:
275: protected function determineType($type, $value)
276: {
277: foreach ((array) $type as $t) {
278: if ($t == 'string' && (is_string($value) || (is_object($value) && method_exists($value, '__toString')))) {
279: return 'string';
280: } elseif ($t == 'object' && (is_array($value) || is_object($value))) {
281: return 'object';
282: } elseif ($t == 'array' && is_array($value)) {
283: return 'array';
284: } elseif ($t == 'integer' && is_integer($value)) {
285: return 'integer';
286: } elseif ($t == 'boolean' && is_bool($value)) {
287: return 'boolean';
288: } elseif ($t == 'number' && is_numeric($value)) {
289: return 'number';
290: } elseif ($t == 'numeric' && is_numeric($value)) {
291: return 'numeric';
292: } elseif ($t == 'null' && !$value) {
293: return 'null';
294: } elseif ($t == 'any') {
295: return 'any';
296: }
297: }
298:
299: return false;
300: }
301: }
302: