mirror of
https://github.com/php/php-src.git
synced 2026-03-24 00:02:20 +01:00
Zend: Add object_init_with_constructor() API (#14440)
This will instantiate the object and execute its constructor with the given parameters.
This commit is contained in:
committed by
GitHub
parent
a580d4a1c3
commit
51379d66ec
@@ -1846,6 +1846,74 @@ ZEND_API zend_result object_init_ex(zval *arg, zend_class_entry *class_type) /*
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
ZEND_API zend_result object_init_with_constructor(zval *arg, zend_class_entry *class_type, uint32_t param_count, zval *params, HashTable *named_params) /* {{{ */
|
||||
{
|
||||
zend_result status = _object_and_properties_init(arg, class_type, NULL);
|
||||
if (UNEXPECTED(status == FAILURE)) {
|
||||
ZVAL_UNDEF(arg);
|
||||
return FAILURE;
|
||||
}
|
||||
zend_object *obj = Z_OBJ_P(arg);
|
||||
zend_function *constructor = obj->handlers->get_constructor(obj);
|
||||
if (constructor == NULL) {
|
||||
/* The constructor can be NULL for 2 different reasons:
|
||||
* - It is not defined
|
||||
* - We are not allowed to call the constructor (e.g. private, or internal opaque class)
|
||||
* and an exception has been thrown
|
||||
* in the former case, we are (mostly) done and the object is initialized,
|
||||
* in the latter we need to destroy the object as initialization failed
|
||||
*/
|
||||
if (UNEXPECTED(EG(exception))) {
|
||||
zval_ptr_dtor(arg);
|
||||
ZVAL_UNDEF(arg);
|
||||
return FAILURE;
|
||||
}
|
||||
|
||||
/* Surprisingly, this is the only case where internal classes will allow to pass extra arguments
|
||||
* However, if there are named arguments (and it is not empty),
|
||||
* an Error must be thrown to be consistent with new ClassName() */
|
||||
if (UNEXPECTED(named_params != NULL && zend_hash_num_elements(named_params) != 0)) {
|
||||
/* Throw standard Error */
|
||||
zend_string *arg_name = NULL;
|
||||
zend_hash_get_current_key(named_params, &arg_name, /* num_index */ NULL);
|
||||
ZEND_ASSERT(arg_name != NULL);
|
||||
zend_throw_error(NULL, "Unknown named parameter $%s", ZSTR_VAL(arg_name));
|
||||
zend_string_release(arg_name);
|
||||
/* Do not call destructor, free object, and set arg to IS_UNDEF */
|
||||
zend_object_store_ctor_failed(obj);
|
||||
zval_ptr_dtor(arg);
|
||||
ZVAL_UNDEF(arg);
|
||||
return FAILURE;
|
||||
} else {
|
||||
return SUCCESS;
|
||||
}
|
||||
}
|
||||
/* A constructor should not return a value, however if an exception is thrown
|
||||
* zend_call_known_function() will set the retval to IS_UNDEF */
|
||||
zval retval;
|
||||
zend_call_known_function(
|
||||
constructor,
|
||||
obj,
|
||||
class_type,
|
||||
&retval,
|
||||
param_count,
|
||||
params,
|
||||
named_params
|
||||
);
|
||||
if (Z_TYPE(retval) == IS_UNDEF) {
|
||||
/* Do not call destructor, free object, and set arg to IS_UNDEF */
|
||||
zend_object_store_ctor_failed(obj);
|
||||
zval_ptr_dtor(arg);
|
||||
ZVAL_UNDEF(arg);
|
||||
return FAILURE;
|
||||
} else {
|
||||
/* Unlikely, but user constructors may return any value they want */
|
||||
zval_ptr_dtor(&retval);
|
||||
return SUCCESS;
|
||||
}
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
ZEND_API void object_init(zval *arg) /* {{{ */
|
||||
{
|
||||
ZVAL_OBJ(arg, zend_objects_new(zend_standard_class_def));
|
||||
|
||||
@@ -537,6 +537,7 @@ ZEND_API const char *zend_get_type_by_const(int type);
|
||||
#define array_init_size(arg, size) ZVAL_ARR((arg), zend_new_array(size))
|
||||
ZEND_API void object_init(zval *arg);
|
||||
ZEND_API zend_result object_init_ex(zval *arg, zend_class_entry *ce);
|
||||
ZEND_API zend_result object_init_with_constructor(zval *arg, zend_class_entry *class_type, uint32_t param_count, zval *params, HashTable *named_params);
|
||||
ZEND_API zend_result object_and_properties_init(zval *arg, zend_class_entry *ce, HashTable *properties);
|
||||
ZEND_API void object_properties_init(zend_object *object, zend_class_entry *class_type);
|
||||
ZEND_API void object_properties_init_ex(zend_object *object, HashTable *properties);
|
||||
|
||||
@@ -460,6 +460,29 @@ static ZEND_FUNCTION(zend_call_method)
|
||||
zend_call_method(obj, ce, NULL, ZSTR_VAL(method_name), ZSTR_LEN(method_name), return_value, argc - 2, arg1, arg2);
|
||||
}
|
||||
|
||||
/* Instantiate a class and run the constructor via object_init_with_constructor */
|
||||
static ZEND_FUNCTION(zend_object_init_with_constructor)
|
||||
{
|
||||
zend_class_entry *ce = NULL;
|
||||
zval *args;
|
||||
uint32_t num_args;
|
||||
HashTable *named_args;
|
||||
|
||||
ZEND_PARSE_PARAMETERS_START(1, -1)
|
||||
Z_PARAM_CLASS(ce)
|
||||
Z_PARAM_VARIADIC_WITH_NAMED(args, num_args, named_args)
|
||||
ZEND_PARSE_PARAMETERS_END();
|
||||
|
||||
zval obj;
|
||||
/* We don't use return_value directly to check for memory leaks of the API on failure */
|
||||
zend_result status = object_init_with_constructor(&obj, ce, num_args, args, named_args);
|
||||
if (status == FAILURE) {
|
||||
RETURN_THROWS();
|
||||
}
|
||||
ZEND_ASSERT(!EG(exception));
|
||||
ZVAL_COPY_VALUE(return_value, &obj);
|
||||
}
|
||||
|
||||
static ZEND_FUNCTION(zend_get_unit_enum)
|
||||
{
|
||||
ZEND_PARSE_PARAMETERS_NONE();
|
||||
|
||||
@@ -247,6 +247,8 @@ namespace {
|
||||
|
||||
function zend_call_method(object|string $obj_or_class, string $method, mixed $arg1 = UNKNOWN, mixed $arg2 = UNKNOWN): mixed {}
|
||||
|
||||
function zend_object_init_with_constructor(string $class, mixed ...$args): mixed {}
|
||||
|
||||
function zend_test_zend_ini_parse_quantity(string $str): int {}
|
||||
function zend_test_zend_ini_parse_uquantity(string $str): int {}
|
||||
|
||||
|
||||
9
ext/zend_test/test_arginfo.h
generated
9
ext/zend_test/test_arginfo.h
generated
@@ -1,5 +1,5 @@
|
||||
/* This is a generated file, edit the .stub.php file instead.
|
||||
* Stub hash: 53027610adee7e1c675b1c1b38886100ce4e63ef */
|
||||
* Stub hash: 35c11b9781669cff5ad72aa78b9b3732f7b827f1 */
|
||||
|
||||
ZEND_STATIC_ASSERT(PHP_VERSION_ID >= 80000, "test_arginfo.h only supports PHP version ID 80000 or newer, "
|
||||
"but it is included on an older PHP version");
|
||||
@@ -100,6 +100,11 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_call_method, 0, 2, IS_MIXED
|
||||
ZEND_ARG_TYPE_INFO(0, arg2, IS_MIXED, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_object_init_with_constructor, 0, 1, IS_MIXED, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, class, IS_STRING, 0)
|
||||
ZEND_ARG_VARIADIC_TYPE_INFO(0, args, IS_MIXED, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_zend_ini_parse_quantity, 0, 1, IS_LONG, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, str, IS_STRING, 0)
|
||||
ZEND_END_ARG_INFO()
|
||||
@@ -255,6 +260,7 @@ static ZEND_FUNCTION(zend_get_unit_enum);
|
||||
static ZEND_FUNCTION(zend_test_parameter_with_attribute);
|
||||
static ZEND_FUNCTION(zend_get_current_func_name);
|
||||
static ZEND_FUNCTION(zend_call_method);
|
||||
static ZEND_FUNCTION(zend_object_init_with_constructor);
|
||||
static ZEND_FUNCTION(zend_test_zend_ini_parse_quantity);
|
||||
static ZEND_FUNCTION(zend_test_zend_ini_parse_uquantity);
|
||||
static ZEND_FUNCTION(zend_test_zend_ini_str);
|
||||
@@ -350,6 +356,7 @@ static const zend_function_entry ext_functions[] = {
|
||||
ZEND_FE(zend_test_parameter_with_attribute, arginfo_zend_test_parameter_with_attribute)
|
||||
ZEND_FE(zend_get_current_func_name, arginfo_zend_get_current_func_name)
|
||||
ZEND_FE(zend_call_method, arginfo_zend_call_method)
|
||||
ZEND_FE(zend_object_init_with_constructor, arginfo_zend_object_init_with_constructor)
|
||||
ZEND_FE(zend_test_zend_ini_parse_quantity, arginfo_zend_test_zend_ini_parse_quantity)
|
||||
ZEND_FE(zend_test_zend_ini_parse_uquantity, arginfo_zend_test_zend_ini_parse_uquantity)
|
||||
ZEND_FE(zend_test_zend_ini_str, arginfo_zend_test_zend_ini_str)
|
||||
|
||||
165
ext/zend_test/tests/zend_object_init_with_constructor.phpt
Normal file
165
ext/zend_test/tests/zend_object_init_with_constructor.phpt
Normal file
@@ -0,0 +1,165 @@
|
||||
--TEST--
|
||||
Zend: Test object_init_with_constructor() API
|
||||
--EXTENSIONS--
|
||||
zend_test
|
||||
sysvmsg
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class PrivateUser {
|
||||
private function __construct() {
|
||||
return new stdClass();
|
||||
}
|
||||
public function __destruct() {
|
||||
echo 'Destructor for ', __CLASS__, PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
class ThrowingUser {
|
||||
public function __construct() {
|
||||
throw new Exception("Don't construct");
|
||||
}
|
||||
public function __destruct() {
|
||||
echo 'Destructor for ', __CLASS__, PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractClass {
|
||||
public function __construct() {
|
||||
return new stdClass();
|
||||
}
|
||||
public function __destruct() {
|
||||
echo 'Destructor for ', __CLASS__, PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
class TestUserWithConstructorArgs {
|
||||
public function __construct(int $int_param, string $string_param) {
|
||||
return new stdClass();
|
||||
}
|
||||
public function __destruct() {
|
||||
echo 'Destructor for ', __CLASS__, PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
class TestUserWithConstructorNoParams {
|
||||
public function __construct() {
|
||||
return new stdClass();
|
||||
}
|
||||
public function __destruct() {
|
||||
echo 'Destructor for ', __CLASS__, PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
echo "Testing impossible initializations\n";
|
||||
try {
|
||||
$o = zend_object_init_with_constructor("_ZendTestInterface");
|
||||
var_dump($o);
|
||||
unset($o);
|
||||
} catch (\Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
try {
|
||||
$o = zend_object_init_with_constructor("_ZendTestTrait");
|
||||
var_dump($o);
|
||||
unset($o);
|
||||
} catch (\Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
try {
|
||||
$o = zend_object_init_with_constructor("ZendTestUnitEnum");
|
||||
var_dump($o);
|
||||
unset($o);
|
||||
} catch (\Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
try {
|
||||
$o = zend_object_init_with_constructor("AbstractClass");
|
||||
var_dump($o);
|
||||
unset($o);
|
||||
} catch (\Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
try {
|
||||
$o = zend_object_init_with_constructor("SysvMessageQueue");
|
||||
var_dump($o);
|
||||
unset($o);
|
||||
} catch (\Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
try {
|
||||
$o = zend_object_init_with_constructor("PrivateUser");
|
||||
var_dump($o);
|
||||
unset($o);
|
||||
} catch (\Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
try {
|
||||
$o = zend_object_init_with_constructor("ThrowingUser");
|
||||
var_dump($o);
|
||||
unset($o);
|
||||
} catch (\Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
|
||||
echo "Testing param passing\n";
|
||||
try {
|
||||
$o = zend_object_init_with_constructor("TestUserWithConstructorArgs");
|
||||
var_dump($o);
|
||||
unset($o);
|
||||
} catch (\Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
try {
|
||||
$o = zend_object_init_with_constructor("TestUserWithConstructorArgs", "str", 5);
|
||||
var_dump($o);
|
||||
unset($o);
|
||||
} catch (\Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
try {
|
||||
$o = zend_object_init_with_constructor("TestUserWithConstructorArgs", 5, string_param: "str", unused_param: 15.3);
|
||||
var_dump($o);
|
||||
unset($o);
|
||||
} catch (\Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
|
||||
$o = zend_object_init_with_constructor("TestUserWithConstructorArgs", 5, string_param: "str");
|
||||
var_dump($o);
|
||||
unset($o);
|
||||
|
||||
echo "Passing too many args to constructor\n";
|
||||
$o = zend_object_init_with_constructor("TestUserWithConstructorArgs", 5, "str", 'unused_param');
|
||||
var_dump($o);
|
||||
unset($o);
|
||||
|
||||
echo "Testing class with defined constructor and no params\n";
|
||||
$o = zend_object_init_with_constructor("TestUserWithConstructorNoParams");
|
||||
var_dump($o);
|
||||
unset($o);
|
||||
?>
|
||||
--EXPECT--
|
||||
Testing impossible initializations
|
||||
Error: Cannot instantiate interface _ZendTestInterface
|
||||
Error: Cannot instantiate trait _ZendTestTrait
|
||||
Error: Cannot instantiate enum ZendTestUnitEnum
|
||||
Error: Cannot instantiate abstract class AbstractClass
|
||||
Error: Cannot directly construct SysvMessageQueue, use msg_get_queue() instead
|
||||
Error: Call to private PrivateUser::__construct() from global scope
|
||||
Exception: Don't construct
|
||||
Testing param passing
|
||||
ArgumentCountError: Too few arguments to function TestUserWithConstructorArgs::__construct(), 0 passed and exactly 2 expected
|
||||
TypeError: TestUserWithConstructorArgs::__construct(): Argument #1 ($int_param) must be of type int, string given
|
||||
Error: Unknown named parameter $unused_param
|
||||
object(TestUserWithConstructorArgs)#1 (0) {
|
||||
}
|
||||
Destructor for TestUserWithConstructorArgs
|
||||
Passing too many args to constructor
|
||||
object(TestUserWithConstructorArgs)#1 (0) {
|
||||
}
|
||||
Destructor for TestUserWithConstructorArgs
|
||||
Testing class with defined constructor and no params
|
||||
object(TestUserWithConstructorNoParams)#1 (0) {
|
||||
}
|
||||
Destructor for TestUserWithConstructorNoParams
|
||||
@@ -0,0 +1,149 @@
|
||||
--TEST--
|
||||
Zend: Test object_init_with_constructor() API for objects without constructors
|
||||
--EXTENSIONS--
|
||||
zend_test
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class TestUserWithoutConstructor {
|
||||
public function __destruct() {
|
||||
echo 'Destructor for ', __CLASS__, PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
echo "#### Passing no args ####\n";
|
||||
echo "Userland class:\n";
|
||||
echo "Using new:\n";
|
||||
$o = new TestUserWithoutConstructor();
|
||||
var_dump($o);
|
||||
unset($o);
|
||||
echo "Using zend_object_init_with_constructor():\n";
|
||||
$o = zend_object_init_with_constructor("TestUserWithoutConstructor");
|
||||
var_dump($o);
|
||||
unset($o);
|
||||
|
||||
echo "Internal class:\n";
|
||||
echo "Using new:\n";
|
||||
$o = new _ZendTestMagicCall();
|
||||
var_dump($o);
|
||||
unset($o);
|
||||
echo "Using zend_object_init_with_constructor():\n";
|
||||
$o = zend_object_init_with_constructor("_ZendTestMagicCall");
|
||||
var_dump($o);
|
||||
unset($o);
|
||||
|
||||
echo "\n#### Passing extra positional args ####\n";
|
||||
echo "Userland class:\n";
|
||||
echo "Using new:\n";
|
||||
$o = new TestUserWithoutConstructor('position_arg');
|
||||
var_dump($o);
|
||||
unset($o);
|
||||
echo "Using zend_object_init_with_constructor():\n";
|
||||
$o = zend_object_init_with_constructor("TestUserWithoutConstructor", 'position_arg');
|
||||
var_dump($o);
|
||||
unset($o);
|
||||
|
||||
echo "Internal class:\n";
|
||||
echo "Using new:\n";
|
||||
try {
|
||||
$o = new _ZendTestMagicCall('position_arg');
|
||||
var_dump($o);
|
||||
unset($o);
|
||||
} catch (\Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
echo "Using zend_object_init_with_constructor():\n";
|
||||
try {
|
||||
$o = zend_object_init_with_constructor("_ZendTestMagicCall", 'position_arg');
|
||||
var_dump($o);
|
||||
unset($o);
|
||||
} catch (\Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
|
||||
echo "\n#### Passing extra named args ####\n";
|
||||
echo "Userland class:\n";
|
||||
echo "Using new:\n";
|
||||
try {
|
||||
$o = new TestUserWithoutConstructor(unknown_param: 'named_arg');
|
||||
var_dump($o);
|
||||
unset($o);
|
||||
} catch (\Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
echo "Using zend_object_init_with_constructor():\n";
|
||||
try {
|
||||
$o = zend_object_init_with_constructor("TestUserWithoutConstructor", unknown_param: 'named_arg');
|
||||
var_dump($o);
|
||||
unset($o);
|
||||
} catch (\Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
|
||||
echo "Internal class:\n";
|
||||
echo "Using new:\n";
|
||||
try {
|
||||
$o = new _ZendTestMagicCall(unknown_param: 'named_arg');
|
||||
var_dump($o);
|
||||
unset($o);
|
||||
} catch (\Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
echo "Using zend_object_init_with_constructor():\n";
|
||||
try {
|
||||
$o = zend_object_init_with_constructor("_ZendTestMagicCall", unknown_param: 'named_arg');
|
||||
var_dump($o);
|
||||
unset($o);
|
||||
} catch (\Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
#### Passing no args ####
|
||||
Userland class:
|
||||
Using new:
|
||||
object(TestUserWithoutConstructor)#1 (0) {
|
||||
}
|
||||
Destructor for TestUserWithoutConstructor
|
||||
Using zend_object_init_with_constructor():
|
||||
object(TestUserWithoutConstructor)#1 (0) {
|
||||
}
|
||||
Destructor for TestUserWithoutConstructor
|
||||
Internal class:
|
||||
Using new:
|
||||
object(_ZendTestMagicCall)#1 (0) {
|
||||
}
|
||||
Using zend_object_init_with_constructor():
|
||||
object(_ZendTestMagicCall)#1 (0) {
|
||||
}
|
||||
|
||||
#### Passing extra positional args ####
|
||||
Userland class:
|
||||
Using new:
|
||||
object(TestUserWithoutConstructor)#1 (0) {
|
||||
}
|
||||
Destructor for TestUserWithoutConstructor
|
||||
Using zend_object_init_with_constructor():
|
||||
object(TestUserWithoutConstructor)#1 (0) {
|
||||
}
|
||||
Destructor for TestUserWithoutConstructor
|
||||
Internal class:
|
||||
Using new:
|
||||
object(_ZendTestMagicCall)#1 (0) {
|
||||
}
|
||||
Using zend_object_init_with_constructor():
|
||||
object(_ZendTestMagicCall)#1 (0) {
|
||||
}
|
||||
|
||||
#### Passing extra named args ####
|
||||
Userland class:
|
||||
Using new:
|
||||
Error: Unknown named parameter $unknown_param
|
||||
Using zend_object_init_with_constructor():
|
||||
Error: Unknown named parameter $unknown_param
|
||||
Internal class:
|
||||
Using new:
|
||||
Error: Unknown named parameter $unknown_param
|
||||
Using zend_object_init_with_constructor():
|
||||
Error: Unknown named parameter $unknown_param
|
||||
Reference in New Issue
Block a user