-
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
Conversation
f5c8ecb
to
05ae5ed
Compare
named_params | ||
); | ||
if (Z_TYPE(retval) == IS_UNDEF) { | ||
return FAILURE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should set the IS_OBJ_DESTRUCTOR_CALLED
flag to match to behavior or new
and ReflectionClass::newInstance()
.
Should we also release the object, or let the caller release it?
Currently the caller can not tell if arg
is initialized or not when object_init_with_constructor()
returns FAILURE.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TIL about zend_object_store_ctor_failed
, I think it indeed makes sense to call it.
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 comment
The 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 comment
The 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 #[\Deprecated]
PR, I've made a follow-up to also ZVAL_UNDEF()
the zval then, making it safe to unconditionally zval_ptr_dtor()
the zval no matter if the constructor failed or not:
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 comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah this is what I am doing
Zend/zend_API.c
Outdated
zend_object *obj = Z_OBJ_P(arg); | ||
zend_function *constructor = obj->handlers->get_constructor(obj); | ||
if (UNEXPECTED(constructor == NULL)) { | ||
return FAILURE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Depending on the use-case of this function, I think that returning SUCCESS here when no arguments were passed, would be a better API. Otherwise we can not use this function as an internal equivalent of new
without checking the existence of a constructor before that.
ReflectionClass::newInstance()
does that, and throws Class %s does not have a constructor, so you cannot pass any constructor arguments
when arguments where passed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is actually a good point, really wondering if the SPL API that was exposed was not completely broken in reality now...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am wondering, should we throw an exception, or just allow it to initialize like a new
call with too many arguments? As this is not currently an error in PHP: https://3v4l.org/CHps3, and it would be inconsistent with passing too many parameters which would initialize it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case I would prefer to follow the behavior of new
, then. Because if this is used by user-facing APIs, users would expect that behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I think we need to handle internal and userland classes differently.
Need to add a test for this when I come back to work on it.
ab85fe1
to
43e2892
Compare
So it turns out, initializing an object by calling the constructor and have the behaviour be identical to I must say, I'm not sure the Every other object uses the standard object hook provided by Zend. I can see a couple of ways to improve the semantics here:
I must say, it is also slightly surprising that internal classes that don't define a constructor can take arbitrary amount of positional arguments, this feels like it goes against what one would normally expect of internal objects/functions |
Zend/zend_API.c
Outdated
/* 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's actually allowed to pass an empty hash table to the underlying zend_call_known_function
function.
And API users might expect the same to hold for this function.
However, in that case this line will fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, fixed this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this new function now be used somewhere? That would prove its correctness better than the test.
More specifically for error handling in |
This will instantiate the object and execute its constructor with the given parameters.
Found an existing bug with private constructors
Turns out the logic is really baffling
73c3cb7
to
9ad870a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. I find the control flow a bit confusing, I would prefer a common failure path:
https://gist.github.com/iluuu1994/8e2ae4715ef29176c2b97c37443d0e2f
But I'll leave it up to you if that's something you want to do.
This will instantiate the object and execute its constructor with the given parameters.
As it came up in #14418
Edit: I probably also should add support for a named argument HashTable.