Skip to content

Commit 51379d6

Browse files
authored
Zend: Add object_init_with_constructor() API (#14440)
This will instantiate the object and execute its constructor with the given parameters.
1 parent a580d4a commit 51379d6

7 files changed

+416
-1
lines changed

Zend/zend_API.c

+68
Original file line numberDiff line numberDiff line change
@@ -1846,6 +1846,74 @@ ZEND_API zend_result object_init_ex(zval *arg, zend_class_entry *class_type) /*
18461846
}
18471847
/* }}} */
18481848

1849+
ZEND_API zend_result object_init_with_constructor(zval *arg, zend_class_entry *class_type, uint32_t param_count, zval *params, HashTable *named_params) /* {{{ */
1850+
{
1851+
zend_result status = _object_and_properties_init(arg, class_type, NULL);
1852+
if (UNEXPECTED(status == FAILURE)) {
1853+
ZVAL_UNDEF(arg);
1854+
return FAILURE;
1855+
}
1856+
zend_object *obj = Z_OBJ_P(arg);
1857+
zend_function *constructor = obj->handlers->get_constructor(obj);
1858+
if (constructor == NULL) {
1859+
/* The constructor can be NULL for 2 different reasons:
1860+
* - It is not defined
1861+
* - We are not allowed to call the constructor (e.g. private, or internal opaque class)
1862+
* and an exception has been thrown
1863+
* in the former case, we are (mostly) done and the object is initialized,
1864+
* in the latter we need to destroy the object as initialization failed
1865+
*/
1866+
if (UNEXPECTED(EG(exception))) {
1867+
zval_ptr_dtor(arg);
1868+
ZVAL_UNDEF(arg);
1869+
return FAILURE;
1870+
}
1871+
1872+
/* Surprisingly, this is the only case where internal classes will allow to pass extra arguments
1873+
* However, if there are named arguments (and it is not empty),
1874+
* an Error must be thrown to be consistent with new ClassName() */
1875+
if (UNEXPECTED(named_params != NULL && zend_hash_num_elements(named_params) != 0)) {
1876+
/* Throw standard Error */
1877+
zend_string *arg_name = NULL;
1878+
zend_hash_get_current_key(named_params, &arg_name, /* num_index */ NULL);
1879+
ZEND_ASSERT(arg_name != NULL);
1880+
zend_throw_error(NULL, "Unknown named parameter $%s", ZSTR_VAL(arg_name));
1881+
zend_string_release(arg_name);
1882+
/* Do not call destructor, free object, and set arg to IS_UNDEF */
1883+
zend_object_store_ctor_failed(obj);
1884+
zval_ptr_dtor(arg);
1885+
ZVAL_UNDEF(arg);
1886+
return FAILURE;
1887+
} else {
1888+
return SUCCESS;
1889+
}
1890+
}
1891+
/* A constructor should not return a value, however if an exception is thrown
1892+
* zend_call_known_function() will set the retval to IS_UNDEF */
1893+
zval retval;
1894+
zend_call_known_function(
1895+
constructor,
1896+
obj,
1897+
class_type,
1898+
&retval,
1899+
param_count,
1900+
params,
1901+
named_params
1902+
);
1903+
if (Z_TYPE(retval) == IS_UNDEF) {
1904+
/* Do not call destructor, free object, and set arg to IS_UNDEF */
1905+
zend_object_store_ctor_failed(obj);
1906+
zval_ptr_dtor(arg);
1907+
ZVAL_UNDEF(arg);
1908+
return FAILURE;
1909+
} else {
1910+
/* Unlikely, but user constructors may return any value they want */
1911+
zval_ptr_dtor(&retval);
1912+
return SUCCESS;
1913+
}
1914+
}
1915+
/* }}} */
1916+
18491917
ZEND_API void object_init(zval *arg) /* {{{ */
18501918
{
18511919
ZVAL_OBJ(arg, zend_objects_new(zend_standard_class_def));

Zend/zend_API.h

+1
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,7 @@ ZEND_API const char *zend_get_type_by_const(int type);
537537
#define array_init_size(arg, size) ZVAL_ARR((arg), zend_new_array(size))
538538
ZEND_API void object_init(zval *arg);
539539
ZEND_API zend_result object_init_ex(zval *arg, zend_class_entry *ce);
540+
ZEND_API zend_result object_init_with_constructor(zval *arg, zend_class_entry *class_type, uint32_t param_count, zval *params, HashTable *named_params);
540541
ZEND_API zend_result object_and_properties_init(zval *arg, zend_class_entry *ce, HashTable *properties);
541542
ZEND_API void object_properties_init(zend_object *object, zend_class_entry *class_type);
542543
ZEND_API void object_properties_init_ex(zend_object *object, HashTable *properties);

ext/zend_test/test.c

+23
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,29 @@ static ZEND_FUNCTION(zend_call_method)
460460
zend_call_method(obj, ce, NULL, ZSTR_VAL(method_name), ZSTR_LEN(method_name), return_value, argc - 2, arg1, arg2);
461461
}
462462

463+
/* Instantiate a class and run the constructor via object_init_with_constructor */
464+
static ZEND_FUNCTION(zend_object_init_with_constructor)
465+
{
466+
zend_class_entry *ce = NULL;
467+
zval *args;
468+
uint32_t num_args;
469+
HashTable *named_args;
470+
471+
ZEND_PARSE_PARAMETERS_START(1, -1)
472+
Z_PARAM_CLASS(ce)
473+
Z_PARAM_VARIADIC_WITH_NAMED(args, num_args, named_args)
474+
ZEND_PARSE_PARAMETERS_END();
475+
476+
zval obj;
477+
/* We don't use return_value directly to check for memory leaks of the API on failure */
478+
zend_result status = object_init_with_constructor(&obj, ce, num_args, args, named_args);
479+
if (status == FAILURE) {
480+
RETURN_THROWS();
481+
}
482+
ZEND_ASSERT(!EG(exception));
483+
ZVAL_COPY_VALUE(return_value, &obj);
484+
}
485+
463486
static ZEND_FUNCTION(zend_get_unit_enum)
464487
{
465488
ZEND_PARSE_PARAMETERS_NONE();

ext/zend_test/test.stub.php

+2
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,8 @@ function zend_get_current_func_name(): string {}
247247

248248
function zend_call_method(object|string $obj_or_class, string $method, mixed $arg1 = UNKNOWN, mixed $arg2 = UNKNOWN): mixed {}
249249

250+
function zend_object_init_with_constructor(string $class, mixed ...$args): mixed {}
251+
250252
function zend_test_zend_ini_parse_quantity(string $str): int {}
251253
function zend_test_zend_ini_parse_uquantity(string $str): int {}
252254

ext/zend_test/test_arginfo.h

+8-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
--TEST--
2+
Zend: Test object_init_with_constructor() API
3+
--EXTENSIONS--
4+
zend_test
5+
sysvmsg
6+
--FILE--
7+
<?php
8+
9+
class PrivateUser {
10+
private function __construct() {
11+
return new stdClass();
12+
}
13+
public function __destruct() {
14+
echo 'Destructor for ', __CLASS__, PHP_EOL;
15+
}
16+
}
17+
18+
class ThrowingUser {
19+
public function __construct() {
20+
throw new Exception("Don't construct");
21+
}
22+
public function __destruct() {
23+
echo 'Destructor for ', __CLASS__, PHP_EOL;
24+
}
25+
}
26+
27+
abstract class AbstractClass {
28+
public function __construct() {
29+
return new stdClass();
30+
}
31+
public function __destruct() {
32+
echo 'Destructor for ', __CLASS__, PHP_EOL;
33+
}
34+
}
35+
36+
class TestUserWithConstructorArgs {
37+
public function __construct(int $int_param, string $string_param) {
38+
return new stdClass();
39+
}
40+
public function __destruct() {
41+
echo 'Destructor for ', __CLASS__, PHP_EOL;
42+
}
43+
}
44+
45+
class TestUserWithConstructorNoParams {
46+
public function __construct() {
47+
return new stdClass();
48+
}
49+
public function __destruct() {
50+
echo 'Destructor for ', __CLASS__, PHP_EOL;
51+
}
52+
}
53+
54+
echo "Testing impossible initializations\n";
55+
try {
56+
$o = zend_object_init_with_constructor("_ZendTestInterface");
57+
var_dump($o);
58+
unset($o);
59+
} catch (\Throwable $e) {
60+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
61+
}
62+
try {
63+
$o = zend_object_init_with_constructor("_ZendTestTrait");
64+
var_dump($o);
65+
unset($o);
66+
} catch (\Throwable $e) {
67+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
68+
}
69+
try {
70+
$o = zend_object_init_with_constructor("ZendTestUnitEnum");
71+
var_dump($o);
72+
unset($o);
73+
} catch (\Throwable $e) {
74+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
75+
}
76+
try {
77+
$o = zend_object_init_with_constructor("AbstractClass");
78+
var_dump($o);
79+
unset($o);
80+
} catch (\Throwable $e) {
81+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
82+
}
83+
try {
84+
$o = zend_object_init_with_constructor("SysvMessageQueue");
85+
var_dump($o);
86+
unset($o);
87+
} catch (\Throwable $e) {
88+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
89+
}
90+
try {
91+
$o = zend_object_init_with_constructor("PrivateUser");
92+
var_dump($o);
93+
unset($o);
94+
} catch (\Throwable $e) {
95+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
96+
}
97+
try {
98+
$o = zend_object_init_with_constructor("ThrowingUser");
99+
var_dump($o);
100+
unset($o);
101+
} catch (\Throwable $e) {
102+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
103+
}
104+
105+
echo "Testing param passing\n";
106+
try {
107+
$o = zend_object_init_with_constructor("TestUserWithConstructorArgs");
108+
var_dump($o);
109+
unset($o);
110+
} catch (\Throwable $e) {
111+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
112+
}
113+
try {
114+
$o = zend_object_init_with_constructor("TestUserWithConstructorArgs", "str", 5);
115+
var_dump($o);
116+
unset($o);
117+
} catch (\Throwable $e) {
118+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
119+
}
120+
try {
121+
$o = zend_object_init_with_constructor("TestUserWithConstructorArgs", 5, string_param: "str", unused_param: 15.3);
122+
var_dump($o);
123+
unset($o);
124+
} catch (\Throwable $e) {
125+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
126+
}
127+
128+
$o = zend_object_init_with_constructor("TestUserWithConstructorArgs", 5, string_param: "str");
129+
var_dump($o);
130+
unset($o);
131+
132+
echo "Passing too many args to constructor\n";
133+
$o = zend_object_init_with_constructor("TestUserWithConstructorArgs", 5, "str", 'unused_param');
134+
var_dump($o);
135+
unset($o);
136+
137+
echo "Testing class with defined constructor and no params\n";
138+
$o = zend_object_init_with_constructor("TestUserWithConstructorNoParams");
139+
var_dump($o);
140+
unset($o);
141+
?>
142+
--EXPECT--
143+
Testing impossible initializations
144+
Error: Cannot instantiate interface _ZendTestInterface
145+
Error: Cannot instantiate trait _ZendTestTrait
146+
Error: Cannot instantiate enum ZendTestUnitEnum
147+
Error: Cannot instantiate abstract class AbstractClass
148+
Error: Cannot directly construct SysvMessageQueue, use msg_get_queue() instead
149+
Error: Call to private PrivateUser::__construct() from global scope
150+
Exception: Don't construct
151+
Testing param passing
152+
ArgumentCountError: Too few arguments to function TestUserWithConstructorArgs::__construct(), 0 passed and exactly 2 expected
153+
TypeError: TestUserWithConstructorArgs::__construct(): Argument #1 ($int_param) must be of type int, string given
154+
Error: Unknown named parameter $unused_param
155+
object(TestUserWithConstructorArgs)#1 (0) {
156+
}
157+
Destructor for TestUserWithConstructorArgs
158+
Passing too many args to constructor
159+
object(TestUserWithConstructorArgs)#1 (0) {
160+
}
161+
Destructor for TestUserWithConstructorArgs
162+
Testing class with defined constructor and no params
163+
object(TestUserWithConstructorNoParams)#1 (0) {
164+
}
165+
Destructor for TestUserWithConstructorNoParams

0 commit comments

Comments
 (0)