From c028e8f932cc4206ab8446409693fba8dfe18ffb Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 4 Mar 2014 18:21:58 +0100 Subject: Php::Value and Php::Object classes can now be used to wrap around Php::Base objects --- Examples/CppClassesInPhp/cppclassinphp.cpp | 19 +++- Examples/CppClassesInPhp/cppclassinphp.php | 12 +++ include/base.h | 115 ++++++++++++++---------- include/classbase.h | 60 +++++-------- include/object.h | 27 ++++++ include/value.h | 6 +- src/base.cpp | 81 +++++++++++++++++ src/classbase.cpp | 138 ++++++++--------------------- src/object.cpp | 30 +++++++ src/value.cpp | 28 +++++- 10 files changed, 318 insertions(+), 198 deletions(-) create mode 100644 src/base.cpp diff --git a/Examples/CppClassesInPhp/cppclassinphp.cpp b/Examples/CppClassesInPhp/cppclassinphp.cpp index 20c04c4..4c3d59f 100644 --- a/Examples/CppClassesInPhp/cppclassinphp.cpp +++ b/Examples/CppClassesInPhp/cppclassinphp.cpp @@ -19,7 +19,12 @@ private: public: MyCustomClass() { - std::cout << "MyCustomClass::MyCustomClass" << std::endl; + std::cout << "MyCustomClass::MyCustomClass()" << std::endl; + } + + MyCustomClass(int value) : _x(value) + { + std::cout << "MyCustomClass::MyCustomClass(" << value << ")" << std::endl; } MyCustomClass(const MyCustomClass &that) @@ -52,7 +57,14 @@ public: // check number of parameters if (params.size() != 1) throw Php::Exception("Invalid number of parameters supplied"); - std::cout << "myMethod is called." << std::endl; + std::cout << "myMethod is called for object " << _x << std::endl; + + + return Php::Object("MyClass", new MyCustomClass(100)); + + + return false; + // create a new PHP DateTime object representing the current time Php::Object now("DateTime", "now"); @@ -67,6 +79,9 @@ public: std::cout << "return " << params[0] << std::endl; + + + // return it return obj; diff --git a/Examples/CppClassesInPhp/cppclassinphp.php b/Examples/CppClassesInPhp/cppclassinphp.php index 7fd4f64..1c60878 100644 --- a/Examples/CppClassesInPhp/cppclassinphp.php +++ b/Examples/CppClassesInPhp/cppclassinphp.php @@ -20,6 +20,18 @@ class TestClass //create a MyCustomClass object, which is an object of a C++ class $object1 = new MyClass(); + +$object2 = $object1->myMethod(1); +$object2->myMethod(1); + +echo("unset\n"); + +unset($object1); + +echo("got here\n"); + +return; + //echo("prop x: ".$object1->x."\n"); $object1->x = 10; diff --git a/include/base.h b/include/base.h index 46f5678..b027d5a 100644 --- a/include/base.h +++ b/include/base.h @@ -10,6 +10,11 @@ */ namespace Php { +/** + * Forward declarations + */ +class MixedObject; + /** * Class definition */ @@ -22,109 +27,121 @@ protected: Base() {} public: - - // @todo should we delete the copy and move operators because we do not - // allow the CPP code to make copies of itself? - - /** * Virtual destructor */ virtual ~Base() {} - /** - * Convert the object to a Php::Value object (how it is used externally) - * @return Object - */ -// Object value(); - - /** - * Convert the object to a Php::Value object (how it is used externally) - * @return Object - */ -// Object value() const; - /** * Get access to a property by name using the [] operator * @param string * @return HashMember */ -// HashMember operator[](const char *name) -// { -// return value()[name]; -// } + HashMember operator[](const char *name) + { + return Value(this)[name]; + } /** * Alternative way to access a property using the [] operator * @param string * @return HashMember */ -// HashMember operator[](const std::string &name) -// { -// return value()[name]; -// } + HashMember operator[](const std::string &name) + { + return Value(this)[name]; + } /** * Retrieve a property by name * @param string * @return HashMember */ -// HashMember property(const char *name) -// { -// return value()[name]; -// } + HashMember property(const char *name) + { + return Value(this)[name]; + } /** * Retrieve a property by name * @param string * @return HashMember */ -// HashMember property(const std::string &name) -// { -// return value()[name]; -// } + HashMember property(const std::string &name) + { + return Value(this)[name]; + } /** * Get access to a property by name using the [] operator * @param string * @return Value */ -// Value operator[](const char *name) const -// { -// return value()[name]; -// } + Value operator[](const char *name) const + { + return Value(this)[name]; + } /** * Alternative way to access a property using the [] operator * @param string * @return Value */ -// Value operator[](const std::string &name) const -// { -// return value()[name]; -// } + Value operator[](const std::string &name) const + { + return Value(this)[name]; + } /** * Retrieve a property by name * @param string * @return Value */ -// Value property(const char *name) const -// { -// return value()[name]; -// } + Value property(const char *name) const + { + return Value(this)[name]; + } /** * Retrieve a property by name * @param string * @return Value */ -// Value property(const std::string &name) const -// { -// return value()[name]; -// } + Value property(const std::string &name) const + { + return Value(this)[name]; + } private: + /** + * Store the object in the zend object cache + * @param entry + * @return MixedObject + */ + MixedObject *store(struct _zend_class_entry *entry); + + /** + * Retrieve the handle + * @return int + */ + int handle() const + { + return _handle; + } + + /** + * The handle in the zend object cache + * @var int + */ + int _handle = 0; + + /** + * Friends that have access to the private members + */ + friend class Value; + friend class Object; + friend class ClassBase; + }; diff --git a/include/classbase.h b/include/classbase.h index a380840..8a691fa 100644 --- a/include/classbase.h +++ b/include/classbase.h @@ -14,6 +14,11 @@ * @copyright 2014 Copernica BV */ +/** + * Forward declarations + */ +struct _zend_object_value; + /** * Set up namespace */ @@ -80,45 +85,6 @@ public: */ virtual ~ClassBase(); - /** - * Construct a new instance of the object - * @param value - * @return Base - */ - Base* construct(const struct _zend_object_value &value) - { - // construct the base - auto *result = construct(); - if (!result) return nullptr; - - // assign the zend object to it - // @todo fix this -// result->assign(value); - - // 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 @@ -144,6 +110,7 @@ public: * @param ns Namespace name */ void initialize(const std::string &ns); + protected: /** @@ -231,6 +198,21 @@ private: */ const struct _zend_function_entry *entries(); + /** + * Static member functions to clone objects based on this class + * @param val The object to be cloned + * @return zend_object_value Object info + */ + static struct _zend_object_value cloneObject(struct _zval_struct *val); + + /** + * 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 struct _zend_object_value createObject(struct _zend_class_entry *entry); + /** * Name of the class * @var string diff --git a/include/object.h b/include/object.h index b3ea5bc..ff2f7c1 100644 --- a/include/object.h +++ b/include/object.h @@ -48,6 +48,33 @@ public: else operator=(value); } + /** + * Constructor to create a new instance of a builtin class + * + * You can use this constructor if you have created an instance of your + * own class, but has not assigned it to a variable yet. This happens + * for example for classes that are not constructed from PHP userspace, + * but from your own functions: + * + * Php::Value yourFunction() + * { + * return Php::Object("MyClass", new MyClass()); + * } + * + * When you construct objects like this, the __construct function is not + * going to be called. If you want to construct the object just as if it + * was constructed from PHP user space, do this: + * + * Php::Value yourFunction() + * { + * return Php::Object("MyClass"); + * } + * + * @param name Name of the class to instantiate + * @param base Implementation of the class + */ + Object(const char *name, Base *base); + /** * Constructor to create a new instance * diff --git a/include/value.h b/include/value.h index 8af95a5..d0079dc 100644 --- a/include/value.h +++ b/include/value.h @@ -67,10 +67,10 @@ public: Value(struct _zval_struct *zval, bool ref = false); /** - * Wrap around an object - * @param value The object value + * Wrap around an object implemented by us + * @param object Object to be wrapped */ - Value(const struct _zend_object_value &value); + Value(Base *base); /** * Copy constructor diff --git a/src/base.cpp b/src/base.cpp new file mode 100644 index 0000000..a96a6ce --- /dev/null +++ b/src/base.cpp @@ -0,0 +1,81 @@ +/** + * Base.cpp + * + * Implementation file for the base of all classes + * + * @copyright 2014 Copernica BV + */ +#include "includes.h" + +/** + * Set up namespace + */ +namespace Php { + +/** + * Function that is called to clean up space that is occupied by the object + * @param object The object to be deallocated + */ +static void deallocate_object(void *object TSRMLS_DC) +{ + // allocate memory for the object + MixedObject *obj = (MixedObject *)object; + + // deallocate the cpp object + if (obj->cpp) delete obj->cpp; + + // get rid of the object properties + // @todo if we enable the following two lines, segmentation + // faults and memory corruption occurs. however, the online + // documentation does it like this + //zend_hash_destroy(obj->php.properties); + //FREE_HASHTABLE(obj->php.properties); + + // deallocate the entire object + efree(obj); +} + +/** + * Store the object in the PHP object cache + * @param entry Class entry + * @return MixedObject + */ +MixedObject *Base::store(zend_class_entry *entry) +{ + // allocate memory for the object + MixedObject *result = (MixedObject *)emalloc(sizeof(MixedObject)); + + // store the new c++ object + result->cpp = this; + + // 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(result->php.properties); + + // initialize the hash table + zend_hash_init(result->php.properties, 0, NULL, ZVAL_PTR_DTOR, 0); + + // initialize the properties + zend_hash_copy(result->php.properties, &entry->default_properties, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval*)); +#else + // version higher than 5.3 have an easier way to initialize + object_properties_init(&result->php, entry); +#endif + + // the destructor and clone handlers are set to NULL. I dont know why, but they do not + // seem to be necessary... + _handle = zend_objects_store_put(result, NULL, deallocate_object, NULL TSRMLS_CC); + + // done + return result; +} + +/** + * End namespace + */ +} + diff --git a/src/classbase.cpp b/src/classbase.cpp index debfa7d..f9b58d0 100644 --- a/src/classbase.cpp +++ b/src/classbase.cpp @@ -13,29 +13,6 @@ */ namespace Php { -/** - * Function that is called to clean up space that is occupied by the object - * @param object The object to be deallocated - */ -static void deallocate_object(void *object TSRMLS_DC) -{ - // allocate memory for the object - MixedObject *obj = (MixedObject *)object; - - // deallocate the cpp object - if (obj->cpp) delete obj->cpp; - - // get rid of the object properties - // @todo if we enable the following two lines, segmentation - // faults and memory corruption occurs. however, the online - // documentation does it like this - //zend_hash_destroy(obj->php.properties); - //FREE_HASHTABLE(obj->php.properties); - - // deallocate the entire object - efree(obj); -} - /** * Retrieve our C++ implementation object * @param entry @@ -60,95 +37,46 @@ static ClassBase *cpp_class(zend_class_entry *entry) 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 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(result->php.properties); - - // initialize the hash table - zend_hash_init(result->php.properties, 0, NULL, ZVAL_PTR_DTOR, 0); - - // initialize the properties - zend_hash_copy(result->php.properties, &entry->default_properties, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval*)); -#else - // 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) +zend_object_value ClassBase::cloneObject(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); + // we need the C++ class meta-information object + ClassBase *meta = cpp_class(entry); // retrieve the old object, which we are going to copy MixedObject *old_object = (MixedObject *)zend_object_store_get_object(val); + + // create a new base c++ object + auto *cpp = meta->clone(old_object->cpp); - // we need the C++ class meta-information object - ClassBase *meta = cpp_class(entry); - + // report error on failure + if (!cpp) throw Php::Exception(std::string("Unable to clone ") + entry->name); + // the thing we're going to return zend_object_value result; + // set the handlers + result.handlers = zend_get_std_object_handlers(); + + // we need a special handler for cloning + result.handlers->clone_obj = &ClassBase::cloneObject; + // store the object - store(new_object, &result); + MixedObject *new_object = cpp->store(entry); + // store the object in the object cache + result.handle = cpp->handle(); + // 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); - // done return result; } @@ -159,24 +87,32 @@ static zend_object_value clone_object(zval *val TSRMLS_DC) * @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) +zend_object_value ClassBase::createObject(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); + // create a new base C++ object + auto *cpp = meta->construct(); + + // report error on failure + if (!cpp) throw Php::Exception(std::string("Unable to instantiate ") + entry->name); + // the thing we're going to return zend_object_value result; - // store the object - store(new_object, &result); + // set the handlers + result.handlers = zend_get_std_object_handlers(); + + // we need a special handler for cloning + result.handlers->clone_obj = ClassBase::cloneObject; - // finally, construct the cpp object - // @todo call this earlier because it could fail - new_object->cpp = meta->construct(result); + // store the object + cpp->store(entry); + // store the object in the object cache + result.handle = cpp->handle(); + // done return result; } @@ -257,7 +193,7 @@ void ClassBase::initialize(const std::string &prefix) INIT_CLASS_ENTRY_EX(entry, _name.c_str(), _name.size(), entries()); // we need a special constructor - entry.create_object = create_object; + entry.create_object = &ClassBase::createObject; // register the class _entry = zend_register_internal_class(&entry TSRMLS_CC); diff --git a/src/object.cpp b/src/object.cpp index aa99c0d..f1bcdf8 100644 --- a/src/object.cpp +++ b/src/object.cpp @@ -11,6 +11,36 @@ */ namespace Php { +/** + * Constructor to create a new instance of a builtin class + * + * @param name Name of the class to instantiate + * @param base Implementation of the class + */ +Object::Object(const char *name, Base *base) +{ + // does the object already have a handle? + if (base->handle()) + { + // the object is already instantiated, we can assign it the this object + operator=(Value(base)); + } + else + { + // this is a brand new object that should be allocated, the C++ instance + // is already there (created by the extension) but it is not yet stored + // in PHP, find out the classname first + auto *entry = zend_fetch_class(name, strlen(name), 0); + if (!entry) throw Php::Exception(std::string("Unknown class name ") + name); + + // store the object in the php object cache (this will give the object a handle) + base->store(entry); + + // now we can store it + operator=(Value(base)); + } +} + /** * Internal method to instantiate an object * @param name diff --git a/src/value.cpp b/src/value.cpp index 13c4d88..b43d72f 100644 --- a/src/value.cpp +++ b/src/value.cpp @@ -171,14 +171,34 @@ Value::Value(struct _zval_struct *val, bool ref) /** * Wrap around an object - * @param value The object value + * @param object */ -Value::Value(const struct _zend_object_value &value) +Value::Value(Base *object) { - // make a normal zval + // there are two options: the object was constructed from user space, + // and is already linked to a handle, or it was constructed from C++ + // space, and no handle does yet exist + int handle = object->handle(); + + // do we have a handle? + if (!handle) throw Php::Exception("Assigning an unassigned object to a variable"); + + // make a regular zval, and set it to an object MAKE_STD_ZVAL(_val); Z_TYPE_P(_val) = IS_OBJECT; - Z_OBJVAL_P(_val) = value; + Z_OBJ_HANDLE_P(_val) = handle; + + // we have to lookup the object in the object-table + zend_object_store_bucket *obj_bucket = &EG(objects_store).object_buckets[handle]; + + // this is copy-pasted from zend_objects.c - and it is necessary too! + if (!obj_bucket->bucket.obj.handlers) obj_bucket->bucket.obj.handlers = &std_object_handlers; + + // store the handlers in the zval too (cast is necessary for php 5.3) + Z_OBJ_HT_P(_val) = (zend_object_handlers*)obj_bucket->bucket.obj.handlers; + + // run the copy constructor + zval_copy_ctor(_val); } /** -- cgit v1.2.3