|
22 | 22 | */
|
23 | 23 | class UndefinedConstraint extends Constraint
|
24 | 24 | {
|
| 25 | + /** |
| 26 | + * @var array List of properties to which a default value has been applied |
| 27 | + */ |
| 28 | + protected $appliedDefaults = array(); |
| 29 | + |
25 | 30 | /**
|
26 | 31 | * {@inheritdoc}
|
27 | 32 | */
|
28 |
| - public function check(&$value, $schema = null, JsonPointer $path = null, $i = null) |
| 33 | + public function check(&$value, $schema = null, JsonPointer $path = null, $i = null, $fromDefault = false) |
29 | 34 | {
|
30 | 35 | if (is_null($schema) || !is_object($schema)) {
|
31 | 36 | return;
|
32 | 37 | }
|
33 | 38 |
|
34 | 39 | $path = $this->incrementPath($path ?: new JsonPointer(''), $i);
|
| 40 | + if ($fromDefault) { |
| 41 | + $path->setFromDefault(); |
| 42 | + } |
35 | 43 |
|
36 | 44 | // check special properties
|
37 | 45 | $this->validateCommonProperties($value, $schema, $path, $i);
|
@@ -67,7 +75,8 @@ public function validateTypes(&$value, $schema = null, JsonPointer $path, $i = n
|
67 | 75 | isset($schema->properties) ? $this->factory->getSchemaStorage()->resolveRefSchema($schema->properties) : $schema,
|
68 | 76 | $path,
|
69 | 77 | isset($schema->additionalProperties) ? $schema->additionalProperties : null,
|
70 |
| - isset($schema->patternProperties) ? $schema->patternProperties : null |
| 78 | + isset($schema->patternProperties) ? $schema->patternProperties : null, |
| 79 | + $this->appliedDefaults |
71 | 80 | );
|
72 | 81 | }
|
73 | 82 |
|
@@ -112,46 +121,8 @@ protected function validateCommonProperties(&$value, $schema = null, JsonPointer
|
112 | 121 | }
|
113 | 122 |
|
114 | 123 | // Apply default values from schema
|
115 |
| - if ($this->factory->getConfig(self::CHECK_MODE_APPLY_DEFAULTS)) { |
116 |
| - if ($this->getTypeCheck()->isObject($value) && isset($schema->properties)) { |
117 |
| - // $value is an object, so apply default properties if defined |
118 |
| - foreach ($schema->properties as $currentProperty => $propertyDefinition) { |
119 |
| - if (!$this->getTypeCheck()->propertyExists($value, $currentProperty) && isset($propertyDefinition->default)) { |
120 |
| - if (is_object($propertyDefinition->default)) { |
121 |
| - $this->getTypeCheck()->propertySet($value, $currentProperty, clone $propertyDefinition->default); |
122 |
| - } else { |
123 |
| - $this->getTypeCheck()->propertySet($value, $currentProperty, $propertyDefinition->default); |
124 |
| - } |
125 |
| - } |
126 |
| - } |
127 |
| - } elseif ($this->getTypeCheck()->isArray($value)) { |
128 |
| - if (isset($schema->properties)) { |
129 |
| - // $value is an array, but default properties are defined, so treat as assoc |
130 |
| - foreach ($schema->properties as $currentProperty => $propertyDefinition) { |
131 |
| - if (!isset($value[$currentProperty]) && isset($propertyDefinition->default)) { |
132 |
| - if (is_object($propertyDefinition->default)) { |
133 |
| - $value[$currentProperty] = clone $propertyDefinition->default; |
134 |
| - } else { |
135 |
| - $value[$currentProperty] = $propertyDefinition->default; |
136 |
| - } |
137 |
| - } |
138 |
| - } |
139 |
| - } elseif (isset($schema->items)) { |
140 |
| - // $value is an array, and default items are defined - treat as plain array |
141 |
| - foreach ($schema->items as $currentProperty => $itemDefinition) { |
142 |
| - if (!isset($value[$currentProperty]) && isset($itemDefinition->default)) { |
143 |
| - if (is_object($itemDefinition->default)) { |
144 |
| - $value[$currentProperty] = clone $itemDefinition->default; |
145 |
| - } else { |
146 |
| - $value[$currentProperty] = $itemDefinition->default; |
147 |
| - } |
148 |
| - } |
149 |
| - } |
150 |
| - } |
151 |
| - } elseif (($value instanceof self || $value === null) && isset($schema->default)) { |
152 |
| - // $value is a leaf, not a container - apply the default directly |
153 |
| - $value = is_object($schema->default) ? clone $schema->default : $schema->default; |
154 |
| - } |
| 124 | + if (!$path->fromDefault()) { |
| 125 | + $this->applyDefaultValues($value, $schema, $path); |
155 | 126 | }
|
156 | 127 |
|
157 | 128 | // Verify required values
|
@@ -214,6 +185,96 @@ protected function validateCommonProperties(&$value, $schema = null, JsonPointer
|
214 | 185 | }
|
215 | 186 | }
|
216 | 187 |
|
| 188 | + /** |
| 189 | + * Check whether a default should be applied for this value |
| 190 | + * |
| 191 | + * @param mixed $schema |
| 192 | + * @param mixed $parentSchema |
| 193 | + * @param bool $requiredOnly |
| 194 | + * |
| 195 | + * @return bool |
| 196 | + */ |
| 197 | + private function shouldApplyDefaultValue($requiredOnly, $schema, $name = null, $parentSchema = null) |
| 198 | + { |
| 199 | + // required-only mode is off |
| 200 | + if (!$requiredOnly) { |
| 201 | + return true; |
| 202 | + } |
| 203 | + // draft-04 required is set |
| 204 | + if ( |
| 205 | + $name !== null |
| 206 | + && isset($parentSchema->required) |
| 207 | + && is_array($parentSchema->required) |
| 208 | + && in_array($name, $parentSchema->required) |
| 209 | + ) { |
| 210 | + return true; |
| 211 | + } |
| 212 | + // draft-03 required is set |
| 213 | + if (isset($schema->required) && !is_array($schema->required) && $schema->required) { |
| 214 | + return true; |
| 215 | + } |
| 216 | + // default case |
| 217 | + return false; |
| 218 | + } |
| 219 | + |
| 220 | + /** |
| 221 | + * Apply default values |
| 222 | + * |
| 223 | + * @param mixed $value |
| 224 | + * @param mixed $schema |
| 225 | + * @param JsonPointer $path |
| 226 | + */ |
| 227 | + protected function applyDefaultValues(&$value, $schema, $path) |
| 228 | + { |
| 229 | + // only apply defaults if feature is enabled |
| 230 | + if (!$this->factory->getConfig(self::CHECK_MODE_APPLY_DEFAULTS)) { |
| 231 | + return; |
| 232 | + } |
| 233 | + |
| 234 | + // apply defaults if appropriate |
| 235 | + $requiredOnly = $this->factory->getConfig(self::CHECK_MODE_ONLY_REQUIRED_DEFAULTS); |
| 236 | + if (isset($schema->properties) && LooseTypeCheck::isObject($value)) { |
| 237 | + // $value is an object or assoc array, and properties are defined - treat as an object |
| 238 | + foreach ($schema->properties as $currentProperty => $propertyDefinition) { |
| 239 | + if ( |
| 240 | + !LooseTypeCheck::propertyExists($value, $currentProperty) |
| 241 | + && property_exists($propertyDefinition, 'default') |
| 242 | + && $this->shouldApplyDefaultValue($requiredOnly, $propertyDefinition, $currentProperty, $schema) |
| 243 | + ) { |
| 244 | + // assign default value |
| 245 | + if (is_object($propertyDefinition->default)) { |
| 246 | + LooseTypeCheck::propertySet($value, $currentProperty, clone $propertyDefinition->default); |
| 247 | + } else { |
| 248 | + LooseTypeCheck::propertySet($value, $currentProperty, $propertyDefinition->default); |
| 249 | + } |
| 250 | + $this->appliedDefaults[] = $currentProperty; |
| 251 | + } |
| 252 | + } |
| 253 | + } elseif (isset($schema->items) && LooseTypeCheck::isArray($value)) { |
| 254 | + // $value is an array, and items are defined - treat as plain array |
| 255 | + foreach ($schema->items as $currentItem => $itemDefinition) { |
| 256 | + if ( |
| 257 | + !array_key_exists($currentItem, $value) |
| 258 | + && property_exists($itemDefinition, 'default') |
| 259 | + && $this->shouldApplyDefaultValue($requiredOnly, $itemDefinition)) { |
| 260 | + if (is_object($itemDefinition->default)) { |
| 261 | + $value[$currentItem] = clone $itemDefinition->default; |
| 262 | + } else { |
| 263 | + $value[$currentItem] = $itemDefinition->default; |
| 264 | + } |
| 265 | + } |
| 266 | + $path->setFromDefault(); |
| 267 | + } |
| 268 | + } elseif ( |
| 269 | + $value instanceof self |
| 270 | + && property_exists($schema, 'default') |
| 271 | + && $this->shouldApplyDefaultValue($requiredOnly, $schema)) { |
| 272 | + // $value is a leaf, not a container - apply the default directly |
| 273 | + $value = is_object($schema->default) ? clone $schema->default : $schema->default; |
| 274 | + $path->setFromDefault(); |
| 275 | + } |
| 276 | + } |
| 277 | + |
217 | 278 | /**
|
218 | 279 | * Validate allOf, anyOf, and oneOf properties
|
219 | 280 | *
|
|
0 commit comments