// Protocol Buffers - Google's data interchange format // Copyright 2014 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "message.h" #include #include #include // This is not self-contained: it must be after other Zend includes. #include #include "arena.h" #include "array.h" #include "convert.h" #include "def.h" #include "map.h" #include "php-upb.h" #include "protobuf.h" // ----------------------------------------------------------------------------- // Message // ----------------------------------------------------------------------------- typedef struct { zend_object std; zval arena; const Descriptor* desc; upb_msg *msg; } Message; zend_class_entry *message_ce; static zend_object_handlers message_object_handlers; // PHP Object Handlers ///////////////////////////////////////////////////////// /** * Message_create() * * PHP class entry function to allocate and initialize a new Message object. */ static zend_object* Message_create(zend_class_entry *class_type) { Message *intern = emalloc(sizeof(Message)); // XXX(haberman): verify whether we actually want to take this route. class_type->default_properties_count = 0; zend_object_std_init(&intern->std, class_type); intern->std.handlers = &message_object_handlers; Arena_Init(&intern->arena); return &intern->std; } /** * Message_dtor() * * Object handler to destroy a Message. This releases all resources associated * with the message. Note that it is possible to access a destroyed object from * PHP in rare cases. */ static void Message_dtor(zend_object* obj) { Message* intern = (Message*)obj; ObjCache_Delete(intern->msg); zval_dtor(&intern->arena); zend_object_std_dtor(&intern->std); } /** * get_field() * * Helper function to look up a field given a member name (as a string). */ static const upb_fielddef *get_field(Message *msg, zval *member) { const upb_msgdef *m = msg->desc->msgdef; const upb_fielddef *f = upb_msgdef_ntof(m, Z_STRVAL_P(member), Z_STRLEN_P(member)); if (!f) { zend_throw_exception_ex(NULL, 0, "No such property %s.", ZSTR_VAL(msg->desc->class_entry->name)); } return f; } /** * Message_read_property() * * Object handler for reading a property in PHP. Called when PHP code does: * * $x = $message->foobar; * * Note that all properties of generated messages are private, so this should * only be possible to invoke from generated code, which has accessors like: * * public function getOptionalInt32() * { * return $this->optional_int32; * } * * We lookup the field and return the scalar, RepeatedField, or MapField for * this field. */ static zval *Message_read_property(zval *obj, zval *member, int type, void **cache_slot, zval *rv) { Message* intern = (Message*)Z_OBJ_P(obj); const upb_fielddef *f = get_field(intern, member); upb_arena *arena = Arena_Get(&intern->arena); if (!f) return NULL; if (upb_fielddef_ismap(f)) { upb_mutmsgval msgval = upb_msg_mutable(intern->msg, f, arena); MapField_GetPhpWrapper(rv, msgval.map, f, &intern->arena); } else if (upb_fielddef_isseq(f)) { upb_mutmsgval msgval = upb_msg_mutable(intern->msg, f, arena); RepeatedField_GetPhpWrapper(rv, msgval.array, f, &intern->arena); } else { upb_msgval msgval = upb_msg_get(intern->msg, f); const Descriptor *subdesc = Descriptor_GetFromFieldDef(f); Convert_UpbToPhp(msgval, rv, upb_fielddef_type(f), subdesc, &intern->arena); } return rv; } /** * Message_write_property() * * Object handler for writing a property in PHP. Called when PHP code does: * * $message->foobar = $x; * * Note that all properties of generated messages are private, so this should * only be possible to invoke from generated code, which has accessors like: * * public function setOptionalInt32($var) * { * GPBUtil::checkInt32($var); * $this->optional_int32 = $var; * * return $this; * } * * The C extension version of checkInt32() doesn't actually check anything, so * we perform all checking and conversion in this function. */ static void Message_write_property(zval *obj, zval *member, zval *val, void **cache_slot) { Message* intern = (Message*)Z_OBJ_P(obj); const upb_fielddef *f = get_field(intern, member); upb_arena *arena = Arena_Get(&intern->arena); upb_msgval msgval; if (!f) return; if (upb_fielddef_ismap(f)) { msgval.map_val = MapField_GetUpbMap(val, f, arena); if (!msgval.map_val) return; } else if (upb_fielddef_isseq(f)) { msgval.array_val = RepeatedField_GetUpbArray(val, f, arena); if (!msgval.array_val) return; } else { upb_fieldtype_t type = upb_fielddef_type(f); const Descriptor *subdesc = Descriptor_GetFromFieldDef(f); bool ok = Convert_PhpToUpb(val, &msgval, type, subdesc, arena); if (!ok) return; } upb_msg_set(intern->msg, f, msgval, arena); } /** * Message_get_property_ptr_ptr() * * Object handler for the get_property_ptr_ptr event in PHP. This returns a * reference to our internal properties. We don't support this, so we return * NULL. */ static zval *Message_get_property_ptr_ptr(zval *object, zval *member, int type, void **cache_slot) { return NULL; // We do not have a properties table. } /** * Message_get_properties() * * Object handler for the get_properties event in PHP. This returns a HashTable * of our internal properties. We don't support this, so we return NULL. */ static HashTable* Message_get_properties(zval* object TSRMLS_DC) { return NULL; // We don't offer direct references to our properties. } // C Functions from message.h. ///////////////////////////////////////////////// // These are documented in the header file. void Message_GetPhpWrapper(zval *val, const Descriptor *desc, upb_msg *msg, zval *arena) { if (!msg) { ZVAL_NULL(val); return; } if (!ObjCache_Get(msg, val)) { Message *intern = emalloc(sizeof(Message)); // XXX(haberman): verify whether we actually want to take this route. desc->class_entry->default_properties_count = 0; zend_object_std_init(&intern->std, desc->class_entry); intern->std.handlers = &message_object_handlers; ZVAL_COPY(&intern->arena, arena); intern->desc = desc; intern->msg = msg; ZVAL_OBJ(val, &intern->std); ObjCache_Add(intern->msg, &intern->std); } } bool Message_GetUpbMessage(zval *val, const Descriptor *desc, upb_arena *arena, upb_msg **msg) { PBPHP_ASSERT(desc); if (Z_ISREF_P(val)) { ZVAL_DEREF(val); } if (Z_TYPE_P(val) == IS_NULL) { *msg = NULL; return true; } if (Z_TYPE_P(val) == IS_OBJECT && instanceof_function(Z_OBJCE_P(val), desc->class_entry)) { Message *intern = (Message*)Z_OBJ_P(val); upb_arena_fuse(arena, Arena_Get(&intern->arena)); *msg = intern->msg; return true; } else { zend_throw_exception_ex(NULL, 0, "Given value is not an instance of %s.", ZSTR_VAL(desc->class_entry->name)); return false; } } // Message PHP methods ///////////////////////////////////////////////////////// /** * Message_InitFromPhp() * * Helper method to handle the initialization of a message from a PHP value, eg. * * $m = new TestMessage([ * 'optional_int32' => -42, * 'optional_bool' => true, * 'optional_string' => 'a', * 'optional_enum' => TestEnum::ONE, * 'optional_message' => new Sub([ * 'a' => 33 * ]), * 'repeated_int32' => [-42, -52], * 'repeated_enum' => [TestEnum::ZERO, TestEnum::ONE], * 'repeated_message' => [new Sub(['a' => 34]), * new Sub(['a' => 35])], * 'map_int32_int32' => [-62 => -62], * 'map_int32_enum' => [1 => TestEnum::ONE], * 'map_int32_message' => [1 => new Sub(['a' => 36])], * ]); * * The initializer must be an array. */ bool Message_InitFromPhp(upb_msg *msg, const upb_msgdef *m, zval *init, upb_arena *arena) { HashTable* table = HASH_OF(init); HashPosition pos; if (Z_ISREF_P(init)) { ZVAL_DEREF(init); } if (Z_TYPE_P(init) != IS_ARRAY) { zend_throw_exception_ex(NULL, 0, "Initializer for a message %s must be an array.", upb_msgdef_fullname(m)); return false; } zend_hash_internal_pointer_reset_ex(table, &pos); while (true) { // Iterate over key/value pairs. zval key; zval *val; const upb_fielddef *f; upb_msgval msgval; zend_hash_get_current_key_zval_ex(table, &key, &pos); val = zend_hash_get_current_data_ex(table, &pos); if (!val) return true; // Finished iteration. if (Z_ISREF_P(val)) { ZVAL_DEREF(val); } f = upb_msgdef_ntof(m, Z_STRVAL_P(&key), Z_STRLEN_P(&key)); if (!f) { zend_throw_exception_ex(NULL, 0, "No such field %s", Z_STRVAL_P(&key)); return false; } if (upb_fielddef_ismap(f)) { msgval.map_val = MapField_GetUpbMap(val, f, arena); if (!msgval.map_val) return false; } else if (upb_fielddef_isseq(f)) { msgval.array_val = RepeatedField_GetUpbArray(val, f, arena); if (!msgval.array_val) return false; } else { const Descriptor *desc = Descriptor_GetFromFieldDef(f); upb_fieldtype_t type = upb_fielddef_type(f); if (!Convert_PhpToUpbAutoWrap(val, &msgval, type, desc, arena)) { return false; } } upb_msg_set(msg, f, msgval, arena); zend_hash_move_forward_ex(table, &pos); zval_dtor(&key); } } /** * Message::__construct() * * Constructor for Message. * @param array Map of initial values ['k' = val] */ PHP_METHOD(Message, __construct) { Message* intern = (Message*)Z_OBJ_P(getThis()); const Descriptor* desc = Descriptor_GetFromClassEntry(Z_OBJCE_P(getThis())); const upb_msgdef *msgdef = desc->msgdef; upb_arena *arena = Arena_Get(&intern->arena); zval *init_arr; intern->desc = desc; intern->msg = upb_msg_new(msgdef, arena); ObjCache_Add(intern->msg, &intern->std); if (zend_parse_parameters(ZEND_NUM_ARGS(), "|a!", &init_arr) == FAILURE) { return; } if (init_arr) { Message_InitFromPhp(intern->msg, desc->msgdef, init_arr, arena); } } /** * Message::discardUnknownFields() * * Discards any unknown fields for this message or any submessages. */ PHP_METHOD(Message, discardUnknownFields) { Message* intern = (Message*)Z_OBJ_P(getThis()); upb_msg_discardunknown(intern->msg, intern->desc->msgdef, 64); } /** * Message::clear() * * Clears all fields of this message. */ PHP_METHOD(Message, clear) { Message* intern = (Message*)Z_OBJ_P(getThis()); upb_msg_clear(intern->msg, intern->desc->msgdef); } /** * Message::mergeFrom() * * Merges from the given message, which must be of the same class as us. * @param object Message to merge from. */ PHP_METHOD(Message, mergeFrom) { Message* intern = (Message*)Z_OBJ_P(getThis()); Message* from; upb_arena *arena = Arena_Get(&intern->arena); const upb_msglayout *l = upb_msgdef_layout(intern->desc->msgdef); zval* value; char *pb; size_t size; bool ok; if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &value, intern->desc->class_entry) == FAILURE) { return; } from = (Message*)Z_OBJ_P(value); // Should be guaranteed since we passed the class type to // zend_parse_parameters(). PBPHP_ASSERT(from->desc == intern->desc); // TODO(haberman): use a temp arena for this once we can make upb_decode() // copy strings. pb = upb_encode(from->msg, l, arena, &size); if (!pb) { zend_throw_exception_ex(NULL, 0, "Max nesting exceeded"); return; } ok = upb_decode(pb, size, intern->msg, l, arena); PBPHP_ASSERT(ok); } /** * Message::mergeFromString() * * Merges from the given string. * @param string Binary protobuf data to merge. */ PHP_METHOD(Message, mergeFromString) { Message* intern = (Message*)Z_OBJ_P(getThis()); char *data = NULL; char *data_copy = NULL; zend_long data_len; const upb_msglayout *l = upb_msgdef_layout(intern->desc->msgdef); upb_arena *arena = Arena_Get(&intern->arena); if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &data, &data_len) == FAILURE) { return; } // TODO(haberman): avoid this copy when we can make the decoder copy. data_copy = upb_arena_malloc(arena, data_len); memcpy(data_copy, data, data_len); if (!upb_decode(data_copy, data_len, intern->msg, l, arena)) { zend_throw_exception_ex(NULL, 0, "Error occurred during parsing"); return; } } /** * Message::serializeToString() * * Serializes this message instance to protobuf data. * @return string Serialized protobuf data. */ PHP_METHOD(Message, serializeToString) { Message* intern = (Message*)Z_OBJ_P(getThis()); const upb_msglayout *l = upb_msgdef_layout(intern->desc->msgdef); upb_arena *tmp_arena = upb_arena_new(); char *data; size_t size; data = upb_encode(intern->msg, l, tmp_arena, &size); if (!data) { zend_throw_exception_ex(NULL, 0, "Error occurred during serialization"); upb_arena_free(tmp_arena); return; } RETVAL_STRINGL(data, size); upb_arena_free(tmp_arena); } /** * Message::mergeFromJsonString() * * Merges the JSON data parsed from the given string. * @param string Serialized JSON data. */ PHP_METHOD(Message, mergeFromJsonString) { Message* intern = (Message*)Z_OBJ_P(getThis()); char *data = NULL; char *data_copy = NULL; zend_long data_len; upb_arena *arena = Arena_Get(&intern->arena); upb_status status; zend_bool ignore_json_unknown = false; int options = 0; if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|b", &data, &data_len, &ignore_json_unknown) == FAILURE) { return; } // TODO(haberman): avoid this copy when we can make the decoder copy. data_copy = upb_arena_malloc(arena, data_len + 1); memcpy(data_copy, data, data_len); data_copy[data_len] = '\0'; if (ignore_json_unknown) { options |= UPB_JSONDEC_IGNOREUNKNOWN; } upb_status_clear(&status); if (!upb_json_decode(data_copy, data_len, intern->msg, intern->desc->msgdef, DescriptorPool_GetSymbolTable(), options, arena, &status)) { zend_throw_exception_ex(NULL, 0, "Error occurred during parsing: %s", upb_status_errmsg(&status)); return; } } /** * Message::serializeToJsonString() * * Serializes this object to JSON. * @return string Serialized JSON data. */ PHP_METHOD(Message, serializeToJsonString) { Message* intern = (Message*)Z_OBJ_P(getThis()); size_t size; int options = 0; char buf[1024]; zend_bool preserve_proto_fieldnames = false; upb_status status; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &preserve_proto_fieldnames) == FAILURE) { return; } if (preserve_proto_fieldnames) { options |= UPB_JSONENC_PROTONAMES; } upb_status_clear(&status); size = upb_json_encode(intern->msg, intern->desc->msgdef, DescriptorPool_GetSymbolTable(), options, buf, sizeof(buf), &status); if (!upb_ok(&status)) { zend_throw_exception_ex(NULL, 0, "Error occurred during JSON serialization: %s", upb_status_errmsg(&status)); return; } if (size >= sizeof(buf)) { char *buf2 = malloc(size + 1); upb_json_encode(intern->msg, intern->desc->msgdef, DescriptorPool_GetSymbolTable(), options, buf2, size + 1, &status); RETVAL_STRINGL(buf2, size); free(buf2); } else { RETVAL_STRINGL(buf, size); } } /** * Message::readWrapperValue() * * Returns an unboxed value for the given field. This is called from generated * methods for wrapper fields, eg. * * public function getDoubleValueUnwrapped() * { * return $this->readWrapperValue("double_value"); * } * * @return Unwrapped field value or null. */ PHP_METHOD(Message, readWrapperValue) { Message* intern = (Message*)Z_OBJ_P(getThis()); char* member; const upb_fielddef *f; zend_long size; if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &member, &size) == FAILURE) { return; } f = upb_msgdef_ntof(intern->desc->msgdef, member, size); if (!f || !upb_msgdef_iswrapper(upb_fielddef_msgsubdef(f))) { zend_throw_exception_ex(NULL, 0, "Message %s has no field %s", upb_msgdef_fullname(intern->desc->msgdef), member); return; } if (upb_msg_has(intern->msg, f)) { const upb_msg *wrapper = upb_msg_get(intern->msg, f).msg_val; const upb_msgdef *m = upb_fielddef_msgsubdef(f); const upb_fielddef *val_f = upb_msgdef_itof(m, 1); const upb_fieldtype_t val_type = upb_fielddef_type(val_f); upb_msgval msgval = upb_msg_get(wrapper, val_f); zval ret; Convert_UpbToPhp(msgval, &ret, val_type, NULL, &intern->arena); RETURN_ZVAL(&ret, 1, 0); } else { RETURN_NULL(); } } /** * Message::writeWrapperValue() * * Sets the given wrapper field to the given unboxed value. This is called from * generated methods for wrapper fields, eg. * * * public function setDoubleValueUnwrapped($var) * { * $this->writeWrapperValue("double_value", $var); * return $this; * } * * @param Unwrapped field value or null. */ PHP_METHOD(Message, writeWrapperValue) { Message* intern = (Message*)Z_OBJ_P(getThis()); upb_arena *arena = Arena_Get(&intern->arena); char* member; const upb_fielddef *f; upb_msgval msgval; zend_long size; zval* val; if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz", &member, &size, &val) == FAILURE) { return; } f = upb_msgdef_ntof(intern->desc->msgdef, member, size); if (!f || !upb_msgdef_iswrapper(upb_fielddef_msgsubdef(f))) { zend_throw_exception_ex(NULL, 0, "Message %s has no field %s", upb_msgdef_fullname(intern->desc->msgdef), member); return; } if (Z_ISREF_P(val)) { ZVAL_DEREF(val); } if (Z_TYPE_P(val) == IS_NULL) { upb_msg_clearfield(intern->msg, f); } else { const upb_msgdef *m = upb_fielddef_msgsubdef(f); const upb_fielddef *val_f = upb_msgdef_itof(m, 1); upb_fieldtype_t val_type = upb_fielddef_type(val_f); upb_msg *wrapper; if (!Convert_PhpToUpb(val, &msgval, val_type, NULL, arena)) { return; // Error is already set. } wrapper = upb_msg_mutable(intern->msg, f, arena).msg; upb_msg_set(wrapper, val_f, msgval, arena); } } /** * Message::whichOneof() * * Given a oneof name, returns the name of the field that is set for this oneof, * or otherwise the empty string. * * @return string The field name in this oneof that is currently set. */ PHP_METHOD(Message, whichOneof) { Message* intern = (Message*)Z_OBJ_P(getThis()); const upb_oneofdef* oneof; const upb_fielddef* field; char* name; zend_long len; if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &name, &len) == FAILURE) { return; } oneof = upb_msgdef_ntoo(intern->desc->msgdef, name, len); if (!oneof) { zend_throw_exception_ex(NULL, 0, "Message %s has no oneof %s", upb_msgdef_fullname(intern->desc->msgdef), name); return; } field = upb_msg_whichoneof(intern->msg, oneof); RETURN_STRING(field ? upb_fielddef_name(field) : ""); } /** * Message::readOneof() * * Returns the contents of the given oneof field, given a field number. Called * from generated code methods such as: * * public function getDoubleValueOneof() * { * return $this->readOneof(10); * } * * @return object The oneof's field value. */ PHP_METHOD(Message, readOneof) { Message* intern = (Message*)Z_OBJ_P(getThis()); zend_long field_num; const upb_fielddef* f; zval ret; if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &field_num) == FAILURE) { return; } f = upb_msgdef_itof(intern->desc->msgdef, field_num); if (!f || !upb_fielddef_realcontainingoneof(f)) { php_error_docref(NULL, E_USER_ERROR, "Internal error, no such oneof field %d\n", (int)field_num); } { upb_msgval msgval = upb_msg_get(intern->msg, f); const Descriptor *subdesc = Descriptor_GetFromFieldDef(f); Convert_UpbToPhp(msgval, &ret, upb_fielddef_type(f), subdesc, &intern->arena); } RETURN_ZVAL(&ret, 1, 0); } /** * Message::writeOneof() * * Sets the contents of the given oneof field, given a field number. Called * from generated code methods such as: * * public function setDoubleValueOneof($var) * { * GPBUtil::checkMessage($var, \Google\Protobuf\DoubleValue::class); * $this->writeOneof(10, $var); * * return $this; * } * * The C extension version of GPBUtil::check*() does nothing, so we perform * all type checking and conversion here. * * @param integer The field number we are setting. * @param object The field value we want to set. */ PHP_METHOD(Message, writeOneof) { Message* intern = (Message*)Z_OBJ_P(getThis()); zend_long field_num; const upb_fielddef* f; upb_arena *arena = Arena_Get(&intern->arena); upb_msgval msgval; zval* val; if (zend_parse_parameters(ZEND_NUM_ARGS(), "lz", &field_num, &val) == FAILURE) { return; } f = upb_msgdef_itof(intern->desc->msgdef, field_num); if (!Convert_PhpToUpb(val, &msgval, upb_fielddef_type(f), Descriptor_GetFromFieldDef(f), arena)) { return; } upb_msg_set(intern->msg, f, msgval, arena); } static zend_function_entry Message_methods[] = { PHP_ME(Message, clear, NULL, ZEND_ACC_PUBLIC) PHP_ME(Message, discardUnknownFields, NULL, ZEND_ACC_PUBLIC) PHP_ME(Message, serializeToString, NULL, ZEND_ACC_PUBLIC) PHP_ME(Message, mergeFromString, NULL, ZEND_ACC_PUBLIC) PHP_ME(Message, serializeToJsonString, NULL, ZEND_ACC_PUBLIC) PHP_ME(Message, mergeFromJsonString, NULL, ZEND_ACC_PUBLIC) PHP_ME(Message, mergeFrom, NULL, ZEND_ACC_PUBLIC) PHP_ME(Message, readWrapperValue, NULL, ZEND_ACC_PROTECTED) PHP_ME(Message, writeWrapperValue, NULL, ZEND_ACC_PROTECTED) PHP_ME(Message, readOneof, NULL, ZEND_ACC_PROTECTED) PHP_ME(Message, writeOneof, NULL, ZEND_ACC_PROTECTED) PHP_ME(Message, whichOneof, NULL, ZEND_ACC_PROTECTED) PHP_ME(Message, __construct, NULL, ZEND_ACC_PROTECTED) ZEND_FE_END }; /** * Message_ModuleInit() * * Called when the C extension is loaded to register all types. */ void Message_ModuleInit() { zend_class_entry tmp_ce; zend_object_handlers *h = &message_object_handlers; INIT_CLASS_ENTRY(tmp_ce, "Google\\Protobuf\\Internal\\Message", Message_methods); message_ce = zend_register_internal_class(&tmp_ce); message_ce->create_object = Message_create; memcpy(h, &std_object_handlers, sizeof(zend_object_handlers)); h->dtor_obj = Message_dtor; h->read_property = Message_read_property; h->write_property = Message_write_property; h->get_properties = Message_get_properties; h->get_property_ptr_ptr = Message_get_property_ptr_ptr; }