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