diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index bc9f06e5e1a3c..db0a030b00661 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -5127,6 +5127,67 @@ ZEND_METHOD(ReflectionClass, newInstanceArgs) } /* }}} */ +/* {{{ Returns an instance of this class whose properties are filled with the given data before the constructor is called */ +ZEND_METHOD(ReflectionClass, newInstanceFromData) +{ + reflection_object *intern; + zend_class_entry *ce; + int argc = 0; + HashTable *data, *args = NULL; + zend_function *constructor; + zend_string *key; + zval *val; + + GET_REFLECTION_OBJECT_PTR(ce); + + if (ce->type == ZEND_INTERNAL_CLASS) { + zend_throw_exception_ex(reflection_exception_ptr, 0, "Class %s is an internal class that cannot be instantiated from data", ZSTR_VAL(ce->name)); + RETURN_THROWS(); + } + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_ARRAY_HT(data) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY_HT(args) + ZEND_PARSE_PARAMETERS_END(); + + if (args) { + argc = zend_hash_num_elements(args); + } + + if (UNEXPECTED(object_init_ex(return_value, ce) != SUCCESS)) { + return; + } + + ZEND_HASH_FOREACH_STR_KEY_VAL(data, key, val) { + zend_update_property_ex(ce, Z_OBJ_P(return_value), key, val); + } ZEND_HASH_FOREACH_END(); + + const zend_class_entry *old_scope = EG(fake_scope); + EG(fake_scope) = ce; + constructor = Z_OBJ_HT_P(return_value)->get_constructor(Z_OBJ_P(return_value)); + EG(fake_scope) = old_scope; + + /* Run the constructor if there is one */ + if (constructor) { + if (!(constructor->common.fn_flags & ZEND_ACC_PUBLIC)) { + zend_throw_exception_ex(reflection_exception_ptr, 0, "Access to non-public constructor of class %s", ZSTR_VAL(ce->name)); + zval_ptr_dtor(return_value); + RETURN_NULL(); + } + + zend_call_known_function( + constructor, Z_OBJ_P(return_value), Z_OBJCE_P(return_value), NULL, 0, NULL, args); + + if (EG(exception)) { + zend_object_store_ctor_failed(Z_OBJ_P(return_value)); + } + } else if (argc) { + zend_throw_exception_ex(reflection_exception_ptr, 0, "Class %s does not have a constructor, so you cannot pass any constructor arguments", ZSTR_VAL(ce->name)); + } +} +/* }}} */ + void reflection_class_new_lazy(INTERNAL_FUNCTION_PARAMETERS, int strategy, bool is_reset) { diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index 67fb849db5845..cfd020e8fb290 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -370,6 +370,8 @@ public function newInstanceWithoutConstructor(): object {} /** @tentative-return-type */ public function newInstanceArgs(array $args = []): ?object {} + public function newInstanceFromData(array $data, array $args = []): ?object {} + public function newLazyGhost(callable $initializer, int $options = 0): object {} public function newLazyProxy(callable $factory, int $options = 0): object {} diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index 85827cf502517..114c18d69e8fa 100644 --- a/ext/reflection/php_reflection_arginfo.h +++ b/ext/reflection/php_reflection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: ef9e7f30a29819489e17a9c100f55696d5d164e0 */ + * Stub hash: 302c3e7e07097265ffa379d4158f49acc4b46ca7 */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0) ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0) @@ -289,6 +289,11 @@ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionClass_ ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, args, IS_ARRAY, 0, "[]") ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionClass_newInstanceFromData, 0, 1, IS_OBJECT, 1) + ZEND_ARG_TYPE_INFO(0, data, IS_ARRAY, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, args, IS_ARRAY, 0, "[]") +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionClass_newLazyGhost, 0, 1, IS_OBJECT, 0) ZEND_ARG_TYPE_INFO(0, initializer, IS_CALLABLE, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_LONG, 0, "0") @@ -829,6 +834,7 @@ ZEND_METHOD(ReflectionClass, isInstance); ZEND_METHOD(ReflectionClass, newInstance); ZEND_METHOD(ReflectionClass, newInstanceWithoutConstructor); ZEND_METHOD(ReflectionClass, newInstanceArgs); +ZEND_METHOD(ReflectionClass, newInstanceFromData); ZEND_METHOD(ReflectionClass, newLazyGhost); ZEND_METHOD(ReflectionClass, newLazyProxy); ZEND_METHOD(ReflectionClass, resetAsLazyGhost); @@ -1122,6 +1128,7 @@ static const zend_function_entry class_ReflectionClass_methods[] = { ZEND_ME(ReflectionClass, newInstance, arginfo_class_ReflectionClass_newInstance, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, newInstanceWithoutConstructor, arginfo_class_ReflectionClass_newInstanceWithoutConstructor, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, newInstanceArgs, arginfo_class_ReflectionClass_newInstanceArgs, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClass, newInstanceFromData, arginfo_class_ReflectionClass_newInstanceFromData, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, newLazyGhost, arginfo_class_ReflectionClass_newLazyGhost, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, newLazyProxy, arginfo_class_ReflectionClass_newLazyProxy, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, resetAsLazyGhost, arginfo_class_ReflectionClass_resetAsLazyGhost, ZEND_ACC_PUBLIC) diff --git a/ext/reflection/tests/ReflectionClass_newInstanceFromData_001.phpt b/ext/reflection/tests/ReflectionClass_newInstanceFromData_001.phpt new file mode 100644 index 0000000000000..4ebe11226080e --- /dev/null +++ b/ext/reflection/tests/ReflectionClass_newInstanceFromData_001.phpt @@ -0,0 +1,90 @@ +--TEST-- +ReflectionClass::newInstanceFromData +--FILE-- +newInstanceFromData(['a' => 'bad', 'b' => 123], ['foo', 1337]); +} +catch(Throwable $e) +{ + echo "Exception: " . $e->getMessage() . "\n"; +} + +var_dump($rcA->newInstanceFromData(['a' => 123, 'b' => 'good'], ['foo', 1337])); + +var_dump($rcB->newInstanceFromData(['a' => 123, 'b' => 'good'])); + +var_dump($rcC->newInstanceFromData(['a' => 123, 'b' => 'good'])); + +var_dump($rcC->newInstanceFromData([])); + +var_dump($rcD->newInstanceFromData(['a' => 123, 'b' => 'good'])); + +?> +--EXPECTF-- +Exception: Cannot assign string to property A::$a of type int +In constructor of class A +object(A)#5 (2) { + ["a"]=> + int(123) + ["b"]=> + string(4) "good" +} +object(B)#5 (2) { + ["a"]=> + int(123) + ["b"]=> + string(4) "good" +} + +Deprecated: Creation of dynamic property C::$a is deprecated in %s on line %d + +Deprecated: Creation of dynamic property C::$b is deprecated in %s on line %d +object(C)#5 (2) { + ["a"]=> + int(123) + ["b"]=> + string(4) "good" +} +object(C)#5 (0) { +} +object(D)#5 (2) { + ["a"]=> + int(123) + ["b"]=> + string(4) "good" +} diff --git a/ext/reflection/tests/ReflectionClass_newInstanceFromData_002.phpt b/ext/reflection/tests/ReflectionClass_newInstanceFromData_002.phpt new file mode 100644 index 0000000000000..0852bbbd55f2b --- /dev/null +++ b/ext/reflection/tests/ReflectionClass_newInstanceFromData_002.phpt @@ -0,0 +1,30 @@ +--TEST-- +ReflectionClass::newInstanceFromData - internal class +--FILE-- +newInstanceFromData([], ['now', new DateTimeZone('UTC')]); +} +catch(Throwable $e) +{ + echo "Exception: " . $e->getMessage() . "\n"; +} + +try +{ + $rcPDOStatement->newInstanceFromData(['a' => 123]); +} +catch(Throwable $e) +{ + echo "Exception: " . $e->getMessage() . "\n"; +} + +?> +--EXPECTF-- +Exception: Class DateTime is an internal class that cannot be instantiated from data +Exception: Class PDOStatement is an internal class that cannot be instantiated from data diff --git a/ext/reflection/tests/ReflectionClass_toString_001.phpt b/ext/reflection/tests/ReflectionClass_toString_001.phpt index fd5d83e917419..8d5beb16fc7ee 100644 --- a/ext/reflection/tests/ReflectionClass_toString_001.phpt +++ b/ext/reflection/tests/ReflectionClass_toString_001.phpt @@ -30,7 +30,7 @@ Class [ class ReflectionClass implements Stringable, Refle Property [ public string $name ] } - - Methods [64] { + - Methods [65] { Method [ private method __clone ] { - Parameters [0] { @@ -332,6 +332,15 @@ Class [ class ReflectionClass implements Stringable, Refle - Tentative return [ ?object ] } + Method [ public method newInstanceFromData ] { + + - Parameters [2] { + Parameter #0 [ array $data ] + Parameter #1 [ array $args = [] ] + } + - Return [ ?object ] + } + Method [ public method newLazyGhost ] { - Parameters [2] {