- 
                Notifications
    You must be signed in to change notification settings 
- Fork 8k
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.