From 200952ad4004f6ee5527598622505adbe84df8af Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 4 Mar 2014 15:11:46 +0100 Subject: implemented cloning of objects --- Examples/CppClassesInPhp/cppclassinphp.cpp | 9 +- Examples/CppClassesInPhp/cppclassinphp.php | 18 +++- include/class.h | 14 +++ include/classbase.h | 27 +++++ include/interface.h | 11 ++ src/classbase.cpp | 156 +++++++++++++++++++++-------- 6 files changed, 190 insertions(+), 45 deletions(-) diff --git a/Examples/CppClassesInPhp/cppclassinphp.cpp b/Examples/CppClassesInPhp/cppclassinphp.cpp index 95f909b..20c04c4 100644 --- a/Examples/CppClassesInPhp/cppclassinphp.cpp +++ b/Examples/CppClassesInPhp/cppclassinphp.cpp @@ -14,14 +14,17 @@ using namespace std; class MyCustomClass : public Php::Base // , public Php::Countable { private: - int _x; + int _x = 3; public: MyCustomClass() { - _x = 3; std::cout << "MyCustomClass::MyCustomClass" << std::endl; - std::cout << _x << std::endl; + } + + MyCustomClass(const MyCustomClass &that) + { + std::cout << "MyCustomClass::MyCustomClass copy constructor" << std::endl; } virtual ~MyCustomClass() diff --git a/Examples/CppClassesInPhp/cppclassinphp.php b/Examples/CppClassesInPhp/cppclassinphp.php index c969a4c..7fd4f64 100644 --- a/Examples/CppClassesInPhp/cppclassinphp.php +++ b/Examples/CppClassesInPhp/cppclassinphp.php @@ -18,7 +18,23 @@ class TestClass } //create a MyCustomClass object, which is an object of a C++ class -$object = new MyClass(); +$object1 = new MyClass(); + +//echo("prop x: ".$object1->x."\n"); + +$object1->x = 10; +$object1->y = 20; + +echo("prop x: ".$object1->x."\n"); +echo("prop y: ".$object1->y."\n"); + +$object2 = clone $object1; + +echo("prop x: ".$object2->x."\n"); +echo("prop y: ".$object2->y."\n"); + + +return; // run a function of the class $obj = $object->myMethod("MyClass"); diff --git a/include/class.h b/include/class.h index 8bfcad4..7d82441 100644 --- a/include/class.h +++ b/include/class.h @@ -138,6 +138,20 @@ private: return new T(); } + /** + * Construct a clone + * @param orig + * @return Base + */ + virtual Base *clone(Base *orig) override + { + // cast to the original object + T *t = (T *)orig; + + // construct a new base by calling the copy constructor + return new T(*t); + } + /** * Namespaces have access to the private base class */ diff --git a/include/classbase.h b/include/classbase.h index e23c579..a380840 100644 --- a/include/classbase.h +++ b/include/classbase.h @@ -98,12 +98,39 @@ public: // done return result; } + + /** + * Construct a clone of the object + * @param orig + * @param value + * @return Base + */ + Base* clone(Base *orig, const struct _zend_object_value &value) + { + // construct the base + auto *result = clone(orig); + if (!result) return nullptr; + + // assign the zend object to it + // @todo fix this +// result->assign(value); + + // done + return result; + } /** * Construct a new instance of the object * @return Base */ virtual Base* construct() = 0; + + /** + * Create a clone of an object + * @param orig + * @return Base + */ + virtual Base *clone(Base *orig) = 0; /** * Initialize the class, given its name diff --git a/include/interface.h b/include/interface.h index f276d6f..b3031e9 100644 --- a/include/interface.h +++ b/include/interface.h @@ -49,6 +49,17 @@ private: return nullptr; } + /** + * Construct a clone of the object + * @param orig + * @return Base + */ + virtual Base* clone(Base *orig) override + { + // this does not occur for interfaces + return nullptr; + } + /** * Namespaces have access to the private base class */ diff --git a/src/classbase.cpp b/src/classbase.cpp index af707c4..debfa7d 100644 --- a/src/classbase.cpp +++ b/src/classbase.cpp @@ -37,71 +37,145 @@ static void deallocate_object(void *object TSRMLS_DC) } /** - * Function that is called to create space for a cloned object - * @param object The object to be cloned - * @param clone The address that should become the clone - */ -static void clone_object(void *object, void **clone TSRMLS_DC) -{ - // @todo implementation -} - -/** - * Function that is called when an instance of the class needs to be created. - * This function will create the C++ class, and the PHP object - * @param type Pointer to the class information - * @return zend_object_value The newly created object + * Retrieve our C++ implementation object + * @param entry + * @return ClassBase */ -static zend_object_value create_object(zend_class_entry *type TSRMLS_DC) +static ClassBase *cpp_class(zend_class_entry *entry) { - // allocate memory for the object - MixedObject *object = (MixedObject *)emalloc(sizeof(MixedObject)); + // we need the base class (in user space the class may have been overridden, + // but we are not interested in these user space classes) + while (entry->parent) entry = entry->parent; - // find base object (because the class may have been extended in user space) - zend_class_entry *base = type; - while (base->parent) base = base->parent; - - // retrieve the classinfo object #if PHP_VERSION_ID >= 50400 - const char *comment = base->info.user.doc_comment; + // retrieve the comment (it has a pointer hidden in it to the ClassBase object) + const char *comment = entry->info.user.doc_comment; #else - const char *comment = base->doc_comment; + // retrieve the comment php5.3 style (it has a pointer hidden in it to the ClassBase object) + const char *comment = entry->doc_comment; #endif // the first byte of the comment is an empty string (null character), but - // the next bytes contain a pointer to the ClassInfo class - ClassBase *info = *((ClassBase **)(comment + 1)); + // the next bytes contain a pointer to the ClassBase class + return *((ClassBase **)(comment + 1)); +} + +/** + * Create an object based on a certain class entry + * @param entry + * @return MixedObject + */ +static MixedObject *allocate_object(zend_class_entry *entry) +{ + // allocate memory for the object + MixedObject *result = (MixedObject *)emalloc(sizeof(MixedObject)); - // store the class - object->php.ce = type; + // store the class entry in the newly created object + result->php.ce = entry; +#if PHP_VERSION_ID < 50399 // the original create_object fills the initial object with the default properties, // we're going to do exactly the same. start with setting up a hashtable for the props - ALLOC_HASHTABLE(object->php.properties); + ALLOC_HASHTABLE(result->php.properties); // initialize the hash table - zend_hash_init(object->php.properties, 0, NULL, ZVAL_PTR_DTOR, 0); + zend_hash_init(result->php.properties, 0, NULL, ZVAL_PTR_DTOR, 0); - // initialize the properties -#if PHP_VERSION_ID < 50399 - zend_hash_copy(object->php.properties, &(type->default_properties), - (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval*)); + zend_hash_copy(result->php.properties, &entry->default_properties, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval*)); #else - object_properties_init(&(object->php), type); + // version higher than 5.3 have an easier way to initialize + object_properties_init(&result->php, entry); #endif + + // done + return result; +} + +/** + * Forward declaration + */ +static zend_object_value clone_object(zval *val TSRMLS_DC); + +/** + * Store the mixed object in the PHP object cache + * @param object The object to store + * @param value The zend_object_value to initialize + */ +static void store(MixedObject *object, zend_object_value *value) +{ + // set the handlers + value->handlers = zend_get_std_object_handlers(); + + // we need a special handler for cloning + value->handlers->clone_obj = clone_object; + + // the destructor and clone handlers are set to NULL. I dont know why, but they do not + // seem to be necessary... + value->handle = zend_objects_store_put(object, NULL, deallocate_object, NULL TSRMLS_CC); +} + +/** + * Function that is called to create space for a cloned object + * @param val The object to be cloned + * @return zend_obejct_value The object to be created + */ +static zend_object_value clone_object(zval *val TSRMLS_DC) +{ + // @todo refactoring because there is a lot of double code here + + // retrieve the class entry linked to this object + auto *entry = zend_get_class_entry(val); + + // allocate memory for the new object + MixedObject *new_object = allocate_object(entry); + // retrieve the old object, which we are going to copy + MixedObject *old_object = (MixedObject *)zend_object_store_get_object(val); + + // we need the C++ class meta-information object + ClassBase *meta = cpp_class(entry); + // the thing we're going to return zend_object_value result; + + // store the object + store(new_object, &result); + + // clone the members + zend_objects_clone_members(&new_object->php, result, &old_object->php, Z_OBJ_HANDLE_P(val)); + + // finally, construct the cpp object + // @todo call this earlier, because it could fail + new_object->cpp = meta->clone(old_object->cpp, result); - // use default object handlers - result.handlers = zend_get_std_object_handlers(); + // done + return result; +} + +/** + * Function that is called when an instance of the class needs to be created. + * This function will create the C++ class, and the PHP object + * @param entry Pointer to the class information + * @return zend_object_value The newly created object + */ +static zend_object_value create_object(zend_class_entry *entry TSRMLS_DC) +{ + // allocate memory for the object + MixedObject *new_object = allocate_object(entry); + + // we need the C++ class meta-information object + ClassBase *meta = cpp_class(entry); + + // the thing we're going to return + zend_object_value result; - // put the object in the storage, and assign a method for deallocating and cloning - result.handle = zend_objects_store_put(object, NULL, deallocate_object, clone_object TSRMLS_CC); + // store the object + store(new_object, &result); // finally, construct the cpp object - object->cpp = info->construct(result); + // @todo call this earlier because it could fail + new_object->cpp = meta->construct(result); // done return result; @@ -115,7 +189,7 @@ ClassBase::~ClassBase() // destruct the entries if (_entries) delete[] _entries; - // php 5.3 deallocated the doc_comment by iself + // php 5.3 deallocates the doc_comment by iself #if PHP_VERSION_ID >= 50400 if (_comment) free(_comment); #endif -- cgit v1.2.3