From bbdcdae98979e002476fc1e8296effd0b270928b Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Wed, 2 Apr 2014 22:25:45 +0200 Subject: 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 --- src/base.cpp | 4 +- src/classbase.cpp | 1854 ++--------------------------------------------------- src/classimpl.cpp | 1444 +++++++++++++++++++++++++++++++++++++++++ src/classimpl.h | 407 ++++++++++++ src/includes.h | 3 +- src/namespace.cpp | 2 +- 6 files changed, 1894 insertions(+), 1820 deletions(-) create mode 100644 src/classimpl.cpp create mode 100644 src/classimpl.h (limited to 'src') 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 @@ -13,1411 +13,28 @@ */ 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(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(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(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(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(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(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(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(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(classname, ClassType::Abstract); + else if (flags & Final) _impl = std::make_shared(classname, ClassType::Final); + else _impl = std::make_shared(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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 + * @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(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(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(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(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(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(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(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(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 + * @copyright 2014 Copernica BV + */ + +/** + * Set up namespace + */ +namespace Php { + +/** + * Class definition + */ +class ClassImpl +{ +private: + /** + * Pointer to the actual Php::Class 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> _methods; + + /** + * All class members (class properties) + * @var std::list + */ + std::list> _members; + + /** + * Map of dynamically accessible properties + * @var std::map + */ + std::map> _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(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(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(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(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(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(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(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(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(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(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(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(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(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(name, flags)); } + void property(const char *name, int16_t value, int flags = Php::Public) { _members.push_back(std::make_shared(name, value, flags)); } + void property(const char *name, int32_t value, int flags = Php::Public) { _members.push_back(std::make_shared(name, value, flags)); } + void property(const char *name, int64_t value, int flags = Php::Public) { _members.push_back(std::make_shared(name, value, flags)); } + void property(const char *name, bool value, int flags = Php::Public) { _members.push_back(std::make_shared(name, value, flags)); } + void property(const char *name, char value, int flags = Php::Public) { _members.push_back(std::make_shared(name, &value, 1, flags)); } + void property(const char *name, const std::string &value, int flags = Php::Public) { _members.push_back(std::make_shared(name, value, flags)); } + void property(const char *name, const char *value, int flags = Php::Public) { _members.push_back(std::make_shared(name, value, strlen(value), flags)); } + void property(const char *name, double value, int flags = Php::Public) { _members.push_back(std::make_shared(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(getter); } + void property(const char *name, const getter_callback_1 &getter) { _properties[name] = std::make_shared(getter); } + void property(const char *name, const getter_callback_0 &getter, const setter_callback_0 &setter) { _properties[name] = std::make_shared(getter,setter); } + void property(const char *name, const getter_callback_1 &getter, const setter_callback_0 &setter) { _properties[name] = std::make_shared(getter,setter); } + void property(const char *name, const getter_callback_0 &getter, const setter_callback_1 &setter) { _properties[name] = std::make_shared(getter,setter); } + void property(const char *name, const getter_callback_1 &getter, const setter_callback_1 &setter) { _properties[name] = std::make_shared(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); -- cgit v1.2.3