summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorEmiel Bruijntjes <emiel.bruijntjes@copernica.com>2014-04-02 22:25:45 +0200
committerEmiel Bruijntjes <emiel.bruijntjes@copernica.com>2014-04-02 22:25:45 +0200
commitbbdcdae98979e002476fc1e8296effd0b270928b (patch)
tree8634ff596c4209296a36dd7f8c54d00a8ec29478 /src
parent735ec67aeb40e04a2cf47fa7216b2030c66b37fd (diff)
refactored the class and classbase classes, and introduced a classimpl class in the src directory, this is a first step to remove all zend-specific code from the header files, so that we can later have a hhvm backend for the php-cpp library
Diffstat (limited to 'src')
-rw-r--r--src/base.cpp4
-rw-r--r--src/classbase.cpp1854
-rw-r--r--src/classimpl.cpp1444
-rw-r--r--src/classimpl.h407
-rw-r--r--src/includes.h3
-rw-r--r--src/namespace.cpp2
6 files changed, 1894 insertions, 1820 deletions
diff --git a/src/base.cpp b/src/base.cpp
index 75363af..d415845 100644
--- a/src/base.cpp
+++ b/src/base.cpp
@@ -63,8 +63,8 @@ MixedObject *Base::store(zend_class_entry *entry TSRMLS_DC)
#endif
// store the two destruct methods in temporary vars
- DestructType *destructMethod = &ClassBase::destructObject;
- FreeType *freeMethod = &ClassBase::freeObject;
+ DestructType *destructMethod = &ClassImpl::destructObject;
+ FreeType *freeMethod = &ClassImpl::freeObject;
// the destructor and clone handlers are set to NULL. I dont know why, but they do not
// seem to be necessary...
diff --git a/src/classbase.cpp b/src/classbase.cpp
index afb37aa..037661e 100644
--- a/src/classbase.cpp
+++ b/src/classbase.cpp
@@ -14,1410 +14,27 @@
namespace Php {
/**
- * Retrieve our C++ implementation object
- * @param entry
- * @return ClassBase
- */
-static ClassBase *cpp_class(zend_class_entry *entry)
-{
- // 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;
-
-#if PHP_VERSION_ID >= 50400
- // retrieve the comment (it has a pointer hidden in it to the ClassBase object)
- const char *comment = entry->info.user.doc_comment;
-#else
- // 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 ClassBase class
- return *((ClassBase **)(comment + 1));
-}
-
-/**
- * Retrieve the CPP object
- * @param val
- * @return Base
- */
-static Base *cpp_object(const zval *val TSRMLS_DC)
-{
- // retrieve the old object, which we are going to copy
- MixedObject *object = (MixedObject *)zend_object_store_get_object(val TSRMLS_CC);
-
- // return the cpp object
- return object->cpp;
-}
-
-/**
* Protected constructor
* @param classname Class name
* @param flags Class flags
*/
-ClassBase::ClassBase(const char *classname, int flags) : _name(classname)
+ClassBase::ClassBase(const char *classname, int flags)
{
// the flags hold a method-flag-value, this should be converted into a class-type
- if (flags & Abstract) _type = ClassType::Abstract;
- if (flags & Final) _type = ClassType::Final;
-}
-
-/**
- * Destructor
- */
-ClassBase::~ClassBase()
-{
- // destruct the entries
- if (_entries) delete[] _entries;
-
- // php 5.3 deallocates the doc_comment by iself
-#if PHP_VERSION_ID >= 50400
- if (_comment) free(_comment);
-#endif
-}
-
-/**
- * Extended zend_internal_function structure that we use to store an
- * instance of the ClassBase object. We need this for static method calls
- */
-struct CallData
-{
- // the internal function is the first member, so
- // that it is possible to cast an instance of this
- // struct to a zend_internal_function
- zend_internal_function func;
-
- // and a pointer to the ClassBase object
- ClassBase *self;
-};
-
-/**
- * Handler function that runs the __call function
- * @param ... All normal parameters for function calls
- */
-void ClassBase::callMethod(INTERNAL_FUNCTION_PARAMETERS)
-{
- // retrieve the originally called (and by us allocated) function object
- // (this was copied from the zend engine source code, code looks way too
- // ugly to be made by me)
- CallData *data = (CallData *)EG(current_execute_data)->function_state.function;
- zend_internal_function *func = &data->func;
-
- // retrieve the function name
- const char *name = func->function_name;
- ClassBase *meta = data->self;
-
- // the data structure was allocated by ourselves in the getMethod or
- // getStaticMethod functions, we no longer need it now
- efree(data);
-
- // the function could throw an exception
- try
- {
- // wrap the return value
- Value result(return_value, true);
-
- // construct parameters
- Parameters params(this_ptr, ZEND_NUM_ARGS() TSRMLS_CC);
-
- // retrieve the base object
- Base *base = params.object();
-
- // is this a static, or a non-static call?
- if (base) result = meta->callCall(base, name, params);
- else result = meta->callCallStatic(name, params);
- }
- catch (const NotImplemented &exception)
- {
- // because of the two-step nature, we are going to report the error ourselves
- zend_error(E_ERROR, "Undefined method %s", name);
- }
- catch (Exception &exception)
- {
- // process the exception
- exception.process(TSRMLS_C);
- }
-}
-
-/**
- * Handler function that runs the __invoke function
- * @param ... All normal parameters for function calls
- */
-void ClassBase::callInvoke(INTERNAL_FUNCTION_PARAMETERS)
-{
- // retrieve the originally called (and by us allocated) function object
- // (this was copied from the zend engine source code, code looks way too
- // ugly to be made by me)
- CallData *data = (CallData *)EG(current_execute_data)->function_state.function;
-
- // get self reference
- ClassBase *meta = data->self;
-
- // the data structure was allocated by ourselves in the getMethod or
- // getStaticMethod functions, we no longer need it now
- efree(data);
-
- // the function could throw an exception
- try
- {
- // wrap the return value
- Value result(return_value, true);
-
- // construct parameters
- Parameters params(this_ptr, ZEND_NUM_ARGS() TSRMLS_CC);
-
- // retrieve the base object
- Base *base = params.object();
-
- // call the actual __invoke method on the base object
- result = meta->callInvoke(base, params);
- }
- catch (const NotImplemented &exception)
- {
- // because of the two-step nature, we are going to report the error ourselves
- zend_error(E_ERROR, "Function name must be a string");
-
- }
- catch (Exception &exception)
- {
- // process the exception
- exception.process(TSRMLS_C);
- }
-}
-
-/**
- * Method that returns the function definition of the __call function
- * @param object_ptr
- * @param method_name
- * @param method_len
- * @param tsrm_ls
- * @return zend_function
- */
-#if PHP_VERSION_ID < 50399
-zend_function *ClassBase::getMethod(zval **object_ptr, char *method_name, int method_len TSRMLS_DC)
-#else
-zend_function *ClassBase::getMethod(zval **object_ptr, char *method_name, int method_len, const struct _zend_literal *key TSRMLS_DC)
-#endif
-{
- // something strange about the Zend engine (once more). The structure with
- // object-handlers has a get_method and call_method member. When a function is
- // called, the get_method function is called first, to retrieve information
- // about the method (like the handler that should be called to execute it),
- // after that, this returned handler is also called. The call_method property
- // of the object_handlers structure however, never gets called. Typical.
-
- // first we'll check if the default handler does not have an implementation,
- // in that case the method is probably already implemented as a regular method
-#if PHP_VERSION_ID < 50399
- auto *defaultFunction = std_object_handlers.get_method(object_ptr, method_name, method_len TSRMLS_CC);
-#else
- auto *defaultFunction = std_object_handlers.get_method(object_ptr, method_name, method_len, key TSRMLS_CC);
-#endif
-
- // did the default implementation do anything?
- if (defaultFunction) return defaultFunction;
-
- // retrieve the class entry linked to this object
- auto *entry = zend_get_class_entry(*object_ptr TSRMLS_CC);
-
- // this is peculiar behavior of the zend engine, we first are going to dynamically
- // allocate memory holding all the properties of the __call method (we initially
- // had an implementation here that used a static variable, and that worked too,
- // but we'll follow thread safe implementation of the Zend engine here, although
- // it is strange to allocate and free memory in one and the same method call (free()
- // call happens in call_method())
- auto *data = (CallData *)emalloc(sizeof(CallData));
- auto *function = &data->func;
-
- // we're going to set all properties
- function->type = ZEND_INTERNAL_FUNCTION;
- function->module = nullptr;
- function->handler = &ClassBase::callMethod;
- function->arg_info = nullptr;
- function->num_args = 0;
- function->required_num_args = 0;
- function->scope = entry;
- function->fn_flags = ZEND_ACC_CALL_VIA_HANDLER;
- function->function_name = method_name;
-
- // store pointer to ourselves
- data->self = cpp_class(entry);
-
- // done (cast to zend_function* is allowed, because a zend_function is a union
- // that has one member being a zend_internal_function)
- return (zend_function *)data;
-}
-
-/**
- * Method that is called right before a static method call is attempted
- * @param entry
- * @param method
- * @param method_len
- * @param tsrm_ls
- * @return zend_function
- */
-zend_function *ClassBase::getStaticMethod(zend_class_entry *entry, char* method, int method_len TSRMLS_DC)
-{
- // first we'll check if the default handler does not have an implementation,
- // in that case the method is probably already implemented as a regular method
-#if PHP_VERSION_ID < 50399
- auto *defaultFunction = zend_std_get_static_method(entry, method, method_len TSRMLS_CC);
-#else
- auto *defaultFunction = zend_std_get_static_method(entry, method, method_len, nullptr TSRMLS_CC);
-#endif
-
- // did the default implementation do anything?
- if (defaultFunction) return defaultFunction;
-
- // just like we did in getMethod() (see comment there) we are going to dynamically
- // allocate data holding information about the function
- auto *data = (CallData *)emalloc(sizeof(CallData));
- auto *function = &data->func;
-
- // we're going to set all properties
- function->type = ZEND_INTERNAL_FUNCTION;
- function->module = nullptr;
- function->handler = ClassBase::callMethod;
- function->arg_info = nullptr;
- function->num_args = 0;
- function->required_num_args = 0;
- function->scope = nullptr;
- function->fn_flags = ZEND_ACC_CALL_VIA_HANDLER;
- function->function_name = method;
-
- // store pointer to ourselves
- data->self = cpp_class(entry);
-
- // done (cast to zend_function* is allowed, because a zend_function is a union
- // that has one member being a zend_internal_function)
- return (zend_function *)data;
-}
-
-/**
- * Method that returns the closure -- this is the __invoke handler!
- * @param object
- * @param entry_ptr
- * @param func
- * @param object_ptr
- * @param tsrm_ls
- * @return int
- */
-int ClassBase::getClosure(zval *object, zend_class_entry **entry_ptr, zend_function **func, zval **object_ptr TSRMLS_DC)
-{
- // it is really unbelievable how the Zend engine manages to implement every feature
- // in a complete different manner. You would expect the __invoke() and the
- // __call() functions not to be very different from each other. However, they
- // both have a completely different API. This getClosure method is supposed
- // to fill the function parameter with all information about the invoke()
- // method that is going to get called
-
- // retrieve the class entry linked to this object
- auto *entry = zend_get_class_entry(object TSRMLS_CC);
-
- // just like we did for getMethod(), we're going to dynamically allocate memory
- // with all information about the function
- auto *data = (CallData *)emalloc(sizeof(CallData));
- auto *function = &data->func;
-
- // we're going to set all properties
- function->type = ZEND_INTERNAL_FUNCTION;
- function->module = nullptr;
- function->handler = &ClassBase::callInvoke;
- function->arg_info = nullptr;
- function->num_args = 0;
- function->required_num_args = 0;
- function->scope = entry;
- function->fn_flags = ZEND_ACC_CALL_VIA_HANDLER;
- function->function_name = nullptr;
-
- // store pointer to ourselves
- data->self = cpp_class(entry);
-
- // assign this dynamically allocated variable to the func parameter
- // the case is ok, because zend_internal_function is a member of the
- // zend_function union
- *func = (zend_function *)data;
-
- // the object_ptr should be filled with the object on which the method is
- // called (otherwise the Zend engine tries to call the method statically)
- *object_ptr = object;
-
- // done
- return SUCCESS;
-};
-
-/**
- * Retrieve pointer to our own object handlers
- * @return zend_object_handlers
- */
-zend_object_handlers *ClassBase::objectHandlers()
-{
- // keep static structure
- static zend_object_handlers handlers;
-
- // is the object already initialized?
- static bool initialized = false;
-
- // already initialized?
- if (initialized) return &handlers;
-
- // initialize the handlers
- memcpy(&handlers, &std_object_handlers, sizeof(zend_object_handlers));
-
- // install custom clone function
- if (!clonable()) handlers.clone_obj = nullptr;
- else handlers.clone_obj = &ClassBase::cloneObject;
-
- // functions for the Countable interface
- handlers.count_elements = &ClassBase::countElements;
-
- // functions for the ArrayAccess interface
- handlers.write_dimension = &ClassBase::writeDimension;
- handlers.read_dimension = &ClassBase::readDimension;
- handlers.has_dimension = &ClassBase::hasDimension;
- handlers.unset_dimension = &ClassBase::unsetDimension;
-
- // functions for the magic properties handlers (__get, __set, __isset and __unset)
- handlers.write_property = &ClassBase::writeProperty;
- handlers.read_property = &ClassBase::readProperty;
- handlers.has_property = &ClassBase::hasProperty;
- handlers.unset_property = &ClassBase::unsetProperty;
-
- // when a method is called (__call and __invoke)
- handlers.get_method = &ClassBase::getMethod;
- handlers.get_closure = &ClassBase::getClosure;
-
- // handler to cast to a different type
- handlers.cast_object = &ClassBase::cast;
-
- // method to compare two objects
- handlers.compare_objects = &ClassBase::compare;
-
- // remember that object is now initialized
- initialized = true;
-
- // done
- return &handlers;
-}
-
-/**
- * Function to compare two objects
- * @param object1
- * @param object2
- * @param tsrm_ls
- * @return int
- */
-int ClassBase::compare(zval *object1, zval *object2 TSRMLS_DC)
-{
- // prevent exceptions
- try
- {
- // retrieve the class entry linked to this object
- auto *entry = zend_get_class_entry(object1 TSRMLS_CC);
-
- // other object must be of the same type
- if (entry != zend_get_class_entry(object2 TSRMLS_CC)) throw NotImplemented();
-
- // we need the C++ class meta-information object
- ClassBase *meta = cpp_class(entry);
-
- // get the base objects
- Base *base1 = cpp_object(object1 TSRMLS_CC);
- Base *base2 = cpp_object(object2 TSRMLS_CC);
-
- // run the compare method
- return meta->callCompare(base1, base2);
- }
- catch (const NotImplemented &exception)
- {
- // it was not implemented, do we have a default?
- if (!std_object_handlers.compare_objects) return 1;
-
- // call default
- return std_object_handlers.compare_objects(object1, object2 TSRMLS_CC);
- }
- catch (Exception &exception)
- {
- // a Php::Exception was thrown by the extension __compare function,
- // pass this on to user space
- exception.process(TSRMLS_C);
-
- // what shall we return here...
- return 1;
- }
-}
-
-/**
- * Function to cast the object to a different type
- * @param object
- * @param retval
- * @param type
- * @param tsrm_ls
- * @return int
- */
-int ClassBase::cast(zval *object, zval *retval, int type TSRMLS_DC)
-{
- // get the base object
- Base *base = cpp_object(object TSRMLS_CC);
-
- // retrieve the class entry linked to this object
- auto *entry = zend_get_class_entry(object TSRMLS_CC);
-
- // we need the C++ class meta-information object
- ClassBase *meta = cpp_class(entry);
-
- // retval it not yet initialized --- and again feelings of disbelief,
- // frustration, wonder and anger come up when you see that there are not two
- // functions in the Zend engine that have a comparable API
- INIT_PZVAL(retval);
-
- // wrap zval in value object
- Value result(retval, true);
-
- // when the magic function it not implemented, an exception will be thrown,
- // and the extension may throw a Php::Exception
- try
- {
- // the result zval
- zval *result = nullptr;
-
- // check type
- switch ((Type)type) {
- case Type::Numeric: result = meta->callToInteger(base).detach(); break;
- case Type::Float: result = meta->callToFloat(base).detach(); break;
- case Type::Bool: result = meta->callToBool(base).detach(); break;
- case Type::String: result = meta->callToString(base).detach(); break;
- default: throw NotImplemented(); break;
- }
-
- // @todo do we turn into endless conversion if the __toString object returns 'this' ??
- // (and if it does: who cares? If the extension programmer is stupid, why do we have to suffer?)
-
- // is the object overwritten?
- if (object == retval) zval_dtor(object);
-
- // overwrite the result
- ZVAL_ZVAL(retval, result, 1, 1);
-
- // done
- return SUCCESS;
- }
- catch (const NotImplemented &exception)
- {
- // is there a default?
- if (!std_object_handlers.cast_object) return FAILURE;
-
- // call default
- return std_object_handlers.cast_object(object, retval, type TSRMLS_CC);
- }
- catch (Exception &exception)
- {
- // pass on the exception to php userspace
- exception.process(TSRMLS_C);
-
- // done
- return FAILURE;
- }
-}
-
-/**
- * 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
- */
-zend_object_value ClassBase::cloneObject(zval *val TSRMLS_DC)
-{
- // retrieve the class entry linked to this object
- auto *entry = zend_get_class_entry(val TSRMLS_CC);
-
- // 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 TSRMLS_CC);
-
- // create a new base c++ object
- auto *cpp = meta->clone(old_object->cpp);
-
- // report error on failure (this does not occur because the cloneObject()
- // method is only installed as handler when we have seen that there is indeed
- // a copy constructor)
- 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 = meta->objectHandlers();
-
- // store the object
- MixedObject *new_object = cpp->store(entry TSRMLS_CC);
-
- // store the object in the object cache
- result.handle = cpp->handle();
-
- // clone the members (this will also call the __clone() function if the user
- // had registered that as a visible method)
- zend_objects_clone_members(&new_object->php, result, &old_object->php, Z_OBJ_HANDLE_P(val) TSRMLS_CC);
-
- // was a custom clone method installed? If not we call the magic c++ __clone method
- if (!entry->clone) meta->callClone(cpp);
-
- // done
- return result;
-}
-
-/**
- * Function that is used to count the number of elements in the object
- *
- * If the user has implemented the Countable interface, this method will
- * call the count() method
- *
- * @param val
- * @param count
- * @return int
- */
-int ClassBase::countElements(zval *object, long *count TSRMLS_DC)
-{
- // does it implement the countable interface?
- Countable *countable = dynamic_cast<Countable*>(cpp_object(object TSRMLS_CC));
-
- // if it does not implement the Countable interface, we rely on the default implementation
- if (countable)
- {
- // the user function may throw an exception that needs to be processed
- try
- {
- // call the count function
- *count = countable->count();
-
- // done
- return SUCCESS;
- }
- catch (Exception &exception)
- {
- // process the exception
- exception.process(TSRMLS_C);
-
- // unreachable
- return FAILURE;
- }
- }
- else
- {
- // Countable interface was not implemented, check if there is a default
- if (!std_object_handlers.count_elements) return FAILURE;
-
- // call default
- return std_object_handlers.count_elements(object, count TSRMLS_CC);
- }
-}
-
-/**
- * Function that is called when the object is used as an array in PHP
- *
- * This is the [] operator in PHP, and mapped to the offsetGet() method
- * of the ArrayAccess interface
- *
- * @param object The object on which it is called
- * @param offset The name of the property
- * @param type The type of the variable???
- * @param tsrm_ls
- * @return zval
- */
-zval *ClassBase::readDimension(zval *object, zval *offset, int type TSRMLS_DC)
-{
- // what to do with the type?
- //
- // the type parameter tells us whether the dimension was read in READ
- // mode, WRITE mode, READWRITE mode or UNSET mode.
- //
- // In 99 out of 100 situations, it is called in regular READ mode (value 0),
- // only when it is called from a PHP script that has statements like
- // $x =& $object["x"], $object["x"]["y"] = "something" or unset($object["x"]["y"]),
- // the type parameter is set to a different value.
- //
- // But we must ask ourselves the question what we should be doing with such
- // cases. Internally, the object most likely has a full native implementation,
- // and the property that is returned is just a string or integer or some
- // other value, that is temporary WRAPPED into a zval to make it accessible
- // from PHP. If someone wants to get a reference to such an internal variable,
- // that is in most cases simply impossible.
-
-
- // does it implement the arrayaccess interface?
- ArrayAccess *arrayaccess = dynamic_cast<ArrayAccess*>(cpp_object(object TSRMLS_CC));
-
- // if it does not implement the ArrayAccess interface, we rely on the default implementation
- if (arrayaccess)
- {
- // the C++ code may throw an exception
- try
- {
- // ArrayAccess is implemented, call function
- return toZval(arrayaccess->offsetGet(offset), type);
- }
- catch (Exception &exception)
- {
- // process the exception (send it to user space)
- exception.process(TSRMLS_C);
-
- // unreachable
- return Value(nullptr).detach();
- }
- }
- else
- {
- // ArrayAccess not implemented, check if there is a default handler
- if (!std_object_handlers.read_dimension) return nullptr;
-
- // call default
- return std_object_handlers.read_dimension(object, offset, type TSRMLS_CC);
- }
-}
-
-/**
- * Function that is called when the object is used as an array in PHP
- *
- * This is the [] operator in PHP, and mapped to the offsetSet() method
- * of the ArrayAccess interface
- *
- * @param object The object on which it is called
- * @param offset The name of the property
- * @param value The new value
- * @param tsrm_ls
- * @return zval
- */
-void ClassBase::writeDimension(zval *object, zval *offset, zval *value TSRMLS_DC)
-{
- // does it implement the arrayaccess interface?
- ArrayAccess *arrayaccess = dynamic_cast<ArrayAccess*>(cpp_object(object TSRMLS_CC));
-
- // if it does not implement the ArrayAccess interface, we rely on the default implementation
- if (arrayaccess)
- {
- // method may throw an exception
- try
- {
- // set the value
- arrayaccess->offsetSet(offset, value);
- }
- catch (Exception &exception)
- {
- // process the exception (send it to user space
- exception.process(TSRMLS_C);
- }
- }
- else
- {
- // ArrayAccess not interface, check if there is a default handler
- if (!std_object_handlers.write_dimension) return;
-
- // call the default
- std_object_handlers.write_dimension(object, offset, value TSRMLS_CC);
- }
-}
-
-/**
- * Function that is called when the object is used as an array in PHP
- *
- * This is the [] operator in PHP, and mapped to the offsetExists() method
- * of the ArrayAccess interface
- *
- * @param object The object on which it is called
- * @param member The member to check
- * @param check_empty Was this an isset() call, or an empty() call?
- * @param tsrm_ls
- * @return bool
- */
-int ClassBase::hasDimension(zval *object, zval *member, int check_empty TSRMLS_DC)
-{
- // does it implement the arrayaccess interface?
- ArrayAccess *arrayaccess = dynamic_cast<ArrayAccess*>(cpp_object(object TSRMLS_CC));
-
- // if it does not implement the ArrayAccess interface, we rely on the default implementation
- if (arrayaccess)
- {
- // user implemented callbacks could throw an exception
- try
- {
- // check if the member exists
- if (!arrayaccess->offsetExists(member)) return false;
-
- // we know for certain that the offset exists, but should we check
- // more, like whether the value is empty or not?
- if (!check_empty) return true;
-
- // the user wants to know if the property is empty
- return empty(arrayaccess->offsetGet(member));
- }
- catch (Exception &exception)
- {
- // process the exception (send it to user space)
- exception.process(TSRMLS_C);
-
- // unreachable
- return false;
- }
- }
- else
- {
- // ArrayAccess interface is not implemented, check if there is a default handler
- if (!std_object_handlers.has_dimension) return 0;
-
- // call default
- return std_object_handlers.has_dimension(object, member, check_empty TSRMLS_CC);
- }
-}
-
-/**
- * Function that is called when the object is used as an array in PHP
- *
- * This is the [] operator in PHP, and mapped to the offsetUnset() method
- * of the ArrayAccess interface
- *
- * @param object The object on which it is called
- * @param member The member to remove
- * @param tsrm_ls
- */
-void ClassBase::unsetDimension(zval *object, zval *member TSRMLS_DC)
-{
- // does it implement the arrayaccess interface?
- ArrayAccess *arrayaccess = dynamic_cast<ArrayAccess*>(cpp_object(object TSRMLS_CC));
-
- // if it does not implement the ArrayAccess interface, we rely on the default implementation
- if (arrayaccess)
- {
- // user implemented code could throw an exception
- try
- {
- // remove the member
- arrayaccess->offsetUnset(member);
- }
- catch (Exception &exception)
- {
- // process the exception (send it to user space)
- exception.process(TSRMLS_C);
- }
- }
- else
- {
- // ArrayAccess is not implemented, is a default handler available?
- if (!std_object_handlers.unset_dimension) return;
-
- // call the default
- std_object_handlers.unset_dimension(object, member TSRMLS_CC);
- }
-}
-
-/**
- * Helper method to turn a property into a zval
- * @param value
- * @param type
- * @return Value
- */
-zval *ClassBase::toZval(Value &&value, int type)
-{
- // because we do not want the value object to destruct the zval when
- // it falls out of scope, we detach the zval from it, if this is a regular
- // read operation we can do this right away
- if (type == 0) return value.detach();
-
- // this is a more complicated read operation, the scripts wants to get
- // deeper access to the returned value. This, however, is only possible
- // if the value has more than once reference (if it has a refcount of one,
- // the value object that we have here is the only instance of the zval,
- // and it is simply impossible to return a reference or so
- if (value.refcount() <= 1) return value.detach();
-
- // we're dealing with an editable zval, return a reference variable
- return Value(value.detach(), true).detach();
-}
-
-/**
- * Function that is called when a property is read
- * @param object
- * @param name
- * @param type
- * @param key
- * @param tsrm_ls
- * @return val
- */
-#if PHP_VERSION_ID < 50399
-zval *ClassBase::readProperty(zval *object, zval *name, int type TSRMLS_DC)
-#else
-zval *ClassBase::readProperty(zval *object, zval *name, int type, const struct _zend_literal *key TSRMLS_DC)
-#endif
-{
- // what to do with the type?
- //
- // the type parameter tells us whether the property was read in READ
- // mode, WRITE mode, READWRITE mode or UNSET mode.
- //
- // In 99 out of 100 situations, it is called in regular READ mode (value 0),
- // only when it is called from a PHP script that has statements like
- // $x =& $object->x, $object->x->y = "something" or unset($object->x->y)
- // the type parameter is set to a different value.
- //
- // But we must ask ourselves the question what we should be doing with such
- // cases. Internally, the object most likely has a full native implementation,
- // and the property that is returned is just a string or integer or some
- // other value, that is temporary WRAPPED into a zval to make it accessible
- // from PHP. If someone wants to get a reference to such an internal variable,
- // that is in most cases simply impossible.
-
- // retrieve the object and class
- Base *base = cpp_object(object TSRMLS_CC);
-
- // retrieve the class entry linked to this object
- auto *entry = zend_get_class_entry(object TSRMLS_CC);
-
- // we need the C++ class meta-information object
- ClassBase *meta = cpp_class(entry);
-
- // the default implementation throws an exception, so by catching
- // the exception we know if the object was implemented by the user or not
- try
- {
- // convert name to a Value object
- Value key(name);
-
- // is it a property with a callback?
- auto iter = meta->_properties.find(key);
-
- // was it found?
- if (iter == meta->_properties.end())
- {
- // retrieve value from the __get method
- return toZval(meta->callGet(base, key), type);
- }
- else
- {
- // get the value
- return toZval(iter->second->get(base), type);
- }
- }
- catch (const NotImplemented &exception)
- {
- // __get() function was not overridden by the user
- if (!std_object_handlers.read_property) return nullptr;
-
- // call default
-#if PHP_VERSION_ID < 50399
- return std_object_handlers.read_property(object, name, type TSRMLS_CC);
-#else
- return std_object_handlers.read_property(object, name, type, key TSRMLS_CC);
-#endif
- }
- catch (Exception &exception)
- {
- // user threw an exception in its magic method
- // implementation, send it to user space
- exception.process(TSRMLS_C);
-
- // unreachable
- return Value(nullptr).detach();
- }
-}
-
-/**
- * Function that is called when a property is set / updated
- *
- * This is the handler for the __set() function, and is called when a property
- * is updated.
- *
- * @param object The object on which it is called
- * @param name The name of the property
- * @param value The new value
- * @param key ???
- * @param tsrm_ls
- * @return zval
- */
-#if PHP_VERSION_ID < 50399
-void ClassBase::writeProperty(zval *object, zval *name, zval *value TSRMLS_DC)
-#else
-void ClassBase::writeProperty(zval *object, zval *name, zval *value, const struct _zend_literal *key TSRMLS_DC)
-#endif
-{
- // retrieve the object and class
- Base *base = cpp_object(object TSRMLS_CC);
-
- // retrieve the class entry linked to this object
- auto *entry = zend_get_class_entry(object TSRMLS_CC);
-
- // we need the C++ class meta-information object
- ClassBase *meta = cpp_class(entry);
-
- // the default implementation throws an exception, if we catch that
- // we know for sure that the user has not overridden the __set method
- try
- {
- // wrap the name
- Value key(name);
-
- // check if the property has a callback
- auto iter = meta->_properties.find(key);
-
- // is it set?
- if (iter == meta->_properties.end())
- {
- // use the __set method
- meta->callSet(base, key, value);
- }
- else
- {
- // check if it could be set
- if (iter->second->set(base, value)) return;
-
- // read-only property
- zend_error(E_ERROR, "Unable to write to read-only property %s", (const char *)key);
- }
- }
- catch (const NotImplemented &exception)
- {
- // __set() function was not overridden by user, check if there is a default
- if (!std_object_handlers.write_property) return;
-
- // call the default
-#if PHP_VERSION_ID < 50399
- std_object_handlers.write_property(object, name, value TSRMLS_CC);
-#else
- std_object_handlers.write_property(object, name, value, key TSRMLS_CC);
-#endif
- }
- catch (Exception &exception)
- {
- // user threw an exception in its magic method
- // implementation, send it to user space
- exception.process(TSRMLS_C);
- }
-}
-
-/**
- * Function that is called to check whether a certain property is set
- * for an object
- *
- * This is the handler for the __isset() function, and is called when a PHP
- * script checks if a certain property is set.
- *
- * The has_set_exists parameter can have the following values:
- *
- * 0 (has) whether property exists and is not NULL
- * 1 (set) whether property exists and is true
- * 2 (exists) whether property exists
- *
- * @param object The object on which it is called
- * @param name The name of the property to check
- * @param has_set_exists See above
- * @param key ???
- * @param tsrm_ls
- * @return bool
- */
-#if PHP_VERSION_ID < 50399
-int ClassBase::hasProperty(zval *object, zval *name, int has_set_exists TSRMLS_DC)
-#else
-int ClassBase::hasProperty(zval *object, zval *name, int has_set_exists, const struct _zend_literal *key TSRMLS_DC)
-#endif
-{
- // the default implementation throws an exception, if we catch that
- // we know for sure that the user has not overridden the __isset method
- try
- {
- // get the cpp object
- Base *base = cpp_object(object TSRMLS_CC);
-
- // retrieve the class entry linked to this object
- auto *entry = zend_get_class_entry(object TSRMLS_CC);
-
- // we need the C++ class meta-information object
- ClassBase *meta = cpp_class(entry);
-
- // convert the name to a Value object
- Value key(name);
-
- // check if this is a callback property
- if (meta->_properties.find(key) != meta->_properties.end()) return true;
-
- // call the C++ object
- if (!meta->callIsset(base, key)) return false;
-
- // property exists, but what does the user want to know
- if (has_set_exists == 2) return true;
-
- // we have to retrieve the property
- Value value = meta->callGet(base, key);
-
- // should we check on NULL?
- switch (has_set_exists) {
- case 0: return value.type() != Type::Null;
- default: return value.boolValue();
- }
- }
- catch (const NotImplemented &exception)
- {
- // __isset was not implemented, do we have a default?
- if (!std_object_handlers.has_property) return 0;
-
- // call default
-#if PHP_VERSION_ID < 50399
- return std_object_handlers.has_property(object, name, has_set_exists TSRMLS_CC);
-#else
- return std_object_handlers.has_property(object, name, has_set_exists, key TSRMLS_CC);
-#endif
- }
- catch (Exception &exception)
- {
- // user threw an exception in its magic method
- // implementation, send it to user space
- exception.process(TSRMLS_C);
-
- // unreachable
- return false;
- }
-}
-
-/**
- * Function that is called when a property is removed from the project
- *
- * This is the handler for the __unset() method
- *
- * @param object The object on which it is called
- * @param member The member to remove
- * @param key
- * @param tsrm_ls
- */
-#if PHP_VERSION_ID < 50399
-void ClassBase::unsetProperty(zval *object, zval *member TSRMLS_DC)
-#else
-void ClassBase::unsetProperty(zval *object, zval *member, const struct _zend_literal *key TSRMLS_DC)
-#endif
-{
- // the default implementation throws an exception, if we catch that
- // we know for sure that the user has not overridden the __unset method
- try
- {
- // retrieve the class entry linked to this object
- auto *entry = zend_get_class_entry(object TSRMLS_CC);
-
- // we need the C++ class meta-information object
- ClassBase *meta = cpp_class(entry);
-
- // property name
- Value name(member);
-
- // is this a callback property?
- auto iter = meta->_properties.find(name);
-
- // if the property does not exist, we forward to the __unset
- if (iter == meta->_properties.end()) meta->callUnset(cpp_object(object TSRMLS_CC), member);
-
- // callback properties cannot be unset
- zend_error(E_ERROR, "Property %s can not be unset", (const char *)name);
- }
- catch (const NotImplemented &exception)
- {
- // __unset was not implemented, do we have a default?
- if (!std_object_handlers.unset_property) return;
-
- // call the default
-#if PHP_VERSION_ID < 50399
- std_object_handlers.unset_property(object, member TSRMLS_CC);
-#else
- std_object_handlers.unset_property(object, member, key TSRMLS_CC);
-#endif
- }
- catch (Exception &exception)
- {
- // user threw an exception in its magic method
- // implementation, send it to user space
- exception.process(TSRMLS_C);
- }
-}
-
-/**
- * Function that is called when an object is about to be destructed
- * This will call the magic __destruct method
- * @param object
- * @param handle
- * @param tsrm_ls
- */
-void ClassBase::destructObject(zend_object *object, zend_object_handle handle TSRMLS_DC)
-{
- // allocate memory for the object
- MixedObject *obj = (MixedObject *)object;
-
- // get meta info
- ClassBase *meta = cpp_class(object->ce);
-
- // prevent exceptions
- try
- {
- // call the destruct function
- if (obj->cpp) meta->callDestruct(obj->cpp);
- }
- catch (const NotImplemented &exception)
- {
- // fallback on the default destructor call
- zend_objects_destroy_object(object, handle TSRMLS_CC);
- }
- catch (Exception &exception)
- {
- // a regular Php::Exception was thrown by the extension, pass it on
- // to PHP user space
- exception.process(TSRMLS_C);
- }
-}
-
-/**
- * Function that is called to clean up space that is occupied by the object
- * @param object The object to be deallocated
- * @param tsrm_ls
- */
-void ClassBase::freeObject(zend_object *object TSRMLS_DC)
-{
- // allocate memory for the object
- MixedObject *obj = (MixedObject *)object;
-
- // deallocate the cpp object
- if (obj->cpp) delete obj->cpp;
-
- // pass on to the default destructor
- zend_objects_free_object_storage(object TSRMLS_CC);
-}
-
-/**
- * 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
- * @param tsrm_ls
- * @return zend_object_value The newly created object
- */
-zend_object_value ClassBase::createObject(zend_class_entry *entry TSRMLS_DC)
-{
- // 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;
-
- // set the handlers
- result.handlers = meta->objectHandlers();
-
- // store the object
- cpp->store(entry TSRMLS_CC);
-
- // store the object in the object cache
- result.handle = cpp->handle();
-
- // done
- return result;
-}
-
-/**
- * Function to create a new iterator to iterate over an object
- * @param entry The class entry
- * @param object The object to iterate over
- * @param by_ref ?????
- * @param tsrm_ls
- * @return zend_object_iterator* Pointer to the iterator
- */
-zend_object_iterator *ClassBase::getIterator(zend_class_entry *entry, zval *object, int by_ref TSRMLS_DC)
-{
- // by-ref is not possible (copied from SPL)
- if (by_ref) throw Php::Exception("Foreach by ref is not possible");
-
- // retrieve the traversable object
- Traversable *traversable = dynamic_cast<Traversable*>(cpp_object(object TSRMLS_CC));
-
- // user may throw an exception in the getIterator() function
- try
- {
- // create an iterator
- auto *iterator = traversable->getIterator();
-
- // return the implementation
- return iterator->implementation();
- }
- catch (Exception &exception)
- {
- // user threw an exception in its method
- // implementation, send it to user space
- exception.process(TSRMLS_C);
-
- // unreachable
- return nullptr;
- }
-}
-
-/**
- * Method that is called to serialize an object
- * @param object The object to be serialized
- * @param buffer Buffer in which to store the data
- * @param buf_len Size of the bufffer
- * @param data ??
- * @param tsrm_ls
- * @return int
- */
-int ClassBase::serialize(zval *object, unsigned char **buffer, zend_uint *buf_len, zend_serialize_data *data TSRMLS_DC)
-{
- // get the serializable object
- Serializable *serializable = dynamic_cast<Serializable*>(cpp_object(object TSRMLS_CC));
-
- // call the serialize method on the object
- auto value = serializable->serialize();
-
- // allocate the buffer, and copy the data into it (the zend engine will
- // (hopefully) clean up the data for us - the default serialize method does
- // it like this too)
- *buffer = (unsigned char*)estrndup(value.c_str(), value.size());
- *buf_len = value.size();
-
- // done
- return SUCCESS;
-}
-
-/**
- * Method that is called to unserialize an object
- * @param object The object to be unserialized
- * @param entry The class entry to which is belongs
- * @param buffer Buffer holding the unserialized data
- * @param data All the unserialize data
- * @param tsrm_ls
- * @return int
- */
-int ClassBase::unserialize(zval **object, zend_class_entry *entry, const unsigned char *buffer, zend_uint buf_len, zend_unserialize_data *data TSRMLS_DC)
-{
- // create the PHP object
- object_init_ex(*object, entry);
-
- // turn this into a serializale
- Serializable *serializable = dynamic_cast<Serializable*>(cpp_object(*object TSRMLS_CC));
-
- // call the unserialize method on it
- serializable->unserialize((const char *)buffer, buf_len);
-
- // done
- return SUCCESS;
-}
-
-/**
- * Retrieve an array of zend_function_entry objects that hold the
- * properties for each method. This method is called at extension
- * startup time to register all methods.
- *
- * @param classname The class name
- * @return zend_function_entry[]
- */
-const struct _zend_function_entry *ClassBase::entries()
-{
- // already initialized?
- if (_entries) return _entries;
-
- // allocate memory for the functions
- _entries = new zend_function_entry[_methods.size() + 1];
-
- // keep iterator counter
- int i = 0;
-
- // loop through the functions
- for (auto &method : _methods)
- {
- // retrieve entry
- zend_function_entry *entry = &_entries[i++];
-
- // let the function fill the entry
- method->initialize(entry, _name);
- }
-
- // last entry should be set to all zeros
- zend_function_entry *last = &_entries[i];
-
- // all should be set to zero
- memset(last, 0, sizeof(zend_function_entry));
-
- // done
- return _entries;
+ if (flags & Abstract) _impl = std::make_shared<ClassImpl>(classname, ClassType::Abstract);
+ else if (flags & Final) _impl = std::make_shared<ClassImpl>(classname, ClassType::Final);
+ else _impl = std::make_shared<ClassImpl>(classname, ClassType::Regular);
}
/**
- * Initialize the class, given its name
- *
- * The module functions are registered on module startup, but classes are
- * initialized afterwards. The Zend engine is a strange thing. Nevertheless,
- * this means that this method is called after the module is already available.
- * This function will inform the Zend engine about the existence of the
- * class.
- *
- * @param prefix namespace prefix
- * @param tsrm_ls
+ * Protected constructor
+ * @param classname Class name
+ * @param type Class type
*/
-void ClassBase::initialize(const std::string &prefix TSRMLS_DC)
+ClassBase::ClassBase(const char *classname, ClassType type)
{
- // the class entry
- zend_class_entry entry;
-
- // update the name
- if (prefix.size() > 0) _name = prefix + "\\" + _name;
-
- // initialize the class entry
- INIT_CLASS_ENTRY_EX(entry, _name.c_str(), _name.size(), entries());
-
- // we need a special constructor
- entry.create_object = &ClassBase::createObject;
-
- // register function that is called for static method calls
- entry.get_static_method = &ClassBase::getStaticMethod;
-
- // for traversable classes we install a special method to get the iterator
- if (traversable()) entry.get_iterator = &ClassBase::getIterator;
-
- // for serializable classes, we install callbacks for serializing and unserializing
- if (serializable())
- {
- // add handlers to serialize and unserialize
- entry.serialize = &ClassBase::serialize;
- entry.unserialize = &ClassBase::unserialize;
- }
-
- // register the class
- _entry = zend_register_internal_class(&entry TSRMLS_CC);
-
- // allocate doc comment to contain an empty string + a hidden pointer
- if (!_comment)
- {
- // allocate now
- _comment = (char *)malloc(1 + sizeof(ClassBase *));
-
- // empty string on first position
- _comment[0] = '\0';
-
- // this pointer has to be copied to temporary pointer, as &this causes compiler error
- ClassBase *base = this;
-
- // copy the 'this' pointer to the doc-comment
- memcpy(_comment+1, &base, sizeof(ClassBase *));
- }
-
- // store pointer to the class in the unused doc_comment member
-#if PHP_VERSION_ID >= 50400
- _entry->info.user.doc_comment = _comment;
-#else
- // and store the wrapper inside the comment
- _entry->doc_comment = _comment;
-#endif
-
- // set access types flags for class
- _entry->ce_flags = (int)_type;
-
- // declare all member variables
- for (auto &member : _members) member->initialize(_entry TSRMLS_CC);
+ // construct implementation
+ _impl = std::make_shared<ClassImpl>(classname, type);
}
/**
@@ -1436,152 +53,15 @@ void ClassBase::notImplemented()
* @param method The actual method
* @param flags Optional flags
* @param args Description of the supported arguments
- * @return ClassBase Same object to allow chaining
- */
-ClassBase &ClassBase::method(const char *name, const method_callback_0 &callback, int flags, const Arguments &args)
-{
- // add the method
- _methods.push_back(std::make_shared<Method>(name, callback, flags, args));
-
- // allow chaining
- return *this;
-}
-
-/**
- * Add a method to the class
- * @param name Name of the method
- * @param method The actual method
- * @param flags Optional flags
- * @param args Description of the supported arguments
- * @return ClassBase Same object to allow chaining
- */
-ClassBase &ClassBase::method(const char *name, const method_callback_1 &callback, int flags, const Arguments &args)
-{
- // add the method
- _methods.push_back(std::make_shared<Method>(name, callback, flags, args));
-
- // allow chaining
- return *this;
-}
-
-/**
- * Add a method to the class
- * @param name Name of the method
- * @param method The actual method
- * @param flags Optional flags
- * @param args Description of the supported arguments
- * @return ClassBase Same object to allow chaining
- */
-ClassBase &ClassBase::method(const char *name, const method_callback_2 &callback, int flags, const Arguments &args)
-{
- // add the method
- _methods.push_back(std::make_shared<Method>(name, callback, flags, args));
-
- // allow chaining
- return *this;
-}
-
-/**
- * Add a method to the class
- * @param name Name of the method
- * @param method The actual method
- * @param flags Optional flags
- * @param args Description of the supported arguments
- * @return ClassBase Same object to allow chaining
- */
-ClassBase &ClassBase::method(const char *name, const method_callback_3 &callback, int flags, const Arguments &args)
-{
- // add the method
- _methods.push_back(std::make_shared<Method>(name, callback, flags, args));
-
- // allow chaining
- return *this;
-}
-
-/**
- * Add a method to the class
- * @param name Name of the method
- * @param method The actual method
- * @param flags Optional flags
- * @param args Description of the supported arguments
- * @return ClassBase Same object to allow chaining
- */
-ClassBase &ClassBase::method(const char *name, const method_callback_4 &callback, int flags, const Arguments &args)
-{
- // add the method
- _methods.push_back(std::make_shared<Method>(name, callback, flags, args));
-
- // allow chaining
- return *this;
-}
-
-/**
- * Add a method to the class
- * @param name Name of the method
- * @param method The actual method
- * @param flags Optional flags
- * @param args Description of the supported arguments
- * @return ClassBase Same object to allow chaining
- */
-ClassBase &ClassBase::method(const char *name, const method_callback_5 &callback, int flags, const Arguments &args)
-{
- // add the method
- _methods.push_back(std::make_shared<Method>(name, callback, flags, args));
-
- // allow chaining
- return *this;
-}
-
-/**
- * Add a method to the class
- * @param name Name of the method
- * @param method The actual method
- * @param flags Optional flags
- * @param args Description of the supported arguments
- * @return ClassBase Same object to allow chaining
- */
-ClassBase &ClassBase::method(const char *name, const method_callback_6 &callback, int flags, const Arguments &args)
-{
- // add the method
- _methods.push_back(std::make_shared<Method>(name, callback, flags, args));
-
- // allow chaining
- return *this;
-}
-
-/**
- * Add a method to the class
- * @param name Name of the method
- * @param method The actual method
- * @param flags Optional flags
- * @param args Description of the supported arguments
- * @return ClassBase Same object to allow chaining
- */
-ClassBase &ClassBase::method(const char *name, const method_callback_7 &callback, int flags, const Arguments &args)
-{
- // add the method
- _methods.push_back(std::make_shared<Method>(name, callback, flags, args));
-
- // allow chaining
- return *this;
-}
-
-/**
- * Add a static method to the class
- * @param name Name of the method
- * @param method The actual method
- * @param flags Optional flags
- * @param args Description of the supported arguments
- * @return ClassBase Same object to allow chaining
*/
-ClassBase &ClassBase::method(const char *name, const native_callback_0 &method, int flags, const Arguments &args)
-{
- // add the method
- _methods.push_back(std::make_shared<Method>(name, method, flags | ZEND_ACC_STATIC, args));
-
- // allow chaining
- return *this;
-}
+void ClassBase::method(const char *name, const method_callback_0 &callback, int flags, const Arguments &args) { _impl->method(name, callback, flags, args); }
+void ClassBase::method(const char *name, const method_callback_1 &callback, int flags, const Arguments &args) { _impl->method(name, callback, flags, args); }
+void ClassBase::method(const char *name, const method_callback_2 &callback, int flags, const Arguments &args) { _impl->method(name, callback, flags, args); }
+void ClassBase::method(const char *name, const method_callback_3 &callback, int flags, const Arguments &args) { _impl->method(name, callback, flags, args); }
+void ClassBase::method(const char *name, const method_callback_4 &callback, int flags, const Arguments &args) { _impl->method(name, callback, flags, args); }
+void ClassBase::method(const char *name, const method_callback_5 &callback, int flags, const Arguments &args) { _impl->method(name, callback, flags, args); }
+void ClassBase::method(const char *name, const method_callback_6 &callback, int flags, const Arguments &args) { _impl->method(name, callback, flags, args); }
+void ClassBase::method(const char *name, const method_callback_7 &callback, int flags, const Arguments &args) { _impl->method(name, callback, flags, args); }
/**
* Add a static method to the class
@@ -1589,305 +69,47 @@ ClassBase &ClassBase::method(const char *name, const native_callback_0 &method,
* @param method The actual method
* @param flags Optional flags
* @param args Description of the supported arguments
- * @return ClassBase Same object to allow chaining
*/
-ClassBase &ClassBase::method(const char *name, const native_callback_1 &method, int flags, const Arguments &args)
-{
- // add the method
- _methods.push_back(std::make_shared<Method>(name, method, flags | ZEND_ACC_STATIC, args));
-
- // allow chaining
- return *this;
-}
-
-/**
- * Add a static method to the class
- * @param name Name of the method
- * @param method The actual method
- * @param flags Optional flags
- * @param args Description of the supported arguments
- * @return ClassBase Same object to allow chaining
- */
-ClassBase &ClassBase::method(const char *name, const native_callback_2 &method, int flags, const Arguments &args)
-{
- // add the method
- _methods.push_back(std::make_shared<Method>(name, method, flags | ZEND_ACC_STATIC, args));
-
- // allow chaining
- return *this;
-}
-
-/**
- * Add a static method to the class
- * @param name Name of the method
- * @param method The actual method
- * @param flags Optional flags
- * @param args Description of the supported arguments
- * @return ClassBase Same object to allow chaining
- */
-ClassBase &ClassBase::method(const char *name, const native_callback_3 &method, int flags, const Arguments &args)
-{
- // add the method
- _methods.push_back(std::make_shared<Method>(name, method, flags | ZEND_ACC_STATIC, args));
-
- // allow chaining
- return *this;
-}
+void ClassBase::method(const char *name, const native_callback_0 &method, int flags, const Arguments &args) { _impl->method(name, method, flags, args); }
+void ClassBase::method(const char *name, const native_callback_1 &method, int flags, const Arguments &args) { _impl->method(name, method, flags, args); }
+void ClassBase::method(const char *name, const native_callback_2 &method, int flags, const Arguments &args) { _impl->method(name, method, flags, args); }
+void ClassBase::method(const char *name, const native_callback_3 &method, int flags, const Arguments &args) { _impl->method(name, method, flags, args); }
/**
* Add an abstract method to the class
* @param name Name of the method
* @param flags Optional flags (like public or protected)
* @param args Description of the supported arguments
- * @return ClassBase Same object to allow chaining
- */
-ClassBase &ClassBase::method(const char *name, int flags, const Arguments &args)
-{
- // add the method
- _methods.push_back(std::make_shared<Method>(name, Abstract | flags, args));
-
- // allow chaining
- return *this;
-}
-
-/**
- * Add a property to the class
- * @param name Name of the property
- * @param value Actual property value
- * @param flags Optional flags
- * @return ClassBase Same object to allow chaining
- */
-ClassBase &ClassBase::property(const char *name, std::nullptr_t value, int flags)
-{
- // add property
- _members.push_back(std::make_shared<NullMember>(name, flags));
-
- // allow chaining
- return *this;
-}
-
-/**
- * Add a property to the class
- * @param name Name of the property
- * @param value Actual property value
- * @param flags Optional flags
- * @return ClassBase Same object to allow chaining
- */
-ClassBase &ClassBase::property(const char *name, int16_t value, int flags)
-{
- // add property
- _members.push_back(std::make_shared<NumericMember>(name, value, flags));
-
- // allow chaining
- return *this;
-}
-
-/**
- * Add a property to the class
- * @param name Name of the property
- * @param value Actual property value
- * @param flags Optional flags
- * @return ClassBase Same object to allow chaining
- */
-ClassBase &ClassBase::property(const char *name, int32_t value, int flags)
-{
- // add property
- _members.push_back(std::make_shared<NumericMember>(name, value, flags));
-
- // allow chaining
- return *this;
-}
-
-/**
- * Add a property to the class
- * @param name Name of the property
- * @param value Actual property value
- * @param flags Optional flags
- * @return ClassBase Same object to allow chaining
- */
-ClassBase &ClassBase::property(const char *name, int64_t value, int flags)
-{
- // add property
- _members.push_back(std::make_shared<NumericMember>(name, value, flags));
-
- // allow chaining
- return *this;
-}
-
-/**
- * Add a property to the class
- * @param name Name of the property
- * @param value Actual property value
- * @param flags Optional flags
- * @return ClassBase Same object to allow chaining
- */
-ClassBase &ClassBase::property(const char *name, bool value, int flags)
-{
- // add property
- _members.push_back(std::make_shared<BoolMember>(name, value, flags));
-
- // allow chaining
- return *this;
-}
-
-/**
- * Add a property to the class
- * @param name Name of the property
- * @param value Actual property value
- * @param flags Optional flags
- * @return ClassBase Same object to allow chaining
- */
-ClassBase &ClassBase::property(const char *name, char value, int flags)
-{
- // add property
- _members.push_back(std::make_shared<StringMember>(name, &value, 1, flags));
-
- // allow chaining
- return *this;
-}
-
-/**
- * Add a property to the class
- * @param name Name of the property
- * @param value Actual property value
- * @param flags Optional flags
- * @return ClassBase Same object to allow chaining
- */
-ClassBase &ClassBase::property(const char *name, const std::string &value, int flags)
-{
- // add property
- _members.push_back(std::make_shared<StringMember>(name, value, flags));
-
- // allow chaining
- return *this;
-}
-
-/**
- * Add a property to the class
- * @param name Name of the property
- * @param value Actual property value
- * @param flags Optional flags
- * @return ClassBase Same object to allow chaining
*/
-ClassBase &ClassBase::property(const char *name, const char *value, int flags)
-{
- // add property
- _members.push_back(std::make_shared<StringMember>(name, value, strlen(value), flags));
-
- // allow chaining
- return *this;
-}
+void ClassBase::method(const char *name, int flags, const Arguments &args) { _impl->method(name, flags, args); }
/**
* Add a property to the class
* @param name Name of the property
* @param value Actual property value
* @param flags Optional flags
- * @return ClassBase Same object to allow chaining
*/
-ClassBase &ClassBase::property(const char *name, double value, int flags)
-{
- // add property
- _members.push_back(std::make_shared<FloatMember>(name, value, flags));
-
- // allow chaining
- return *this;
-}
+void ClassBase::property(const char *name, std::nullptr_t value, int flags) { _impl->property(name, value, flags); }
+void ClassBase::property(const char *name, int16_t value, int flags) { _impl->property(name, value, flags); }
+void ClassBase::property(const char *name, int32_t value, int flags) { _impl->property(name, value, flags); }
+void ClassBase::property(const char *name, int64_t value, int flags) { _impl->property(name, value, flags); }
+void ClassBase::property(const char *name, bool value, int flags) { _impl->property(name, value, flags); }
+void ClassBase::property(const char *name, char value, int flags) { _impl->property(name, value, flags); }
+void ClassBase::property(const char *name, const std::string &value, int flags) { _impl->property(name, value, flags); }
+void ClassBase::property(const char *name, const char *value, int flags) { _impl->property(name, value, flags); }
+void ClassBase::property(const char *name, double value, int flags) { _impl->property(name, value, flags); }
/**
* Set property with callbacks
* @param name Name of the property
* @param getter Getter method
- * @return ClassBase Same object to allow chaining
*/
-ClassBase &ClassBase::property(const char *name, const getter_callback_0 &getter)
-{
- // add property
- _properties[name] = std::make_shared<Property>(getter);
-
- // allow chaining
- return *this;
-}
-
-/**
- * Set property with callbacks
- * @param name Name of the property
- * @param getter Getter method
- * @return ClassBase Same object to allow chaining
- */
-ClassBase &ClassBase::property(const char *name, const getter_callback_1 &getter)
-{
- // add property
- _properties[name] = std::make_shared<Property>(getter);
-
- // allow chaining
- return *this;
-}
-
-/**
- * Set property with callbacks
- * @param name Name of the property
- * @param getter Getter method
- * @param setter Setter method
- * @return ClassBase Same object to allow chaining
- */
-ClassBase &ClassBase::property(const char *name, const getter_callback_0 &getter, const setter_callback_0 &setter)
-{
- // add property
- _properties[name] = std::make_shared<Property>(getter,setter);
-
- // allow chaining
- return *this;
-}
-
-/**
- * Set property with callbacks
- * @param name Name of the property
- * @param getter Getter method
- * @param setter Setter method
- * @return ClassBase Same object to allow chaining
- */
-ClassBase &ClassBase::property(const char *name, const getter_callback_1 &getter, const setter_callback_0 &setter)
-{
- // add property
- _properties[name] = std::make_shared<Property>(getter,setter);
-
- // allow chaining
- return *this;
-}
-
-/**
- * Set property with callbacks
- * @param name Name of the property
- * @param getter Getter method
- * @param setter Setter method
- * @return ClassBase Same object to allow chaining
- */
-ClassBase &ClassBase::property(const char *name, const getter_callback_0 &getter, const setter_callback_1 &setter)
-{
- // add property
- _properties[name] = std::make_shared<Property>(getter,setter);
-
- // allow chaining
- return *this;
-}
-
-/**
- * Set property with callbacks
- * @param name Name of the property
- * @param getter Getter method
- * @param setter Setter method
- * @return ClassBase Same object to allow chaining
- */
-ClassBase &ClassBase::property(const char *name, const getter_callback_1 &getter, const setter_callback_1 &setter)
-{
- // add property
- _properties[name] = std::make_shared<Property>(getter,setter);
-
- // allow chaining
- return *this;
-}
-
+void ClassBase::property(const char *name, const getter_callback_0 &getter) { _impl->property(name, getter); }
+void ClassBase::property(const char *name, const getter_callback_1 &getter) { _impl->property(name, getter); }
+void ClassBase::property(const char *name, const getter_callback_0 &getter, const setter_callback_0 &setter) { _impl->property(name, getter, setter); }
+void ClassBase::property(const char *name, const getter_callback_1 &getter, const setter_callback_0 &setter) { _impl->property(name, getter, setter); }
+void ClassBase::property(const char *name, const getter_callback_0 &getter, const setter_callback_1 &setter) { _impl->property(name, getter, setter); }
+void ClassBase::property(const char *name, const getter_callback_1 &getter, const setter_callback_1 &setter) { _impl->property(name, getter, setter); }
/**
* End namespace
diff --git a/src/classimpl.cpp b/src/classimpl.cpp
new file mode 100644
index 0000000..1ee7b0b
--- /dev/null
+++ b/src/classimpl.cpp
@@ -0,0 +1,1444 @@
+/**
+ * ClassImpl.cpp
+ *
+ * Implementation file for the ClassImpl class
+ *
+ * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
+ * @copyright 2014 Copernica BV
+ */
+#include "includes.h"
+
+/**
+ * Set up namespace
+ */
+namespace Php {
+
+/**
+ * Destructor
+ */
+ClassImpl::~ClassImpl()
+{
+ // destruct the entries
+ if (_entries) delete[] _entries;
+
+ // php 5.3 deallocates the doc_comment by iself
+#if PHP_VERSION_ID >= 50400
+ if (_comment) free(_comment);
+#endif
+}
+
+/**
+ * Retrieve our C++ implementation object
+ * @param entry
+ * @return ClassImpl
+ */
+static ClassImpl *cpp_impl(zend_class_entry *entry)
+{
+ // 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;
+
+#if PHP_VERSION_ID >= 50400
+ // retrieve the comment (it has a pointer hidden in it to the ClassBase object)
+ const char *comment = entry->info.user.doc_comment;
+#else
+ // 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 ClassBase class
+ return *((ClassImpl **)(comment + 1));
+}
+
+/**
+ * Retrieve the extension's class object
+ * @param entry
+ * @return ClassBase
+ */
+ClassBase *ClassImpl::base(zend_class_entry *entry)
+{
+ // first the implementation class
+ ClassImpl *impl = cpp_impl(entry);
+
+ // and now the base
+ return impl->_base;
+}
+
+/**
+ * Retrieve the CPP object
+ * @param val
+ * @return Base
+ */
+static Base *cpp_object(const zval *val TSRMLS_DC)
+{
+ // retrieve the old object, which we are going to copy
+ MixedObject *object = (MixedObject *)zend_object_store_get_object(val TSRMLS_CC);
+
+ // return the cpp object
+ return object->cpp;
+}
+
+/**
+ * Extended zend_internal_function structure that we use to store an
+ * instance of the ClassBase object. We need this for static method calls
+ */
+struct CallData
+{
+ // the internal function is the first member, so
+ // that it is possible to cast an instance of this
+ // struct to a zend_internal_function
+ zend_internal_function func;
+
+ // and a pointer to the ClassImpl object
+ ClassImpl *self;
+};
+
+/**
+ * Handler function that runs the __call function
+ * @param ... All normal parameters for function calls
+ */
+void ClassImpl::callMethod(INTERNAL_FUNCTION_PARAMETERS)
+{
+ // retrieve the originally called (and by us allocated) function object
+ // (this was copied from the zend engine source code, code looks way too
+ // ugly to be made by me)
+ CallData *data = (CallData *)EG(current_execute_data)->function_state.function;
+ zend_internal_function *func = &data->func;
+
+ // retrieve the function name
+ const char *name = func->function_name;
+ ClassBase *meta = data->self->_base;
+
+ // the data structure was allocated by ourselves in the getMethod or
+ // getStaticMethod functions, we no longer need it now
+ efree(data);
+
+ // the function could throw an exception
+ try
+ {
+ // wrap the return value
+ Value result(return_value, true);
+
+ // construct parameters
+ Parameters params(this_ptr, ZEND_NUM_ARGS() TSRMLS_CC);
+
+ // retrieve the base object
+ Base *base = params.object();
+
+ // is this a static, or a non-static call?
+ if (base) result = meta->callCall(base, name, params);
+ else result = meta->callCallStatic(name, params);
+ }
+ catch (const NotImplemented &exception)
+ {
+ // because of the two-step nature, we are going to report the error ourselves
+ zend_error(E_ERROR, "Undefined method %s", name);
+ }
+ catch (Exception &exception)
+ {
+ // process the exception
+ exception.process(TSRMLS_C);
+ }
+}
+
+/**
+ * Handler function that runs the __invoke function
+ * @param ... All normal parameters for function calls
+ */
+void ClassImpl::callInvoke(INTERNAL_FUNCTION_PARAMETERS)
+{
+ // retrieve the originally called (and by us allocated) function object
+ // (this was copied from the zend engine source code, code looks way too
+ // ugly to be made by me)
+ CallData *data = (CallData *)EG(current_execute_data)->function_state.function;
+
+ // get self reference
+ ClassBase *meta = data->self->_base;
+
+ // the data structure was allocated by ourselves in the getMethod or
+ // getStaticMethod functions, we no longer need it now
+ efree(data);
+
+ // the function could throw an exception
+ try
+ {
+ // wrap the return value
+ Value result(return_value, true);
+
+ // construct parameters
+ Parameters params(this_ptr, ZEND_NUM_ARGS() TSRMLS_CC);
+
+ // retrieve the base object
+ Base *base = params.object();
+
+ // call the actual __invoke method on the base object
+ result = meta->callInvoke(base, params);
+ }
+ catch (const NotImplemented &exception)
+ {
+ // because of the two-step nature, we are going to report the error ourselves
+ zend_error(E_ERROR, "Function name must be a string");
+
+ }
+ catch (Exception &exception)
+ {
+ // process the exception
+ exception.process(TSRMLS_C);
+ }
+}
+
+/**
+ * Method that returns the function definition of the __call function
+ * @param object_ptr
+ * @param method_name
+ * @param method_len
+ * @param tsrm_ls
+ * @return zend_function
+ */
+#if PHP_VERSION_ID < 50399
+zend_function *ClassImpl::getMethod(zval **object_ptr, char *method_name, int method_len TSRMLS_DC)
+#else
+zend_function *ClassImpl::getMethod(zval **object_ptr, char *method_name, int method_len, const zend_literal *key TSRMLS_DC)
+#endif
+{
+ // something strange about the Zend engine (once more). The structure with
+ // object-handlers has a get_method and call_method member. When a function is
+ // called, the get_method function is called first, to retrieve information
+ // about the method (like the handler that should be called to execute it),
+ // after that, this returned handler is also called. The call_method property
+ // of the object_handlers structure however, never gets called. Typical.
+
+ // first we'll check if the default handler does not have an implementation,
+ // in that case the method is probably already implemented as a regular method
+#if PHP_VERSION_ID < 50399
+ auto *defaultFunction = std_object_handlers.get_method(object_ptr, method_name, method_len TSRMLS_CC);
+#else
+ auto *defaultFunction = std_object_handlers.get_method(object_ptr, method_name, method_len, key TSRMLS_CC);
+#endif
+
+ // did the default implementation do anything?
+ if (defaultFunction) return defaultFunction;
+
+ // retrieve the class entry linked to this object
+ auto *entry = zend_get_class_entry(*object_ptr TSRMLS_CC);
+
+ // this is peculiar behavior of the zend engine, we first are going to dynamically
+ // allocate memory holding all the properties of the __call method (we initially
+ // had an implementation here that used a static variable, and that worked too,
+ // but we'll follow thread safe implementation of the Zend engine here, although
+ // it is strange to allocate and free memory in one and the same method call (free()
+ // call happens in call_method())
+ auto *data = (CallData *)emalloc(sizeof(CallData));
+ auto *function = &data->func;
+
+ // we're going to set all properties
+ function->type = ZEND_INTERNAL_FUNCTION;
+ function->module = nullptr;
+ function->handler = &ClassImpl::callMethod;
+ function->arg_info = nullptr;
+ function->num_args = 0;
+ function->required_num_args = 0;
+ function->scope = entry;
+ function->fn_flags = ZEND_ACC_CALL_VIA_HANDLER;
+ function->function_name = method_name;
+
+ // store pointer to ourselves
+ data->self = cpp_impl(entry);
+
+ // done (cast to zend_function* is allowed, because a zend_function is a union
+ // that has one member being a zend_internal_function)
+ return (zend_function *)data;
+}
+
+/**
+ * Method that is called right before a static method call is attempted
+ * @param entry
+ * @param method
+ * @param method_len
+ * @param tsrm_ls
+ * @return zend_function
+ */
+zend_function *ClassImpl::getStaticMethod(zend_class_entry *entry, char* method, int method_len TSRMLS_DC)
+{
+ // first we'll check if the default handler does not have an implementation,
+ // in that case the method is probably already implemented as a regular method
+#if PHP_VERSION_ID < 50399
+ auto *defaultFunction = zend_std_get_static_method(entry, method, method_len TSRMLS_CC);
+#else
+ auto *defaultFunction = zend_std_get_static_method(entry, method, method_len, nullptr TSRMLS_CC);
+#endif
+
+ // did the default implementation do anything?
+ if (defaultFunction) return defaultFunction;
+
+ // just like we did in getMethod() (see comment there) we are going to dynamically
+ // allocate data holding information about the function
+ auto *data = (CallData *)emalloc(sizeof(CallData));
+ auto *function = &data->func;
+
+ // we're going to set all properties
+ function->type = ZEND_INTERNAL_FUNCTION;
+ function->module = nullptr;
+ function->handler = ClassImpl::callMethod;
+ function->arg_info = nullptr;
+ function->num_args = 0;
+ function->required_num_args = 0;
+ function->scope = nullptr;
+ function->fn_flags = ZEND_ACC_CALL_VIA_HANDLER;
+ function->function_name = method;
+
+ // store pointer to ourselves
+ data->self = cpp_impl(entry);
+
+ // done (cast to zend_function* is allowed, because a zend_function is a union
+ // that has one member being a zend_internal_function)
+ return (zend_function *)data;
+}
+
+/**
+ * Method that returns the closure -- this is the __invoke handler!
+ * @param object
+ * @param entry_ptr
+ * @param func
+ * @param object_ptr
+ * @param tsrm_ls
+ * @return int
+ */
+int ClassImpl::getClosure(zval *object, zend_class_entry **entry_ptr, zend_function **func, zval **object_ptr TSRMLS_DC)
+{
+ // it is really unbelievable how the Zend engine manages to implement every feature
+ // in a complete different manner. You would expect the __invoke() and the
+ // __call() functions not to be very different from each other. However, they
+ // both have a completely different API. This getClosure method is supposed
+ // to fill the function parameter with all information about the invoke()
+ // method that is going to get called
+
+ // retrieve the class entry linked to this object
+ auto *entry = zend_get_class_entry(object TSRMLS_CC);
+
+ // just like we did for getMethod(), we're going to dynamically allocate memory
+ // with all information about the function
+ auto *data = (CallData *)emalloc(sizeof(CallData));
+ auto *function = &data->func;
+
+ // we're going to set all properties
+ function->type = ZEND_INTERNAL_FUNCTION;
+ function->module = nullptr;
+ function->handler = &ClassImpl::callInvoke;
+ function->arg_info = nullptr;
+ function->num_args = 0;
+ function->required_num_args = 0;
+ function->scope = entry;
+ function->fn_flags = ZEND_ACC_CALL_VIA_HANDLER;
+ function->function_name = nullptr;
+
+ // store pointer to ourselves
+ data->self = cpp_impl(entry);
+
+ // assign this dynamically allocated variable to the func parameter
+ // the case is ok, because zend_internal_function is a member of the
+ // zend_function union
+ *func = (zend_function *)data;
+
+ // the object_ptr should be filled with the object on which the method is
+ // called (otherwise the Zend engine tries to call the method statically)
+ *object_ptr = object;
+
+ // done
+ return SUCCESS;
+};
+
+/**
+ * Retrieve pointer to our own object handlers
+ * @return zend_object_handlers
+ */
+zend_object_handlers *ClassImpl::objectHandlers()
+{
+ // keep static structure
+ static zend_object_handlers handlers;
+
+ // is the object already initialized?
+ static bool initialized = false;
+
+ // already initialized?
+ if (initialized) return &handlers;
+
+ // initialize the handlers
+ memcpy(&handlers, &std_object_handlers, sizeof(zend_object_handlers));
+
+ // install custom clone function
+ if (!_base->clonable()) handlers.clone_obj = nullptr;
+ else handlers.clone_obj = &ClassImpl::cloneObject;
+
+ // functions for the Countable interface
+ handlers.count_elements = &ClassImpl::countElements;
+
+ // functions for the ArrayAccess interface
+ handlers.write_dimension = &ClassImpl::writeDimension;
+ handlers.read_dimension = &ClassImpl::readDimension;
+ handlers.has_dimension = &ClassImpl::hasDimension;
+ handlers.unset_dimension = &ClassImpl::unsetDimension;
+
+ // functions for the magic properties handlers (__get, __set, __isset and __unset)
+ handlers.write_property = &ClassImpl::writeProperty;
+ handlers.read_property = &ClassImpl::readProperty;
+ handlers.has_property = &ClassImpl::hasProperty;
+ handlers.unset_property = &ClassImpl::unsetProperty;
+
+ // when a method is called (__call and __invoke)
+ handlers.get_method = &ClassImpl::getMethod;
+ handlers.get_closure = &ClassImpl::getClosure;
+
+ // handler to cast to a different type
+ handlers.cast_object = &ClassImpl::cast;
+
+ // method to compare two objects
+ handlers.compare_objects = &ClassImpl::compare;
+
+ // remember that object is now initialized
+ initialized = true;
+
+ // done
+ return &handlers;
+}
+
+/**
+ * Function to compare two objects
+ * @param object1
+ * @param object2
+ * @param tsrm_ls
+ * @return int
+ */
+int ClassImpl::compare(zval *object1, zval *object2 TSRMLS_DC)
+{
+ // prevent exceptions
+ try
+ {
+ // retrieve the class entry linked to this object
+ auto *entry = zend_get_class_entry(object1 TSRMLS_CC);
+
+ // other object must be of the same type
+ if (entry != zend_get_class_entry(object2 TSRMLS_CC)) throw NotImplemented();
+
+ // we need the C++ class meta-information object
+ ClassBase *meta = base(entry);
+
+ // get the base objects
+ Base *base1 = cpp_object(object1 TSRMLS_CC);
+ Base *base2 = cpp_object(object2 TSRMLS_CC);
+
+ // run the compare method
+ return meta->callCompare(base1, base2);
+ }
+ catch (const NotImplemented &exception)
+ {
+ // it was not implemented, do we have a default?
+ if (!std_object_handlers.compare_objects) return 1;
+
+ // call default
+ return std_object_handlers.compare_objects(object1, object2 TSRMLS_CC);
+ }
+ catch (Exception &exception)
+ {
+ // a Php::Exception was thrown by the extension __compare function,
+ // pass this on to user space
+ exception.process(TSRMLS_C);
+
+ // what shall we return here...
+ return 1;
+ }
+}
+
+/**
+ * Function to cast the object to a different type
+ * @param object
+ * @param retval
+ * @param type
+ * @param tsrm_ls
+ * @return int
+ */
+int ClassImpl::cast(zval *object, zval *retval, int type TSRMLS_DC)
+{
+ // get the base object
+ Base *base = cpp_object(object TSRMLS_CC);
+
+ // retrieve the class entry linked to this object
+ auto *entry = zend_get_class_entry(object TSRMLS_CC);
+
+ // we need the C++ class meta-information object
+ ClassBase *meta = ClassImpl::base(entry);
+
+ // retval it not yet initialized --- and again feelings of disbelief,
+ // frustration, wonder and anger come up when you see that there are not two
+ // functions in the Zend engine that have a comparable API
+ INIT_PZVAL(retval);
+
+ // wrap zval in value object
+ Value result(retval, true);
+
+ // when the magic function it not implemented, an exception will be thrown,
+ // and the extension may throw a Php::Exception
+ try
+ {
+ // the result zval
+ zval *result = nullptr;
+
+ // check type
+ switch ((Type)type) {
+ case Type::Numeric: result = meta->callToInteger(base).detach(); break;
+ case Type::Float: result = meta->callToFloat(base).detach(); break;
+ case Type::Bool: result = meta->callToBool(base).detach(); break;
+ case Type::String: result = meta->callToString(base).detach(); break;
+ default: throw NotImplemented(); break;
+ }
+
+ // @todo do we turn into endless conversion if the __toString object returns 'this' ??
+ // (and if it does: who cares? If the extension programmer is stupid, why do we have to suffer?)
+
+ // is the object overwritten?
+ if (object == retval) zval_dtor(object);
+
+ // overwrite the result
+ ZVAL_ZVAL(retval, result, 1, 1);
+
+ // done
+ return SUCCESS;
+ }
+ catch (const NotImplemented &exception)
+ {
+ // is there a default?
+ if (!std_object_handlers.cast_object) return FAILURE;
+
+ // call default
+ return std_object_handlers.cast_object(object, retval, type TSRMLS_CC);
+ }
+ catch (Exception &exception)
+ {
+ // pass on the exception to php userspace
+ exception.process(TSRMLS_C);
+
+ // done
+ return FAILURE;
+ }
+}
+
+/**
+ * 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
+ */
+zend_object_value ClassImpl::cloneObject(zval *val TSRMLS_DC)
+{
+ // retrieve the class entry linked to this object
+ auto *entry = zend_get_class_entry(val TSRMLS_CC);
+
+ // we need the C++ class meta-information object
+ ClassImpl *impl = cpp_impl(entry);
+ ClassBase *meta = impl->_base;
+
+ // retrieve the old object, which we are going to copy
+ MixedObject *old_object = (MixedObject *)zend_object_store_get_object(val TSRMLS_CC);
+
+ // create a new base c++ object
+ auto *cpp = meta->clone(old_object->cpp);
+
+ // report error on failure (this does not occur because the cloneObject()
+ // method is only installed as handler when we have seen that there is indeed
+ // a copy constructor)
+ 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 = impl->objectHandlers();
+
+ // store the object
+ MixedObject *new_object = cpp->store(entry TSRMLS_CC);
+
+ // store the object in the object cache
+ result.handle = cpp->handle();
+
+ // clone the members (this will also call the __clone() function if the user
+ // had registered that as a visible method)
+ zend_objects_clone_members(&new_object->php, result, &old_object->php, Z_OBJ_HANDLE_P(val) TSRMLS_CC);
+
+ // was a custom clone method installed? If not we call the magic c++ __clone method
+ if (!entry->clone) meta->callClone(cpp);
+
+ // done
+ return result;
+}
+
+/**
+ * Function that is used to count the number of elements in the object
+ *
+ * If the user has implemented the Countable interface, this method will
+ * call the count() method
+ *
+ * @param val
+ * @param count
+ * @return int
+ */
+int ClassImpl::countElements(zval *object, long *count TSRMLS_DC)
+{
+ // does it implement the countable interface?
+ Countable *countable = dynamic_cast<Countable*>(cpp_object(object TSRMLS_CC));
+
+ // if it does not implement the Countable interface, we rely on the default implementation
+ if (countable)
+ {
+ // the user function may throw an exception that needs to be processed
+ try
+ {
+ // call the count function
+ *count = countable->count();
+
+ // done
+ return SUCCESS;
+ }
+ catch (Exception &exception)
+ {
+ // process the exception
+ exception.process(TSRMLS_C);
+
+ // unreachable
+ return FAILURE;
+ }
+ }
+ else
+ {
+ // Countable interface was not implemented, check if there is a default
+ if (!std_object_handlers.count_elements) return FAILURE;
+
+ // call default
+ return std_object_handlers.count_elements(object, count TSRMLS_CC);
+ }
+}
+
+/**
+ * Function that is called when the object is used as an array in PHP
+ *
+ * This is the [] operator in PHP, and mapped to the offsetGet() method
+ * of the ArrayAccess interface
+ *
+ * @param object The object on which it is called
+ * @param offset The name of the property
+ * @param type The type of the variable???
+ * @param tsrm_ls
+ * @return zval
+ */
+zval *ClassImpl::readDimension(zval *object, zval *offset, int type TSRMLS_DC)
+{
+ // what to do with the type?
+ //
+ // the type parameter tells us whether the dimension was read in READ
+ // mode, WRITE mode, READWRITE mode or UNSET mode.
+ //
+ // In 99 out of 100 situations, it is called in regular READ mode (value 0),
+ // only when it is called from a PHP script that has statements like
+ // $x =& $object["x"], $object["x"]["y"] = "something" or unset($object["x"]["y"]),
+ // the type parameter is set to a different value.
+ //
+ // But we must ask ourselves the question what we should be doing with such
+ // cases. Internally, the object most likely has a full native implementation,
+ // and the property that is returned is just a string or integer or some
+ // other value, that is temporary WRAPPED into a zval to make it accessible
+ // from PHP. If someone wants to get a reference to such an internal variable,
+ // that is in most cases simply impossible.
+
+
+ // does it implement the arrayaccess interface?
+ ArrayAccess *arrayaccess = dynamic_cast<ArrayAccess*>(cpp_object(object TSRMLS_CC));
+
+ // if it does not implement the ArrayAccess interface, we rely on the default implementation
+ if (arrayaccess)
+ {
+ // the C++ code may throw an exception
+ try
+ {
+ // ArrayAccess is implemented, call function
+ return toZval(arrayaccess->offsetGet(offset), type);
+ }
+ catch (Exception &exception)
+ {
+ // process the exception (send it to user space)
+ exception.process(TSRMLS_C);
+
+ // unreachable
+ return Value(nullptr).detach();
+ }
+ }
+ else
+ {
+ // ArrayAccess not implemented, check if there is a default handler
+ if (!std_object_handlers.read_dimension) return nullptr;
+
+ // call default
+ return std_object_handlers.read_dimension(object, offset, type TSRMLS_CC);
+ }
+}
+
+/**
+ * Function that is called when the object is used as an array in PHP
+ *
+ * This is the [] operator in PHP, and mapped to the offsetSet() method
+ * of the ArrayAccess interface
+ *
+ * @param object The object on which it is called
+ * @param offset The name of the property
+ * @param value The new value
+ * @param tsrm_ls
+ * @return zval
+ */
+void ClassImpl::writeDimension(zval *object, zval *offset, zval *value TSRMLS_DC)
+{
+ // does it implement the arrayaccess interface?
+ ArrayAccess *arrayaccess = dynamic_cast<ArrayAccess*>(cpp_object(object TSRMLS_CC));
+
+ // if it does not implement the ArrayAccess interface, we rely on the default implementation
+ if (arrayaccess)
+ {
+ // method may throw an exception
+ try
+ {
+ // set the value
+ arrayaccess->offsetSet(offset, value);
+ }
+ catch (Exception &exception)
+ {
+ // process the exception (send it to user space
+ exception.process(TSRMLS_C);
+ }
+ }
+ else
+ {
+ // ArrayAccess not interface, check if there is a default handler
+ if (!std_object_handlers.write_dimension) return;
+
+ // call the default
+ std_object_handlers.write_dimension(object, offset, value TSRMLS_CC);
+ }
+}
+
+/**
+ * Function that is called when the object is used as an array in PHP
+ *
+ * This is the [] operator in PHP, and mapped to the offsetExists() method
+ * of the ArrayAccess interface
+ *
+ * @param object The object on which it is called
+ * @param member The member to check
+ * @param check_empty Was this an isset() call, or an empty() call?
+ * @param tsrm_ls
+ * @return bool
+ */
+int ClassImpl::hasDimension(zval *object, zval *member, int check_empty TSRMLS_DC)
+{
+ // does it implement the arrayaccess interface?
+ ArrayAccess *arrayaccess = dynamic_cast<ArrayAccess*>(cpp_object(object TSRMLS_CC));
+
+ // if it does not implement the ArrayAccess interface, we rely on the default implementation
+ if (arrayaccess)
+ {
+ // user implemented callbacks could throw an exception
+ try
+ {
+ // check if the member exists
+ if (!arrayaccess->offsetExists(member)) return false;
+
+ // we know for certain that the offset exists, but should we check
+ // more, like whether the value is empty or not?
+ if (!check_empty) return true;
+
+ // the user wants to know if the property is empty
+ return empty(arrayaccess->offsetGet(member));
+ }
+ catch (Exception &exception)
+ {
+ // process the exception (send it to user space)
+ exception.process(TSRMLS_C);
+
+ // unreachable
+ return false;
+ }
+ }
+ else
+ {
+ // ArrayAccess interface is not implemented, check if there is a default handler
+ if (!std_object_handlers.has_dimension) return 0;
+
+ // call default
+ return std_object_handlers.has_dimension(object, member, check_empty TSRMLS_CC);
+ }
+}
+
+/**
+ * Function that is called when the object is used as an array in PHP
+ *
+ * This is the [] operator in PHP, and mapped to the offsetUnset() method
+ * of the ArrayAccess interface
+ *
+ * @param object The object on which it is called
+ * @param member The member to remove
+ * @param tsrm_ls
+ */
+void ClassImpl::unsetDimension(zval *object, zval *member TSRMLS_DC)
+{
+ // does it implement the arrayaccess interface?
+ ArrayAccess *arrayaccess = dynamic_cast<ArrayAccess*>(cpp_object(object TSRMLS_CC));
+
+ // if it does not implement the ArrayAccess interface, we rely on the default implementation
+ if (arrayaccess)
+ {
+ // user implemented code could throw an exception
+ try
+ {
+ // remove the member
+ arrayaccess->offsetUnset(member);
+ }
+ catch (Exception &exception)
+ {
+ // process the exception (send it to user space)
+ exception.process(TSRMLS_C);
+ }
+ }
+ else
+ {
+ // ArrayAccess is not implemented, is a default handler available?
+ if (!std_object_handlers.unset_dimension) return;
+
+ // call the default
+ std_object_handlers.unset_dimension(object, member TSRMLS_CC);
+ }
+}
+
+/**
+ * Helper method to turn a property into a zval
+ * @param value
+ * @param type
+ * @return Value
+ */
+zval *ClassImpl::toZval(Value &&value, int type)
+{
+ // because we do not want the value object to destruct the zval when
+ // it falls out of scope, we detach the zval from it, if this is a regular
+ // read operation we can do this right away
+ if (type == 0) return value.detach();
+
+ // this is a more complicated read operation, the scripts wants to get
+ // deeper access to the returned value. This, however, is only possible
+ // if the value has more than once reference (if it has a refcount of one,
+ // the value object that we have here is the only instance of the zval,
+ // and it is simply impossible to return a reference or so
+ if (value.refcount() <= 1) return value.detach();
+
+ // we're dealing with an editable zval, return a reference variable
+ return Value(value.detach(), true).detach();
+}
+
+/**
+ * Function that is called when a property is read
+ * @param object
+ * @param name
+ * @param type
+ * @param key
+ * @param tsrm_ls
+ * @return val
+ */
+#if PHP_VERSION_ID < 50399
+zval *ClassImpl::readProperty(zval *object, zval *name, int type TSRMLS_DC)
+#else
+zval *ClassImpl::readProperty(zval *object, zval *name, int type, const zend_literal *key TSRMLS_DC)
+#endif
+{
+ // what to do with the type?
+ //
+ // the type parameter tells us whether the property was read in READ
+ // mode, WRITE mode, READWRITE mode or UNSET mode.
+ //
+ // In 99 out of 100 situations, it is called in regular READ mode (value 0),
+ // only when it is called from a PHP script that has statements like
+ // $x =& $object->x, $object->x->y = "something" or unset($object->x->y)
+ // the type parameter is set to a different value.
+ //
+ // But we must ask ourselves the question what we should be doing with such
+ // cases. Internally, the object most likely has a full native implementation,
+ // and the property that is returned is just a string or integer or some
+ // other value, that is temporary WRAPPED into a zval to make it accessible
+ // from PHP. If someone wants to get a reference to such an internal variable,
+ // that is in most cases simply impossible.
+
+ // retrieve the object and class
+ Base *base = cpp_object(object TSRMLS_CC);
+
+ // retrieve the class entry linked to this object
+ auto *entry = zend_get_class_entry(object TSRMLS_CC);
+
+ // we need the C++ class meta-information object
+ ClassImpl *impl = cpp_impl(entry);
+ ClassBase *meta = impl->_base;
+
+ // the default implementation throws an exception, so by catching
+ // the exception we know if the object was implemented by the user or not
+ try
+ {
+ // convert name to a Value object
+ Value key(name);
+
+ // is it a property with a callback?
+ auto iter = impl->_properties.find(key);
+
+ // was it found?
+ if (iter == impl->_properties.end())
+ {
+ // retrieve value from the __get method
+ return toZval(meta->callGet(base, key), type);
+ }
+ else
+ {
+ // get the value
+ return toZval(iter->second->get(base), type);
+ }
+ }
+ catch (const NotImplemented &exception)
+ {
+ // __get() function was not overridden by the user
+ if (!std_object_handlers.read_property) return nullptr;
+
+ // call default
+#if PHP_VERSION_ID < 50399
+ return std_object_handlers.read_property(object, name, type TSRMLS_CC);
+#else
+ return std_object_handlers.read_property(object, name, type, key TSRMLS_CC);
+#endif
+ }
+ catch (Exception &exception)
+ {
+ // user threw an exception in its magic method
+ // implementation, send it to user space
+ exception.process(TSRMLS_C);
+
+ // unreachable
+ return Value(nullptr).detach();
+ }
+}
+
+/**
+ * Function that is called when a property is set / updated
+ *
+ * This is the handler for the __set() function, and is called when a property
+ * is updated.
+ *
+ * @param object The object on which it is called
+ * @param name The name of the property
+ * @param value The new value
+ * @param key ???
+ * @param tsrm_ls
+ * @return zval
+ */
+#if PHP_VERSION_ID < 50399
+void ClassImpl::writeProperty(zval *object, zval *name, zval *value TSRMLS_DC)
+#else
+void ClassImpl::writeProperty(zval *object, zval *name, zval *value, const zend_literal *key TSRMLS_DC)
+#endif
+{
+ // retrieve the object and class
+ Base *base = cpp_object(object TSRMLS_CC);
+
+ // retrieve the class entry linked to this object
+ auto *entry = zend_get_class_entry(object TSRMLS_CC);
+
+ // we need the C++ class meta-information object
+ ClassImpl *impl = cpp_impl(entry);
+ ClassBase *meta = impl->_base;
+
+ // the default implementation throws an exception, if we catch that
+ // we know for sure that the user has not overridden the __set method
+ try
+ {
+ // wrap the name
+ Value key(name);
+
+ // check if the property has a callback
+ auto iter = impl->_properties.find(key);
+
+ // is it set?
+ if (iter == impl->_properties.end())
+ {
+ // use the __set method
+ meta->callSet(base, key, value);
+ }
+ else
+ {
+ // check if it could be set
+ if (iter->second->set(base, value)) return;
+
+ // read-only property
+ zend_error(E_ERROR, "Unable to write to read-only property %s", (const char *)key);
+ }
+ }
+ catch (const NotImplemented &exception)
+ {
+ // __set() function was not overridden by user, check if there is a default
+ if (!std_object_handlers.write_property) return;
+
+ // call the default
+#if PHP_VERSION_ID < 50399
+ std_object_handlers.write_property(object, name, value TSRMLS_CC);
+#else
+ std_object_handlers.write_property(object, name, value, key TSRMLS_CC);
+#endif
+ }
+ catch (Exception &exception)
+ {
+ // user threw an exception in its magic method
+ // implementation, send it to user space
+ exception.process(TSRMLS_C);
+ }
+}
+
+/**
+ * Function that is called to check whether a certain property is set
+ * for an object
+ *
+ * This is the handler for the __isset() function, and is called when a PHP
+ * script checks if a certain property is set.
+ *
+ * The has_set_exists parameter can have the following values:
+ *
+ * 0 (has) whether property exists and is not NULL
+ * 1 (set) whether property exists and is true
+ * 2 (exists) whether property exists
+ *
+ * @param object The object on which it is called
+ * @param name The name of the property to check
+ * @param has_set_exists See above
+ * @param key ???
+ * @param tsrm_ls
+ * @return bool
+ */
+#if PHP_VERSION_ID < 50399
+int ClassImpl::hasProperty(zval *object, zval *name, int has_set_exists TSRMLS_DC)
+#else
+int ClassImpl::hasProperty(zval *object, zval *name, int has_set_exists, const zend_literal *key TSRMLS_DC)
+#endif
+{
+ // the default implementation throws an exception, if we catch that
+ // we know for sure that the user has not overridden the __isset method
+ try
+ {
+ // get the cpp object
+ Base *base = cpp_object(object TSRMLS_CC);
+
+ // retrieve the class entry linked to this object
+ auto *entry = zend_get_class_entry(object TSRMLS_CC);
+
+ // we need the C++ class meta-information object
+ ClassImpl *impl = cpp_impl(entry);
+ ClassBase *meta = impl->_base;
+
+ // convert the name to a Value object
+ Value key(name);
+
+ // check if this is a callback property
+ if (impl->_properties.find(key) != impl->_properties.end()) return true;
+
+ // call the C++ object
+ if (!meta->callIsset(base, key)) return false;
+
+ // property exists, but what does the user want to know
+ if (has_set_exists == 2) return true;
+
+ // we have to retrieve the property
+ Value value = meta->callGet(base, key);
+
+ // should we check on NULL?
+ switch (has_set_exists) {
+ case 0: return value.type() != Type::Null;
+ default: return value.boolValue();
+ }
+ }
+ catch (const NotImplemented &exception)
+ {
+ // __isset was not implemented, do we have a default?
+ if (!std_object_handlers.has_property) return 0;
+
+ // call default
+#if PHP_VERSION_ID < 50399
+ return std_object_handlers.has_property(object, name, has_set_exists TSRMLS_CC);
+#else
+ return std_object_handlers.has_property(object, name, has_set_exists, key TSRMLS_CC);
+#endif
+ }
+ catch (Exception &exception)
+ {
+ // user threw an exception in its magic method
+ // implementation, send it to user space
+ exception.process(TSRMLS_C);
+
+ // unreachable
+ return false;
+ }
+}
+
+/**
+ * Function that is called when a property is removed from the project
+ *
+ * This is the handler for the __unset() method
+ *
+ * @param object The object on which it is called
+ * @param member The member to remove
+ * @param key
+ * @param tsrm_ls
+ */
+#if PHP_VERSION_ID < 50399
+void ClassImpl::unsetProperty(zval *object, zval *member TSRMLS_DC)
+#else
+void ClassImpl::unsetProperty(zval *object, zval *member, const zend_literal *key TSRMLS_DC)
+#endif
+{
+ // the default implementation throws an exception, if we catch that
+ // we know for sure that the user has not overridden the __unset method
+ try
+ {
+ // retrieve the class entry linked to this object
+ auto *entry = zend_get_class_entry(object TSRMLS_CC);
+
+ // we need the C++ class meta-information object
+ ClassImpl *impl = cpp_impl(entry);
+
+ // property name
+ Value name(member);
+
+ // is this a callback property?
+ auto iter = impl->_properties.find(name);
+
+ // if the property does not exist, we forward to the __unset
+ if (iter == impl->_properties.end()) impl->_base->callUnset(cpp_object(object TSRMLS_CC), member);
+
+ // callback properties cannot be unset
+ zend_error(E_ERROR, "Property %s can not be unset", (const char *)name);
+ }
+ catch (const NotImplemented &exception)
+ {
+ // __unset was not implemented, do we have a default?
+ if (!std_object_handlers.unset_property) return;
+
+ // call the default
+#if PHP_VERSION_ID < 50399
+ std_object_handlers.unset_property(object, member TSRMLS_CC);
+#else
+ std_object_handlers.unset_property(object, member, key TSRMLS_CC);
+#endif
+ }
+ catch (Exception &exception)
+ {
+ // user threw an exception in its magic method
+ // implementation, send it to user space
+ exception.process(TSRMLS_C);
+ }
+}
+
+/**
+ * Function that is called when an object is about to be destructed
+ * This will call the magic __destruct method
+ * @param object
+ * @param handle
+ * @param tsrm_ls
+ */
+void ClassImpl::destructObject(zend_object *object, zend_object_handle handle TSRMLS_DC)
+{
+ // allocate memory for the object
+ MixedObject *obj = (MixedObject *)object;
+
+ // get meta info
+ ClassImpl *impl = cpp_impl(object->ce);
+
+ // prevent exceptions
+ try
+ {
+ // call the destruct function
+ if (obj->cpp) impl->_base->callDestruct(obj->cpp);
+ }
+ catch (const NotImplemented &exception)
+ {
+ // fallback on the default destructor call
+ zend_objects_destroy_object(object, handle TSRMLS_CC);
+ }
+ catch (Exception &exception)
+ {
+ // a regular Php::Exception was thrown by the extension, pass it on
+ // to PHP user space
+ exception.process(TSRMLS_C);
+ }
+}
+
+/**
+ * Function that is called to clean up space that is occupied by the object
+ * @param object The object to be deallocated
+ * @param tsrm_ls
+ */
+void ClassImpl::freeObject(zend_object *object TSRMLS_DC)
+{
+ // allocate memory for the object
+ MixedObject *obj = (MixedObject *)object;
+
+ // deallocate the cpp object
+ if (obj->cpp) delete obj->cpp;
+
+ // pass on to the default destructor
+ zend_objects_free_object_storage(object TSRMLS_CC);
+}
+
+/**
+ * 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
+ * @param tsrm_ls
+ * @return zend_object_value The newly created object
+ */
+zend_object_value ClassImpl::createObject(zend_class_entry *entry TSRMLS_DC)
+{
+ // we need the C++ class meta-information object
+ ClassImpl *impl = cpp_impl(entry);
+
+ // create a new base C++ object
+ auto *cpp = impl->_base->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;
+
+ // set the handlers
+ result.handlers = impl->objectHandlers();
+
+ // store the object
+ cpp->store(entry TSRMLS_CC);
+
+ // store the object in the object cache
+ result.handle = cpp->handle();
+
+ // done
+ return result;
+}
+
+/**
+ * Function to create a new iterator to iterate over an object
+ * @param entry The class entry
+ * @param object The object to iterate over
+ * @param by_ref ?????
+ * @param tsrm_ls
+ * @return zend_object_iterator* Pointer to the iterator
+ */
+zend_object_iterator *ClassImpl::getIterator(zend_class_entry *entry, zval *object, int by_ref TSRMLS_DC)
+{
+ // by-ref is not possible (copied from SPL)
+ if (by_ref) throw Php::Exception("Foreach by ref is not possible");
+
+ // retrieve the traversable object
+ Traversable *traversable = dynamic_cast<Traversable*>(cpp_object(object TSRMLS_CC));
+
+ // user may throw an exception in the getIterator() function
+ try
+ {
+ // create an iterator
+ auto *iterator = traversable->getIterator();
+
+ // return the implementation
+ return iterator->implementation();
+ }
+ catch (Exception &exception)
+ {
+ // user threw an exception in its method
+ // implementation, send it to user space
+ exception.process(TSRMLS_C);
+
+ // unreachable
+ return nullptr;
+ }
+}
+
+/**
+ * Method that is called to serialize an object
+ * @param object The object to be serialized
+ * @param buffer Buffer in which to store the data
+ * @param buf_len Size of the bufffer
+ * @param data ??
+ * @param tsrm_ls
+ * @return int
+ */
+int ClassImpl::serialize(zval *object, unsigned char **buffer, zend_uint *buf_len, zend_serialize_data *data TSRMLS_DC)
+{
+ // get the serializable object
+ Serializable *serializable = dynamic_cast<Serializable*>(cpp_object(object TSRMLS_CC));
+
+ // call the serialize method on the object
+ auto value = serializable->serialize();
+
+ // allocate the buffer, and copy the data into it (the zend engine will
+ // (hopefully) clean up the data for us - the default serialize method does
+ // it like this too)
+ *buffer = (unsigned char*)estrndup(value.c_str(), value.size());
+ *buf_len = value.size();
+
+ // done
+ return SUCCESS;
+}
+
+/**
+ * Method that is called to unserialize an object
+ * @param object The object to be unserialized
+ * @param entry The class entry to which is belongs
+ * @param buffer Buffer holding the unserialized data
+ * @param data All the unserialize data
+ * @param tsrm_ls
+ * @return int
+ */
+int ClassImpl::unserialize(zval **object, zend_class_entry *entry, const unsigned char *buffer, zend_uint buf_len, zend_unserialize_data *data TSRMLS_DC)
+{
+ // create the PHP object
+ object_init_ex(*object, entry);
+
+ // turn this into a serializale
+ Serializable *serializable = dynamic_cast<Serializable*>(cpp_object(*object TSRMLS_CC));
+
+ // call the unserialize method on it
+ serializable->unserialize((const char *)buffer, buf_len);
+
+ // done
+ return SUCCESS;
+}
+
+/**
+ * Retrieve an array of zend_function_entry objects that hold the
+ * properties for each method. This method is called at extension
+ * startup time to register all methods.
+ *
+ * @param classname The class name
+ * @return zend_function_entry[]
+ */
+const struct _zend_function_entry *ClassImpl::entries()
+{
+ // already initialized?
+ if (_entries) return _entries;
+
+ // allocate memory for the functions
+ _entries = new zend_function_entry[_methods.size() + 1];
+
+ // keep iterator counter
+ int i = 0;
+
+ // loop through the functions
+ for (auto &method : _methods)
+ {
+ // retrieve entry
+ zend_function_entry *entry = &_entries[i++];
+
+ // let the function fill the entry
+ method->initialize(entry, _name);
+ }
+
+ // last entry should be set to all zeros
+ zend_function_entry *last = &_entries[i];
+
+ // all should be set to zero
+ memset(last, 0, sizeof(zend_function_entry));
+
+ // done
+ return _entries;
+}
+
+/**
+ * Initialize the class, given its name
+ *
+ * The module functions are registered on module startup, but classes are
+ * initialized afterwards. The Zend engine is a strange thing. Nevertheless,
+ * this means that this method is called after the module is already available.
+ * This function will inform the Zend engine about the existence of the
+ * class.
+ *
+ * @param base the c++ class object created in the extension
+ * @param prefix namespace prefix
+ * @param tsrm_ls
+ */
+void ClassImpl::initialize(ClassBase *base, const std::string &prefix TSRMLS_DC)
+{
+ // store base pointer
+ _base = base;
+
+ // the class entry
+ zend_class_entry entry;
+
+ // update the name
+ if (prefix.size() > 0) _name = prefix + "\\" + _name;
+
+ // initialize the class entry
+ INIT_CLASS_ENTRY_EX(entry, _name.c_str(), _name.size(), entries());
+
+ // we need a special constructor
+ entry.create_object = &ClassImpl::createObject;
+
+ // register function that is called for static method calls
+ entry.get_static_method = &ClassImpl::getStaticMethod;
+
+ // for traversable classes we install a special method to get the iterator
+ if (_base->traversable()) entry.get_iterator = &ClassImpl::getIterator;
+
+ // for serializable classes, we install callbacks for serializing and unserializing
+ if (_base->serializable())
+ {
+ // add handlers to serialize and unserialize
+ entry.serialize = &ClassImpl::serialize;
+ entry.unserialize = &ClassImpl::unserialize;
+ }
+
+ // register the class
+ _entry = zend_register_internal_class(&entry TSRMLS_CC);
+
+// // register the classes
+// for (auto &interface : _interfaces)
+// {
+// // register this interface
+// zend_class_implements(_entry, 1, interface->_entry);
+// }
+
+ // allocate doc comment to contain an empty string + a hidden pointer
+ if (!_comment)
+ {
+ // allocate now
+ _comment = (char *)malloc(1 + sizeof(ClassBase *));
+
+ // empty string on first position
+ _comment[0] = '\0';
+
+ // this pointer has to be copied to temporary pointer, as &this causes compiler error
+ ClassImpl *impl = this;
+
+ // copy the 'this' pointer to the doc-comment
+ memcpy(_comment+1, &impl, sizeof(ClassImpl *));
+ }
+
+ // store pointer to the class in the unused doc_comment member
+#if PHP_VERSION_ID >= 50400
+ _entry->info.user.doc_comment = _comment;
+#else
+ // and store the wrapper inside the comment
+ _entry->doc_comment = _comment;
+#endif
+
+ // set access types flags for class
+ _entry->ce_flags = (int)_type;
+
+ // declare all member variables
+ for (auto &member : _members) member->initialize(_entry TSRMLS_CC);
+}
+
+/**
+ * End namespace
+ */
+}
+
diff --git a/src/classimpl.h b/src/classimpl.h
new file mode 100644
index 0000000..bfb4532
--- /dev/null
+++ b/src/classimpl.h
@@ -0,0 +1,407 @@
+/**
+ * ClassImpl.h
+ *
+ * Base implementation class that stores all methods and properties that
+ * exist for a class.
+ *
+ * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
+ * @copyright 2014 Copernica BV
+ */
+
+/**
+ * Set up namespace
+ */
+namespace Php {
+
+/**
+ * Class definition
+ */
+class ClassImpl
+{
+private:
+ /**
+ * Pointer to the actual Php::Class<X> that is created in the extension
+ * @var ClassBase
+ */
+ ClassBase *_base = nullptr;
+
+ /**
+ * Name of the class
+ * @var string
+ */
+ std::string _name;
+
+ /**
+ * The comment for reflexion, with a stored pointer to ourselves
+ * @var char*
+ */
+ char *_comment = nullptr;
+
+ /**
+ * The class type (this can be values like Php::Abstract and Php::Final)
+ * @var ClassType
+ */
+ ClassType _type = ClassType::Regular;
+
+ /**
+ * The class entry
+ * @var zend_class_entry
+ */
+ zend_class_entry *_entry = nullptr;
+
+ /**
+ * Pointer to the entries
+ * @var zend_function_entry[]
+ */
+ zend_function_entry *_entries = nullptr;
+
+ /**
+ * All class methods
+ * @var std::list
+ */
+ std::list<std::shared_ptr<Method>> _methods;
+
+ /**
+ * All class members (class properties)
+ * @var std::list
+ */
+ std::list<std::shared_ptr<Member>> _members;
+
+ /**
+ * Map of dynamically accessible properties
+ * @var std::map
+ */
+ std::map<std::string,std::shared_ptr<Property>> _properties;
+
+
+ /**
+ * Retrieve an array of zend_function_entry objects that hold the
+ * properties for each method. This method is called at extension
+ * startup time to register all methods.
+ *
+ * @param classname The class name
+ * @return zend_function_entry[]
+ */
+ const zend_function_entry *entries();
+
+ /**
+ * Helper method to turn a property into a zval
+ * @param value
+ * @param type
+ * @return Value
+ */
+ static zval *toZval(Value &&value, int type);
+
+public:
+ /**
+ * Constructor
+ * @param name Class name
+ * @param type Class type
+ */
+ ClassImpl(const char *name, ClassType type) : _name(name), _type(type) {}
+
+ /**
+ * No copying or moving
+ * @param that
+ */
+ ClassImpl(const ClassImpl &that) = delete;
+ ClassImpl(ClassImpl &&that) = delete;
+
+ /**
+ * Destructor
+ */
+ virtual ~ClassImpl();
+
+ /**
+ * Retrieve the extension's class object
+ * @param entry
+ * @return ClassBase
+ */
+ static ClassBase *base(zend_class_entry *entry);
+
+ /**
+ * Initialize the class, given its name
+ *
+ * The module functions are registered on module startup, but classes are
+ * initialized afterwards. The Zend engine is a strange thing. Nevertheless,
+ * this means that this method is called after the module is already available.
+ * This function will inform the Zend engine about the existence of the
+ * class.
+ *
+ * @param base The extension C++ class
+ * @param ns Namespace name
+ * @param tsrm_ls
+ */
+ void initialize(ClassBase *base, const std::string &ns TSRMLS_DC);
+
+ /**
+ * Static member functions to create or clone objects based on this class
+ * @param entry Pointer to class information
+ * @param val The object to be cloned
+ * @param tsrm_ls
+ * @return zend_object_value Object info
+ */
+ static zend_object_value createObject(zend_class_entry *entry TSRMLS_DC);
+ static zend_object_value cloneObject(zval *val TSRMLS_DC);
+ static void destructObject(zend_object *object, unsigned int handle TSRMLS_DC);
+ static void freeObject(zend_object *object TSRMLS_DC);
+
+ /**
+ * Static member function that get called when a method or object is called
+ * @param ht ??
+ * @param return_value Zval holding the variable to store the return value in
+ * @param return_value_ptr Pointer to the same zval
+ * @param this_ptr Object being called
+ * @param return_value_used Is the return value used or not?
+ * @param tsrm_ls
+ */
+ static void callMethod(int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC);
+ static void callInvoke(int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC);
+
+ /**
+ * Function that is used to count the number of elements in the object
+ * If the user has implemented the Countable interface, this method will
+ * call the count() method
+ * @param val
+ * @param count
+ * @param tsrm_ls
+ * @return int
+ */
+ static int countElements(zval *object, long *count TSRMLS_DC);
+
+ /**
+ * Function that is called when the object is used as an array in PHP
+ * @param object The object on which it is called
+ * @param offset The name of the property
+ * @param value The new value
+ * @param type The type of the variable???
+ * @param check_empty ????
+ * @return zval
+ */
+ static zval *readDimension(zval *object, zval *offset, int type TSRMLS_DC);
+ static void writeDimension(zval *object, zval *offset, zval *value TSRMLS_DC);
+ static int hasDimension(zval *object, zval *offset, int check_empty TSRMLS_DC);
+ static void unsetDimension(zval *object, zval *offset TSRMLS_DC);
+
+ /**
+ * Retrieve pointer to our own object handlers
+ * @return zend_object_handlers
+ */
+ zend_object_handlers *objectHandlers();
+
+ /**
+ * Function to create a new iterator to iterate over an object
+ * @param entry The class entry
+ * @param object The object to iterate over
+ * @param by_ref ?????
+ * @param tsrm_ls
+ * @return zend_object_iterator* Pointer to the iterator
+ */
+ static zend_object_iterator *getIterator(zend_class_entry *entry, zval *object, int by_ref TSRMLS_DC);
+
+ /**
+ * Function that is called when a property is being read
+ * @param object The object on which it is called
+ * @param offset The name of the property
+ * @param type The type of the variable???
+ * @param key ???
+ * @param tsrm_ls
+ * @return zval
+ */
+ static zval *readProperty(zval *object, zval *name, int type, const zend_literal *key TSRMLS_DC);
+ static zval *readProperty(zval *object, zval *name, int type TSRMLS_DC);
+
+ /**
+ * Function that is called when a property is set / updated
+ * @param object The object on which it is called
+ * @param name The name of the property
+ * @param value The new value
+ * @param key ???
+ * @param tsrm_ls
+ * @return zval
+ */
+ static void writeProperty(zval *object, zval *name, zval *value, const zend_literal *key TSRMLS_DC);
+ static void writeProperty(zval *object, zval *name, zval *value TSRMLS_DC);
+
+ /**
+ * Function that is called to check whether a certain property is set
+ * @param object The object on which it is called
+ * @param name The name of the property to check
+ * @param has_set_exists See above
+ * @param tsrm_ls
+ * @return bool
+ */
+ static int hasProperty(zval *object, zval *name, int has_set_exists, const zend_literal *key TSRMLS_DC);
+ static int hasProperty(zval *object, zval *name, int has_set_exists TSRMLS_DC);
+
+ /**
+ * Function that is called when a property is removed from the project
+ * @param object The object on which it is called
+ * @param member The member to remove
+ * @param tsrm_ls
+ */
+ static void unsetProperty(zval *object, zval *member, const zend_literal *key TSRMLS_DC);
+ static void unsetProperty(zval *object, zval *member TSRMLS_DC);
+
+ /**
+ * Method that returns information about the function signature of a undefined method
+ * @param object_ptr
+ * @param method
+ * @param method_len
+ * @param key
+ * @param tsrm_ls
+ * @return zend_function
+ */
+ static zend_function *getMethod(zval **object_ptr, char *method, int method_len TSRMLS_DC);
+ static zend_function *getMethod(zval **object_ptr, char *method, int method_len, const zend_literal *key TSRMLS_DC);
+
+ /**
+ * Method that returns information about the function signature of an undefined static method
+ * @param object_ptr
+ * @param method
+ * @param method_len
+ * @param key
+ * @param tsrm_ls
+ * @return zend_function
+ */
+ static zend_function *getStaticMethod(zend_class_entry *entry, char* method, int method_len TSRMLS_DC);
+
+ /**
+ * Method that returns information about the __invoke() method
+ * @param object
+ * @param entry
+ * @param func
+ * @param object_ptr
+ * @param tsrm_ls
+ * @return int
+ */
+ static int getClosure(zval *object, zend_class_entry **entry, zend_function **func, zval **object_ptr TSRMLS_DC);
+
+ /**
+ * Function to cast the object to a different type
+ * @param object
+ * @param retval
+ * @param type
+ * @param tsrm_ls
+ * @return int
+ */
+ static int cast(zval *object, zval *retval, int type TSRMLS_DC);
+
+ /**
+ * Function to compare two objects
+ * @param object1
+ * @param object2
+ * @param tsrm_ls
+ * @return int
+ */
+ static int compare(zval *object1, zval *object2 TSRMLS_DC);
+
+ /**
+ * Methods that are called to serialize/unserialize an object
+ * @param object The object to be serialized
+ * @param entry The class entry to which the object belongs
+ * @param buffer Buffer in which to store the data
+ * @param buf_len Size of the bufffer
+ * @param data Structure describing the serialize/unserialize data
+ * @param tsrm_ls
+ * @return int
+ */
+ static int serialize(zval *object, unsigned char **buffer, unsigned int *buf_len, zend_serialize_data *data TSRMLS_DC);
+ static int unserialize(zval **object, zend_class_entry *entry, const unsigned char *buffer, unsigned int buf_len, zend_unserialize_data *data TSRMLS_DC);
+
+ /**
+ * Add a method to the class
+ * zend_serialize_data
+ * The method will be accessible as one of the class methods in your PHP
+ * code. When the method is called, it will automatically be forwarded
+ * to the C++ implementation. The flags can be Php::Public, Php::Protected
+ * or Php::Private (using private methods can be useful if you for example
+ * want to make the __construct() function private). The access-modified
+ * flag can be bitwise combined with the flag Php::Final or Php::Abstract).
+ *
+ * @param name Name of the method
+ * @param method The actual method
+ * @param flags Optional flags
+ * @param args Description of the supported arguments
+ */
+ void method(const char *name, const method_callback_0 &method, int flags=0, const Arguments &args = {}) { _methods.push_back(std::make_shared<Method>(name, method, flags, args)); }
+ void method(const char *name, const method_callback_1 &method, int flags=0, const Arguments &args = {}) { _methods.push_back(std::make_shared<Method>(name, method, flags, args)); }
+ void method(const char *name, const method_callback_2 &method, int flags=0, const Arguments &args = {}) { _methods.push_back(std::make_shared<Method>(name, method, flags, args)); }
+ void method(const char *name, const method_callback_3 &method, int flags=0, const Arguments &args = {}) { _methods.push_back(std::make_shared<Method>(name, method, flags, args)); }
+ void method(const char *name, const method_callback_4 &method, int flags=0, const Arguments &args = {}) { _methods.push_back(std::make_shared<Method>(name, method, flags, args)); }
+ void method(const char *name, const method_callback_5 &method, int flags=0, const Arguments &args = {}) { _methods.push_back(std::make_shared<Method>(name, method, flags, args)); }
+ void method(const char *name, const method_callback_6 &method, int flags=0, const Arguments &args = {}) { _methods.push_back(std::make_shared<Method>(name, method, flags, args)); }
+ void method(const char *name, const method_callback_7 &method, int flags=0, const Arguments &args = {}) { _methods.push_back(std::make_shared<Method>(name, method, flags, args)); }
+
+ /**
+ * Add a static method to the class
+ *
+ * Because a C++ static method is just a regular function, that happens to
+ * have access to the private variables of the class at compile time, you
+ * can register any function that matches one of the function signatures
+ *
+ * @param name Name of the method
+ * @param method The actual method
+ * @param flags Optional flags
+ * @param args Description of the supported arguments
+ */
+ void method(const char *name, const native_callback_0 &method, int flags=0, const Arguments &args = {}) { _methods.push_back(std::make_shared<Method>(name, method, flags | ZEND_ACC_STATIC, args)); }
+ void method(const char *name, const native_callback_1 &method, int flags=0, const Arguments &args = {}) { _methods.push_back(std::make_shared<Method>(name, method, flags | ZEND_ACC_STATIC, args)); }
+ void method(const char *name, const native_callback_2 &method, int flags=0, const Arguments &args = {}) { _methods.push_back(std::make_shared<Method>(name, method, flags | ZEND_ACC_STATIC, args)); }
+ void method(const char *name, const native_callback_3 &method, int flags=0, const Arguments &args = {}) { _methods.push_back(std::make_shared<Method>(name, method, flags | ZEND_ACC_STATIC, args)); }
+
+ /**
+ * Add an abstract method to the class
+ *
+ * @param name Name of the method
+ * @param flags Optional flags (like public or protected)
+ * @param args Description of the supported arguments
+ */
+ void method(const char *name, int flags=0, const Arguments &args = {}) { _methods.push_back(std::make_shared<Method>(name, Abstract | flags, args)); }
+
+ /**
+ * Add a property to the class
+ *
+ * Every instance of this class will have this property. The property
+ * can be Php::Public, Php::Protected or Php::Private (altough setting
+ * private properties is odd as the implementation of the class is in CPP,
+ * so why use private properties while the whole implementation is already
+ * hidden)
+ *
+ * @param name Name of the property
+ * @param value Actual property value
+ * @param flags Optional flags
+ */
+ void property(const char *name, std::nullptr_t value, int flags = Php::Public) { _members.push_back(std::make_shared<NullMember>(name, flags)); }
+ void property(const char *name, int16_t value, int flags = Php::Public) { _members.push_back(std::make_shared<NumericMember>(name, value, flags)); }
+ void property(const char *name, int32_t value, int flags = Php::Public) { _members.push_back(std::make_shared<NumericMember>(name, value, flags)); }
+ void property(const char *name, int64_t value, int flags = Php::Public) { _members.push_back(std::make_shared<NumericMember>(name, value, flags)); }
+ void property(const char *name, bool value, int flags = Php::Public) { _members.push_back(std::make_shared<BoolMember>(name, value, flags)); }
+ void property(const char *name, char value, int flags = Php::Public) { _members.push_back(std::make_shared<StringMember>(name, &value, 1, flags)); }
+ void property(const char *name, const std::string &value, int flags = Php::Public) { _members.push_back(std::make_shared<StringMember>(name, value, flags)); }
+ void property(const char *name, const char *value, int flags = Php::Public) { _members.push_back(std::make_shared<StringMember>(name, value, strlen(value), flags)); }
+ void property(const char *name, double value, int flags = Php::Public) { _members.push_back(std::make_shared<FloatMember>(name, value, flags)); }
+
+ /**
+ * Set property with callbacks
+ * @param name Name of the property
+ * @param getter Getter method
+ * @param setter Setter method
+ */
+ void property(const char *name, const getter_callback_0 &getter) { _properties[name] = std::make_shared<Property>(getter); }
+ void property(const char *name, const getter_callback_1 &getter) { _properties[name] = std::make_shared<Property>(getter); }
+ void property(const char *name, const getter_callback_0 &getter, const setter_callback_0 &setter) { _properties[name] = std::make_shared<Property>(getter,setter); }
+ void property(const char *name, const getter_callback_1 &getter, const setter_callback_0 &setter) { _properties[name] = std::make_shared<Property>(getter,setter); }
+ void property(const char *name, const getter_callback_0 &getter, const setter_callback_1 &setter) { _properties[name] = std::make_shared<Property>(getter,setter); }
+ void property(const char *name, const getter_callback_1 &getter, const setter_callback_1 &setter) { _properties[name] = std::make_shared<Property>(getter,setter); }
+
+
+
+
+};
+
+/**
+ * End namespace
+ */
+}
+
diff --git a/src/includes.h b/src/includes.h
index f06f686..c8f69e9 100644
--- a/src/includes.h
+++ b/src/includes.h
@@ -65,8 +65,8 @@
#include "../include/traversable.h"
#include "../include/classtype.h"
#include "../include/classbase.h"
-#include "../include/class.h"
#include "../include/interface.h"
+#include "../include/class.h"
#include "../include/namespace.h"
#include "../include/extension.h"
#include "../include/call.h"
@@ -94,6 +94,7 @@
#include "invaliditerator.h"
#include "traverseiterator.h"
#include "streambuf.h"
+#include "classimpl.h"
#ifndef ZVAL_COPY_VALUE
#define ZVAL_COPY_VALUE(z, v) \
diff --git a/src/namespace.cpp b/src/namespace.cpp
index e828112..d462684 100644
--- a/src/namespace.cpp
+++ b/src/namespace.cpp
@@ -123,7 +123,7 @@ void Namespace::initialize(const std::string &parent TSRMLS_DC)
std::string prefix = parent.size() ? parent + "\\" + _name : _name;
// loop through the classes in this namespace
- for (auto &c : _classes) c->initialize(prefix TSRMLS_CC);
+ for (auto &c : _classes) c->implementation()->initialize(c.get(), prefix TSRMLS_CC);
// and loop through the other namespaces
for (auto &n : _namespaces) n->initialize(prefix TSRMLS_CC);