-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Zend: Add object_init_with_constructor() API #14440
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5aa6d5a
b702139
6a2ef11
cebc0a2
d50f1c3
5b2946d
6d98dce
3f6e96e
bda84f4
3755151
94724be
0f2a38e
610ca73
9ad870a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should set the Should we also release the object, or let the caller release it? Currently the caller can not tell if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TIL about I was thinking I had a bug in not releasing the object, but I couldn't produce it with a test so I left it, will try and see if I can come up with something and add some destructors to the tests. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The object is useless because it's incompletely initialized, so the caller can't do much with it. I think I'd prefer it it were released and not returned at all. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This discussion reminded me of my PR #14161 (“Add zend_get_attribute_object()”), which also automatically destructs the object on constructor failure. For the I would suggest the same here: If the constructor fails, the object is destroyed and the zval UNDEF'd. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah this is what I am doing |
||
} 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)); | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Uh oh!
There was an error while loading. Please reload this page.