1
0
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:
Gina Peter Banyard
2024-06-06 21:21:16 +01:00
committed by GitHub
parent a580d4a1c3
commit 51379d66ec
7 changed files with 416 additions and 1 deletions

View File

@@ -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));

View File

@@ -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);

View File

@@ -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();

View File

@@ -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 {}

View File

@@ -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)

View 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

View File

@@ -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