| @@ -74,9 +74,9 @@ | ||
| 74 | 74 | */ |
| 75 | 75 | public $bStrictNullTypes = true; |
| 76 | 76 | |
| 77 | 77 | /** |
| 78 | - * Allow mapping of private and proteted properties. | |
| 78 | + * Allow mapping of private and protected properties. | |
| 79 | 79 | * |
| 80 | 80 | * @var boolean |
| 81 | 81 | */ |
| 82 | 82 | public $bIgnoreVisibility = false; |
| @@ -130,10 +130,10 @@ | ||
| 130 | 130 | |
| 131 | 131 | /** |
| 132 | 132 | * Map data all data in $json into the given $object instance. |
| 133 | 133 | * |
| 134 | - * @param object|array $json JSON object structure from json_decode() | |
| 135 | - * @param object $object Object to map $json data into | |
| 134 | + * @param object|array $json JSON object structure from json_decode() | |
| 135 | + * @param object|class-string $object Object to map $json data into | |
| 136 | 136 | * |
| 137 | 137 | * @return mixed Mapped object is returned. |
| 138 | 138 | * @see mapArray() |
| 139 | 139 | */ |
| @@ -144,15 +144,20 @@ | ||
| 144 | 144 | 'JsonMapper::map() requires first argument to be an object' |
| 145 | 145 | . ', ' . gettype($json) . ' given.' |
| 146 | 146 | ); |
| 147 | 147 | } |
| 148 | - if (!is_object($object)) { | |
| 148 | + if (!is_object($object) && (!is_string($object) || !class_exists($object))) { | |
| 149 | 149 | throw new InvalidArgumentException( |
| 150 | - 'JsonMapper::map() requires second argument to be an object' | |
| 150 | + 'JsonMapper::map() requires second argument to ' | |
| 151 | + . 'be an object or existing class name' | |
| 151 | 152 | . ', ' . gettype($object) . ' given.' |
| 152 | 153 | ); |
| 153 | 154 | } |
| 154 | 155 | |
| 156 | + if (is_string($object)) { | |
| 157 | + $object = $this->createInstance($object); | |
| 158 | + } | |
| 159 | + | |
| 155 | 160 | $strClassName = get_class($object); |
| 156 | 161 | $rc = new ReflectionClass($object); |
| 157 | 162 | $strNs = $rc->getNamespaceName(); |
| 158 | 163 | $providedProperties = array(); |
| @@ -176,12 +181,17 @@ | ||
| 176 | 181 | 'JSON property "' . $key . '" does not exist' |
| 177 | 182 | . ' in object of type ' . $strClassName |
| 178 | 183 | ); |
| 179 | 184 | } else if ($this->undefinedPropertyHandler !== null) { |
| 180 | - call_user_func( | |
| 185 | + $undefinedPropertyKey = call_user_func( | |
| 181 | 186 | $this->undefinedPropertyHandler, |
| 182 | 187 | $object, $key, $jvalue |
| 183 | 188 | ); |
| 189 | + | |
| 190 | + if (is_string($undefinedPropertyKey)) { | |
| 191 | + list($hasProperty, $accessor, $type, $isNullable) | |
| 192 | + = $this->inspectProperty($rc, $undefinedPropertyKey); | |
| 193 | + } | |
| 184 | 194 | } else { |
| 185 | 195 | $this->log( |
| 186 | 196 | 'info', |
| 187 | 197 | 'Property {property} does not exist in {class}', |
| @@ -187,9 +197,12 @@ | ||
| 187 | 197 | 'Property {property} does not exist in {class}', |
| 188 | 198 | array('property' => $key, 'class' => $strClassName) |
| 189 | 199 | ); |
| 190 | 200 | } |
| 191 | - continue; | |
| 201 | + | |
| 202 | + if (!$hasProperty) { | |
| 203 | + continue; | |
| 204 | + } | |
| 192 | 205 | } |
| 193 | 206 | |
| 194 | 207 | if ($accessor === null) { |
| 195 | 208 | if ($this->bExceptionOnUndefinedProperty) { |
| @@ -228,9 +241,11 @@ | ||
| 228 | 241 | continue; |
| 229 | 242 | } else if ($this->isObjectOfSameType($type, $jvalue)) { |
| 230 | 243 | $this->setProperty($object, $accessor, $jvalue); |
| 231 | 244 | continue; |
| 232 | - } else if ($this->isSimpleType($type)) { | |
| 245 | + } else if ($this->isSimpleType($type) | |
| 246 | + && !(is_array($jvalue) && $this->hasVariadicArrayType($accessor)) | |
| 247 | + ) { | |
| 233 | 248 | if ($type === 'string' && is_object($jvalue)) { |
| 234 | 249 | throw new JsonMapper_Exception( |
| 235 | 250 | 'JSON property "' . $key . '" in class "' |
| 236 | 251 | . $strClassName . '" is an object and' |
| @@ -247,8 +262,13 @@ | ||
| 247 | 262 | throw new JsonMapper_Exception( |
| 248 | 263 | 'Empty type at property "' |
| 249 | 264 | . $strClassName . '::$' . $key . '"' |
| 250 | 265 | ); |
| 266 | + } else if (strpos($type, '|')) { | |
| 267 | + throw new JsonMapper_Exception( | |
| 268 | + 'Cannot decide which of the union types shall be used: ' | |
| 269 | + . $type | |
| 270 | + ); | |
| 251 | 271 | } |
| 252 | 272 | |
| 253 | 273 | $array = null; |
| 254 | 274 | $subtype = null; |
| @@ -262,10 +282,13 @@ | ||
| 262 | 282 | $array = array(); |
| 263 | 283 | } else { |
| 264 | 284 | $array = $this->createInstance($proptype, false, $jvalue); |
| 265 | 285 | } |
| 286 | + } else if (is_array($jvalue) && $this->hasVariadicArrayType($accessor)) { | |
| 287 | + $array = array(); | |
| 288 | + $subtype = $type; | |
| 266 | 289 | } else { |
| 267 | - if (is_a($type, 'ArrayObject', true)) { | |
| 290 | + if (is_a($type, 'ArrayAccess', true)) { | |
| 268 | 291 | $array = $this->createInstance($type, false, $jvalue); |
| 269 | 292 | } |
| 270 | 293 | } |
| 271 | 294 | |
| @@ -479,16 +502,9 @@ | ||
| 479 | 502 | if (count($rparams) > 0) { |
| 480 | 503 | $isNullable = $rparams[0]->allowsNull(); |
| 481 | 504 | $ptype = $rparams[0]->getType(); |
| 482 | 505 | if ($ptype !== null) { |
| 483 | - if ($ptype instanceof ReflectionNamedType) { | |
| 484 | - $typeName = $ptype->getName(); | |
| 485 | - } | |
| 486 | - if ($ptype instanceof ReflectionUnionType | |
| 487 | - || !$ptype->isBuiltin() | |
| 488 | - ) { | |
| 489 | - $typeName = '\\' . $typeName; | |
| 490 | - } | |
| 506 | + $typeName = $this->stringifyReflectionType($ptype); | |
| 491 | 507 | //allow overriding an "array" type hint |
| 492 | 508 | // with a more specific class in the docblock |
| 493 | 509 | if ($typeName !== 'array') { |
| 494 | 510 | return array( |
| @@ -539,10 +555,9 @@ | ||
| 539 | 555 | // If there is no annotations (higher priority) inspect |
| 540 | 556 | // if there's a scalar type being defined |
| 541 | 557 | if (PHP_VERSION_ID >= 70400 && $rprop->hasType()) { |
| 542 | 558 | $rPropType = $rprop->getType(); |
| 543 | - $propTypeName = $rPropType->getName(); | |
| 544 | - | |
| 559 | + $propTypeName = $this->stringifyReflectionType($rPropType); | |
| 545 | 560 | if ($this->isSimpleType($propTypeName)) { |
| 546 | 561 | return array( |
| 547 | 562 | true, |
| 548 | 563 | $rprop, |
| @@ -553,9 +568,9 @@ | ||
| 553 | 568 | |
| 554 | 569 | return array( |
| 555 | 570 | true, |
| 556 | 571 | $rprop, |
| 557 | - '\\'.$propTypeName, | |
| 572 | + '\\' . ltrim($propTypeName, '\\'), | |
| 558 | 573 | $rPropType->allowsNull() |
| 559 | 574 | ); |
| 560 | 575 | } |
| 561 | 576 | |
| @@ -627,8 +642,10 @@ | ||
| 627 | 642 | $accessor->setAccessible(true); |
| 628 | 643 | } |
| 629 | 644 | if ($accessor instanceof ReflectionProperty) { |
| 630 | 645 | $accessor->setValue($object, $value); |
| 646 | + } else if (is_array($value) && $this->hasVariadicArrayType($accessor)) { | |
| 647 | + $accessor->invoke($object, ...$value); | |
| 631 | 648 | } else { |
| 632 | 649 | //setter method |
| 633 | 650 | $accessor->invoke($object, $value); |
| 634 | 651 | } |
| @@ -649,8 +666,14 @@ | ||
| 649 | 666 | protected function createInstance( |
| 650 | 667 | $class, $useParameter = false, $jvalue = null |
| 651 | 668 | ) { |
| 652 | 669 | if ($useParameter) { |
| 670 | + if (PHP_VERSION_ID >= 80100 | |
| 671 | + && is_subclass_of($class, \BackedEnum::class) | |
| 672 | + ) { | |
| 673 | + return $class::from($jvalue); | |
| 674 | + } | |
| 675 | + | |
| 653 | 676 | return new $class($jvalue); |
| 654 | 677 | } else { |
| 655 | 678 | $reflectClass = new ReflectionClass($class); |
| 656 | 679 | $constructor = $reflectClass->getConstructor(); |
| @@ -761,8 +784,34 @@ | ||
| 761 | 784 | return substr($strType, -2) === '[]'; |
| 762 | 785 | } |
| 763 | 786 | |
| 764 | 787 | /** |
| 788 | + * Returns true if accessor is a method and has only one parameter | |
| 789 | + * which is variadic. | |
| 790 | + * | |
| 791 | + * @param ReflectionMethod|ReflectionProperty|null $accessor accessor | |
| 792 | + * to set value | |
| 793 | + * | |
| 794 | + * @return bool | |
| 795 | + */ | |
| 796 | + protected function hasVariadicArrayType($accessor) | |
| 797 | + { | |
| 798 | + if (!$accessor instanceof ReflectionMethod) { | |
| 799 | + return false; | |
| 800 | + } | |
| 801 | + | |
| 802 | + $parameters = $accessor->getParameters(); | |
| 803 | + | |
| 804 | + if (count($parameters) !== 1) { | |
| 805 | + return false; | |
| 806 | + } | |
| 807 | + | |
| 808 | + $parameter = $parameters[0]; | |
| 809 | + | |
| 810 | + return $parameter->isVariadic(); | |
| 811 | + } | |
| 812 | + | |
| 813 | + /** | |
| 765 | 814 | * Checks if the given type is nullable |
| 766 | 815 | * |
| 767 | 816 | * @param string $type type name from the phpdoc param |
| 768 | 817 | * |
| @@ -787,8 +836,33 @@ | ||
| 787 | 836 | } |
| 788 | 837 | return substr( |
| 789 | 838 | str_ireplace('|null|', '|', '|' . $type . '|'), |
| 790 | 839 | 1, -1 |
| 840 | + ); | |
| 841 | + } | |
| 842 | + | |
| 843 | + /** | |
| 844 | + * Get a string representation of the reflection type. | |
| 845 | + * Required because named, union and intersection types need to be handled. | |
| 846 | + * | |
| 847 | + * @param ReflectionType $type Native PHP type | |
| 848 | + * | |
| 849 | + * @return string "foo|bar" | |
| 850 | + */ | |
| 851 | + protected function stringifyReflectionType(ReflectionType $type) | |
| 852 | + { | |
| 853 | + if ($type instanceof ReflectionNamedType) { | |
| 854 | + return ($type->isBuiltin() ? '' : '\\') . $type->getName(); | |
| 855 | + } | |
| 856 | + | |
| 857 | + return implode( | |
| 858 | + '|', | |
| 859 | + array_map( | |
| 860 | + function (ReflectionNamedType $type) { | |
| 861 | + return ($type->isBuiltin() ? '' : '\\') . $type->getName(); | |
| 862 | + }, | |
| 863 | + $type->getTypes() | |
| 864 | + ) | |
| 791 | 865 | ); |
| 792 | 866 | } |
| 793 | 867 | |
| 794 | 868 | /** |