From 35fd3ccbeb4def71b4d8a59dfbb5c31201b099b9 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sun, 6 Apr 2014 21:53:24 +0200 Subject: renamed src directory to zend directory, disabled TSRM debug code --- Makefile | 7 +- src/arithmetic.h | 286 ------- src/base.cpp | 287 ------- src/boolmember.h | 67 -- src/callable.cpp | 133 ---- src/callable.h | 190 ----- src/classbase.cpp | 130 ---- src/classimpl.cpp | 1429 ---------------------------------- src/classimpl.h | 445 ----------- src/extension.cpp | 103 --- src/extensionimpl.cpp | 300 -------- src/extensionimpl.h | 213 ------ src/floatmember.h | 67 -- src/function.h | 102 --- src/global.cpp | 45 -- src/globals.cpp | 92 --- src/hashiterator.h | 250 ------ src/hashmember.cpp | 40 - src/includes.h | 121 --- src/init.h | 54 -- src/invaliditerator.h | 82 -- src/iteratorimpl.cpp | 183 ----- src/iteratorimpl.h | 190 ----- src/member.h | 77 -- src/members.cpp | 90 --- src/method.h | 139 ---- src/modifiers.cpp | 39 - src/namespace.cpp | 134 ---- src/notimplemented.h | 51 -- src/nullmember.h | 59 -- src/numericmember.h | 67 -- src/object.cpp | 79 -- src/objectimpl.h | 204 ----- src/origexception.h | 145 ---- src/parametersimpl.h | 53 -- src/property.h | 160 ---- src/streambuf.cpp | 93 --- src/streambuf.h | 80 -- src/streams.cpp | 40 - src/stringmember.h | 83 -- src/super.cpp | 71 -- src/traverseiterator.h | 259 ------- src/value.cpp | 1911 ---------------------------------------------- src/valueiterator.cpp | 100 --- src/valueiteratorimpl.h | 71 -- zend/arithmetic.h | 286 +++++++ zend/base.cpp | 287 +++++++ zend/boolmember.h | 67 ++ zend/callable.cpp | 133 ++++ zend/callable.h | 190 +++++ zend/classbase.cpp | 130 ++++ zend/classimpl.cpp | 1429 ++++++++++++++++++++++++++++++++++ zend/classimpl.h | 445 +++++++++++ zend/extension.cpp | 103 +++ zend/extensionimpl.cpp | 300 ++++++++ zend/extensionimpl.h | 213 ++++++ zend/floatmember.h | 67 ++ zend/function.h | 102 +++ zend/global.cpp | 45 ++ zend/globals.cpp | 92 +++ zend/hashiterator.h | 250 ++++++ zend/hashmember.cpp | 40 + zend/includes.h | 121 +++ zend/init.h | 54 ++ zend/invaliditerator.h | 82 ++ zend/iteratorimpl.cpp | 183 +++++ zend/iteratorimpl.h | 190 +++++ zend/member.h | 77 ++ zend/members.cpp | 90 +++ zend/method.h | 139 ++++ zend/modifiers.cpp | 39 + zend/namespace.cpp | 134 ++++ zend/notimplemented.h | 51 ++ zend/nullmember.h | 59 ++ zend/numericmember.h | 67 ++ zend/object.cpp | 79 ++ zend/objectimpl.h | 204 +++++ zend/origexception.h | 145 ++++ zend/parametersimpl.h | 53 ++ zend/property.h | 160 ++++ zend/streambuf.cpp | 93 +++ zend/streambuf.h | 80 ++ zend/streams.cpp | 40 + zend/stringmember.h | 83 ++ zend/super.cpp | 71 ++ zend/traverseiterator.h | 259 +++++++ zend/value.cpp | 1911 ++++++++++++++++++++++++++++++++++++++++++++++ zend/valueiterator.cpp | 100 +++ zend/valueiteratorimpl.h | 71 ++ 89 files changed, 8818 insertions(+), 8817 deletions(-) delete mode 100644 src/arithmetic.h delete mode 100644 src/base.cpp delete mode 100644 src/boolmember.h delete mode 100644 src/callable.cpp delete mode 100644 src/callable.h delete mode 100644 src/classbase.cpp delete mode 100644 src/classimpl.cpp delete mode 100644 src/classimpl.h delete mode 100644 src/extension.cpp delete mode 100644 src/extensionimpl.cpp delete mode 100644 src/extensionimpl.h delete mode 100644 src/floatmember.h delete mode 100644 src/function.h delete mode 100644 src/global.cpp delete mode 100644 src/globals.cpp delete mode 100644 src/hashiterator.h delete mode 100644 src/hashmember.cpp delete mode 100644 src/includes.h delete mode 100644 src/init.h delete mode 100644 src/invaliditerator.h delete mode 100644 src/iteratorimpl.cpp delete mode 100644 src/iteratorimpl.h delete mode 100644 src/member.h delete mode 100644 src/members.cpp delete mode 100644 src/method.h delete mode 100644 src/modifiers.cpp delete mode 100644 src/namespace.cpp delete mode 100644 src/notimplemented.h delete mode 100644 src/nullmember.h delete mode 100644 src/numericmember.h delete mode 100644 src/object.cpp delete mode 100644 src/objectimpl.h delete mode 100644 src/origexception.h delete mode 100644 src/parametersimpl.h delete mode 100644 src/property.h delete mode 100644 src/streambuf.cpp delete mode 100644 src/streambuf.h delete mode 100644 src/streams.cpp delete mode 100644 src/stringmember.h delete mode 100644 src/super.cpp delete mode 100644 src/traverseiterator.h delete mode 100644 src/value.cpp delete mode 100644 src/valueiterator.cpp delete mode 100644 src/valueiteratorimpl.h create mode 100644 zend/arithmetic.h create mode 100644 zend/base.cpp create mode 100644 zend/boolmember.h create mode 100644 zend/callable.cpp create mode 100644 zend/callable.h create mode 100644 zend/classbase.cpp create mode 100644 zend/classimpl.cpp create mode 100644 zend/classimpl.h create mode 100644 zend/extension.cpp create mode 100644 zend/extensionimpl.cpp create mode 100644 zend/extensionimpl.h create mode 100644 zend/floatmember.h create mode 100644 zend/function.h create mode 100644 zend/global.cpp create mode 100644 zend/globals.cpp create mode 100644 zend/hashiterator.h create mode 100644 zend/hashmember.cpp create mode 100644 zend/includes.h create mode 100644 zend/init.h create mode 100644 zend/invaliditerator.h create mode 100644 zend/iteratorimpl.cpp create mode 100644 zend/iteratorimpl.h create mode 100644 zend/member.h create mode 100644 zend/members.cpp create mode 100644 zend/method.h create mode 100644 zend/modifiers.cpp create mode 100644 zend/namespace.cpp create mode 100644 zend/notimplemented.h create mode 100644 zend/nullmember.h create mode 100644 zend/numericmember.h create mode 100644 zend/object.cpp create mode 100644 zend/objectimpl.h create mode 100644 zend/origexception.h create mode 100644 zend/parametersimpl.h create mode 100644 zend/property.h create mode 100644 zend/streambuf.cpp create mode 100644 zend/streambuf.h create mode 100644 zend/streams.cpp create mode 100644 zend/stringmember.h create mode 100644 zend/super.cpp create mode 100644 zend/traverseiterator.h create mode 100644 zend/value.cpp create mode 100644 zend/valueiterator.cpp create mode 100644 zend/valueiteratorimpl.h diff --git a/Makefile b/Makefile index 9a66157..5955a3d 100644 --- a/Makefile +++ b/Makefile @@ -120,12 +120,13 @@ MKDIR = mkdir -p # The source files # # For this we use a special Makefile function that automatically scans the -# src/ directory for all *.cpp files. No changes are probably necessary here +# src/, zend/ and hhvm/ directories for all *.cpp files. No changes are +# probably necessary here # SOURCES = $(wildcard src/*.cpp) -PHP_SOURCES = $(wildcard src/zend/*.cpp) -HHVM_SOURCES = $(wildcard src/hhvm/*.cpp) +PHP_SOURCES = $(wildcard zend/*.cpp) +HHVM_SOURCES = $(wildcard hhvm/*.cpp) # # The object files diff --git a/src/arithmetic.h b/src/arithmetic.h deleted file mode 100644 index 20e346e..0000000 --- a/src/arithmetic.h +++ /dev/null @@ -1,286 +0,0 @@ -/** - * Arithmethic.h - * - * Helper class that takes care of arithmetic operations on PHP variables - * - * @author Emiel Bruijntjes - * @copyright 2013 Copernica BV - */ - -/** - * Set up namespace - */ -namespace Php { - -/** - * Class definition - */ -template < template class F> -class Arithmetic -{ -public: - /** - * Constructor - * @param value The original object - */ - Arithmetic(Value *value) : _value(value) {} - - /** - * Destructor - */ - virtual ~Arithmetic() {} - - /** - * Apply a number, and return a new value object after running the arithmetic function - * @param value - * @return Value - */ - Value apply(const Value &value) - { - // is this a type a floating point type? - if (value.isFloat()) return apply(value.floatValue()); - - // we are going to treat it as a numeric (non floating) type - return apply(value.numericValue()); - } - - /** - * Apply a number, and return a new value object after running the arithmetic function - * @param value - * @return Value - */ - Value apply(int16_t value) - { - // check if the current object is a floating point number - if (_value->isFloat()) return Value(F()(_value->floatValue(), value)); - - // apply to natural numbers - return Value(F()(_value->numericValue(), value)); - } - - /** - * Apply a number, and return a new value object after running the arithmetic function - * @param value - * @return Value - */ - Value apply(int32_t value) - { - // check if the current object is a floating point number - if (_value->isFloat()) return Value(F()(_value->floatValue(), value)); - - // apply to natural numbers - return Value(F()(_value->numericValue(), value)); - } - - /** - * Apply a number, and return a new value object after running the arithmetic function - * @param value - * @return Value - */ - Value apply(int64_t value) - { - // check if the current object is a floating point number - if (_value->isFloat()) return Value(F()(_value->floatValue(), value)); - - // apply to natural numbers - return Value(F()(_value->numericValue(), value)); - } - - /** - * Apply a boolean (treat is as 0 or 1), and return a new value object after running the arithmetic function - * @param value - * @return Value - */ - Value apply(bool value) - { - // check if the current object is a floating point number - if (_value->isFloat()) return Value(F()(_value->floatValue(), value?1:0)); - - // apply to natural numbers - return Value(F()(_value->numericValue(), value?1:0)); - } - - /** - * Apply a character (value between '0' and '9'), and return a new value object after running the arithmetic function - * @param value - * @return Value - */ - Value apply(char value) - { - // convert to an integer - int v = value < '0' || value > '9' ? 0 : value - '0'; - - // check if the current object is a floating point number - if (_value->isFloat()) return Value(F()(_value->floatValue(), v)); - - // apply to natural numbers - return Value(F()(_value->numericValue(), v)); - } - - /** - * Apply a string (representing a number), and return a new value object after running the arithmetic function - * @param value - * @return Value - */ - Value apply(const std::string &value) - { - // convert string to integer - return apply(atoi(value.c_str())); - } - - /** - * Apply a string (representing a number), and return a new value object after running the arithmetic function - * @param value - * @return Value - */ - Value apply(const char *value) - { - // convert string to integer - return apply(atoi(value)); - } - - /** - * Apply a string (representing a number), and return a new value object after running the arithmetic function - * @param value - * @return Value - */ - Value apply(double value) - { - return Value(F()(_value->floatValue(), value)); - } - - /** - * Assign a different value object, applying the arithmetic operation - * @param value - * @return Value - */ - Value &assign(const Value &value) - { - // is this a type a floating point type? - if (value.isFloat()) return assign(value.floatValue()); - - // we are going to treat it as a numeric (non floating) type - return assign(value.numericValue()); - } - - /** - * Assign a 16bit number, applying the arithmetic operation - * @param value - * @return Value - */ - Value &assign(int16_t value) - { - // is the current object a floating point type? - if (_value->isFloat()) return _value->operator=(F()(_value->floatValue(), value)); - - // do a numeric operation - return _value->operator=(F()(_value->numericValue(), value)); - } - - /** - * Assign 32bit integer, applying the arithmetic operation - * @param value - * @return Value - */ - Value &assign(int32_t value) - { - // is the current object a floating point type? - if (_value->isFloat()) return _value->operator=(F()(_value->floatValue(), value)); - - // do a numeric operation - return _value->operator=(F()(_value->numericValue(), value)); - } - - /** - * Assign 64bit integer, applying the arithmetic operation - * @param value - * @return Value - */ - Value &assign(int64_t value) - { - // is the current object a floating point type? - if (_value->isFloat()) return _value->operator=(F()(_value->floatValue(), value)); - - // do a numeric operation - return _value->operator=(F()(_value->numericValue(), value)); - } - - /** - * Assign 64bit integer - which is treated as 1 or 0 - applying the arithmetic operation - * @param value - * @return Value - */ - Value &assign(bool value) - { - // is the current object a floating point type? - if (_value->isFloat()) return _value->operator=(F()(_value->floatValue(), value?1:0)); - - // do a numeric operation - return _value->operator=(F()(_value->numericValue(), value?1:0)); - } - - /** - * Assign a single character - which is treated as an int, and applying the arithmetic function - * @param value - * @return Value - */ - Value &assign(char value) - { - // convert to an integer - int v = value < '0' || value > '9' ? 0 : value - '0'; - - // is the current object a floating point type? - if (_value->isFloat()) return _value->operator=(F()(_value->floatValue(), v)); - - // do a numeric operation - return _value->operator=(F()(_value->numericValue(), v)); - } - - /** - * Assign a a string - treating it as an integer, and applying the arithmetic function - * @param value - * @return Value - */ - Value &assign(const std::string &value) - { - // assign integer - return assign(atoi(value.c_str())); - } - - /** - * Assign a string - treating it as an integer, and applying the arithmetic function - * @param value - * @return Value - */ - Value &assign(const char *value) - { - // assign integer - return assign(atoi(value)); - } - - /** - * Assign a double, applying the arithmetic operation - * @param value - * @return Value - */ - Value &assign(double value) - { - // do float operation - return _value->operator=(F()(_value->floatValue(), value)); - } - -private: - /** - * Pointer to the original value object - * @var Value - */ - Value *_value; - - -}; - -/** - * End of namespace - */ -} - diff --git a/src/base.cpp b/src/base.cpp deleted file mode 100644 index 5d15011..0000000 --- a/src/base.cpp +++ /dev/null @@ -1,287 +0,0 @@ -/** - * Base.cpp - * - * Implementation file for the base of all classes - * - * @copyright 2014 Copernica BV - */ -#include "includes.h" - -/** - * Set up namespace - */ -namespace Php { - -/** - * Store the object in the PHP object cache - * @param entry Class entry - * @param tsrm_ls - * @return MixedObject - */ -//MixedObject *Base::store(zend_class_entry *entry TSRMLS_DC) -//{ -// // allocate memory for the object -// MixedObject *result = (MixedObject *)emalloc(sizeof(MixedObject)); -// -// // store the new c++ object -// result->cpp = this; -// -// // store the class entry in the newly created object -// result->php.ce = entry; -// -// // initialize the object -// zend_object_std_init(&result->php, entry TSRMLS_CC); -// -//#if PHP_VERSION_ID < 50399 -// -// // tmp variable -// zval *tmp; -// -// // initialize the properties, php 5.3 way -// zend_hash_copy(result->php.properties, &entry->default_properties, (copy_ctor_func_t) zval_property_ctor, &tmp, sizeof(zval*)); -// -//#else -// -// // version higher than 5.3 have an easier way to initialize -// object_properties_init(&result->php, entry); -// -//#endif -// -//#ifdef ZTS -// -// // when in thread safety mode, the destruct method and free method have -// // an extra parameter holding thread information -// using DestructType = void(zend_object*,unsigned int,void***); -// using FreeType = void(zend_object*,void***); -// -//#else -// -// // not in thread mode: no special parameter for the tsrm_ls variable -// using DestructType = void(zend_object*,unsigned int); -// using FreeType = void(zend_object*); -// -//#endif -// -// // store the two destruct methods in temporary vars -// 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... -// _handle = zend_objects_store_put(result, (zend_objects_store_dtor_t)destructMethod, (zend_objects_free_object_storage_t)freeMethod, NULL TSRMLS_CC); -// -// // done -// return result; -//} - -/** - * Overridable method that is called right before an object is destructed - */ -void Base::__destruct() const -{ - // throw exception, so that the PHP-CPP library will check if the user - // somehow registered an explicit __destruct method - throw NotImplemented(); -} - -/** - * Overridable method that is called to check if a property is set - * - * The default implementation does nothing, and the script will fall back - * to accessing the regular object properties - * - * @param key - * @return bool - */ -bool Base::__isset(const Php::Value &key) const -{ - // throw an exception that will be caught in the ClassBase class, - // so that the default implementation of the unset function can be called - throw NotImplemented(); -} - -/** - * Overridable method that is called to set a new property - * - * The default implementation does nothing, and the script will fall back - * to accessing the regular object properties - * - * @param key - * @param value - */ -void Base::__set(const Php::Value &key, const Php::Value &value) const -{ - // throw an exception that will be caught in the ClassBase class, - // so that the default implementation of the unset function can be called - throw NotImplemented(); -} - -/** - * Retrieve a property - * - * The default implementation does nothing, and the script will fall back - * to accessing the regular object properties - * - * @param key - * @return value - */ -Php::Value Base::__get(const Php::Value &key) const -{ - // throw an exception that will be caught in the ClassBase class, - // so that the default implementation of the function can be called - throw NotImplemented(); - - // unreachable code - return nullptr; -} - -/** - * Remove a member - * - * The default implementation does nothing, and the script will fall back - * to accessing the regular object properties - * - * @param key - */ -void Base::__unset(const Php::Value &key) const -{ - // throw an exception that will be caught in the ClassBase class, - // so that the default implementation of the function can be called - throw NotImplemented(); -} - -/** - * Call a method - * - * This method is called when a method is called from the PHP script that - * was not explicitly defined. You can use this to catch variable method - * names, or to support all thinkable method names. - * - * @param method Name of the method that was called - * @param params The parameters that were passed to the function - * @return Value The return value - */ -Value Base::__call(const char *method, Parameters ¶ms) const -{ - // throw an exception that will be caught in the ClassBase class, - // so that the default implementation of the function can be called - throw NotImplemented(); - - // unreachable code - return nullptr; -} - -/** - * Call the class as if it was a function - * - * This method is called when a an object is used with () operators: - * $object(). You can override this method to make objects callable. - * - * @param params The parameters that were passed to the function - * @return Value The return value - */ -Value Base::__invoke(Parameters ¶ms) const -{ - // throw an exception that will be caught in the ClassBase class, - // so that the default implementation of the function can be called - throw NotImplemented(); - - // unreachable code - return nullptr; -} - -/** - * Cast the object to a string - * - * This method is called when an object is casted to a string, or when - * it is used in a string context - * - * @return Value The object as a string - */ -Value Base::__toString() const -{ - // throw an exception that will be caught in the ClassBase class, - // so that the default implementation of the function can be called - throw NotImplemented(); - - // unreachable code - return nullptr; -} - -/** - * Cast the object to an integer - * - * This method is called when an object is casted to an integer, or when - * it is used in an integer context - * - * @return int Integer value - */ -Value Base::__toInteger() const -{ - // throw an exception that will be caught in the ClassBase class, - // so that the default implementation of the function can be called - throw NotImplemented(); - - // unreachable code - return 0; -} - -/** - * Cast the object to a float - * - * This method is called when an object is casted to a float, or when it - * is used in a float context - * - * @return double Floating point value - */ -Value Base::__toFloat() const -{ - // throw an exception that will be caught in the ClassBase class, - // so that the default implementation of the function can be called - throw NotImplemented(); - - // unreachable code - return 0.0; -} - -/** - * Cast the object to a boolean - * - * This method is called when an object is casted to a bool, or when it - * is used in a boolean context - * - * @return bool - */ -Value Base::__toBool() const -{ - // throw an exception that will be caught in the ClassBase class, - // so that the default implementation of the function can be called - throw NotImplemented(); - - // unreachable code - return false; -} - -/** - * Compare the object with a different object - * - * Check how a different object compares to this object - * - * @param that Object to compare with - * @return int - */ -int Base::__compare(const Base &that) const -{ - // throw an exception that will be caught in the ClassBase class, - // so that the default implementation of the function can be called - throw NotImplemented(); - - // unreachable code - return 1; -} - -/** - * End namespace - */ -} - diff --git a/src/boolmember.h b/src/boolmember.h deleted file mode 100644 index 5b5d43d..0000000 --- a/src/boolmember.h +++ /dev/null @@ -1,67 +0,0 @@ -/** - * BoolMember.h - * - * Implementation for a property that is initially set to a boolean value - * - * @author Emiel Bruijntjes - * @copyright 2013 Copernica BV - */ - -/** - * Set up namespace - */ -namespace Php { - -/** - * Class definition - */ -class BoolMember : public Member -{ -private: - /** - * The value - * @var bool - */ - bool _value; - -public: - /** - * Constructor - * @param name - * @param value - * @param flags - */ - BoolMember(const char *name, bool value, int flags) : Member(name, flags), _value(value) {} - - /** - * Destructor - */ - virtual ~BoolMember() {} - - /** - * Virtual method to declare a class constant - * @param entry Class entry - * @param tsrm_ls - */ - virtual void constant(struct _zend_class_entry *entry TSRMLS_DC) override - { - zend_declare_class_constant_bool(entry, _name.c_str(), _name.size(), _value TSRMLS_CC); - } - - /** - * Virtual method to declare the property - * @param entry Class entry - * @param tsrm_ls - */ - virtual void declare(struct _zend_class_entry *entry TSRMLS_DC) override - { - // char* cast is necessary for php 5.3 - zend_declare_property_bool(entry, (char *)_name.c_str(), _name.size(), _value, _flags TSRMLS_CC); - } -}; - -/** - * End of namespace - */ -} - diff --git a/src/callable.cpp b/src/callable.cpp deleted file mode 100644 index 737b85b..0000000 --- a/src/callable.cpp +++ /dev/null @@ -1,133 +0,0 @@ -/** - * Function.cpp - * - * Implementation for the function class - * - * @author Emiel Bruijntjes - * @copyright 2013 Copernica BV - */ -#include "includes.h" - -/** - * Set up namespace - */ -namespace Php { - -/** - * Function that is called by the Zend engine every time that a function gets called - * @param ht - * @param return_value - * @param return_value_ptr - * @param this_ptr - * @param return_value_used - * @param tsrm_ls - * @return integer - */ -static void invoke_callable(INTERNAL_FUNCTION_PARAMETERS) -{ - // find the function name - const char *name = get_active_function_name(TSRMLS_C); - - // uncover the hidden pointer inside the function name - Callable *callable = HiddenPointer(name); - - // wrap the return value - Value result(return_value, true); - - // construct parameters - ParametersImpl params(this_ptr, ZEND_NUM_ARGS() TSRMLS_CC); - - // the function could throw an exception - try - { - // get the result - result = callable->invoke(params); - } - catch (Exception &exception) - { - // process the exception - process(exception TSRMLS_CC); - } -} - -/** - * Fill a function entry - * - * This method is called when the extension is registering itself, when the - * function or method introces himself - * - * @param entry Entry to be filled - * @param classname Optional class name - * @param flags Is this a public property? - */ -void Callable::initialize(zend_function_entry *entry, const char *classname, int flags) const -{ - // fill the members of the entity, and hide a pointer to the current object in the name - entry->fname = (const char *)_ptr; - entry->handler = invoke_callable; - entry->arg_info = _argv; - entry->num_args = _argc; - entry->flags = flags; - - // we should fill the first argument as well - initialize((zend_arg_info *)entry->arg_info, classname); -} - -/** - * Fill a function entry - * @param info Info to be filled - * @param classname Optional classname - */ -void Callable::initialize(zend_arg_info *info, const char *classname) const -{ -#if PHP_VERSION_ID >= 50400 - // up until php 5.3, the first info object is filled with alternative information, - // later it is casted to a zend_internal_function object - auto *finfo = (zend_internal_function_info *)info; - - // fill in all the members, note that return reference is false by default, - // because we do not support returning references in PHP-CPP, although Zend - // engine allows it. Inside the name we hide a pointer to the current object - finfo->_name = _ptr; - finfo->_name_len = strlen(_ptr); - finfo->_class_name = classname; - - // number of required arguments, and the expected return type - finfo->required_num_args = _required; - finfo->_type_hint = (unsigned char)_return; - - // we do not support return-by-reference - finfo->return_reference = false; - -# if PHP_VERSION_ID >= 50600 - // since php 5.6 there are _allow_null and _is_variadic properties. It's - // not exactly clear what they do (@todo find this out) so for now we set - // them to false - finfo->_allow_null = false; - finfo->_is_variadic = false; - -# else - // passing by reference is not used (only for php 5.4 and php 5.5) - finfo->pass_rest_by_reference = false; -# endif - -#else - // php 5.3 code - info->name = nullptr; - info->name_len = 0; - info->class_name = nullptr; - info->class_name_len = 0; - info->array_type_hint = false; - info->allow_null = false; - info->pass_by_reference = false; - info->return_reference = false; - info->required_num_args = _required; -#endif -} - -/** - * End of namespace - */ -} - - diff --git a/src/callable.h b/src/callable.h deleted file mode 100644 index 65ce719..0000000 --- a/src/callable.h +++ /dev/null @@ -1,190 +0,0 @@ -/** - * Callable.h - * - * Object represents a callable function or method that is defined with the CPP - * API. - * - * @author Emiel Bruijntjes - * @copyright 2013 Copernica BV - */ - -/** - * Set up namespace - */ -namespace Php { - -/** - * Class definition - */ -class Callable -{ -public: - /** - * Constructor - * @param name Function or method name - * @param arguments Information about the arguments - */ - Callable(const char *name, const Arguments &arguments = {}) : _ptr(this, name) - { - // construct vector for arguments - _argc = arguments.size(); - _argv = new zend_arg_info[_argc+1]; - - // the first record is initialized with information about the function, - // so we skip that here - int i=1; - - // loop through the arguments - for (auto it = arguments.begin(); it != arguments.end(); it++) - { - // increment counter with number of required parameters - if (it->required()) _required++; - - // fill the arg info - fill(&_argv[i], *it); - } - } - - /** - * Copy constructor - * @param that - */ - Callable(const Callable &that) : - _ptr(that._ptr), - _return(that._return), - _required(that._required), - _argc(that._argc), - _argv(nullptr) {} - - /** - * Move constructor - * @param that - */ - Callable(Callable &&that) : - _ptr(std::move(that._ptr)), - _return(that._return), - _required(that._required), - _argc(that._argc), - _argv(that._argv) - { - // invalidate other object - that._argv = nullptr; - } - - /** - * Destructor - */ - virtual ~Callable() - { - if (_argv) delete[] _argv; - } - - /** - * Method that gets called every time the function is executed - * @param params The parameters that were passed - * @return Variable Return value - */ - virtual Value invoke(Parameters ¶ms) = 0; - - /** - * Fill a function entry - * @param entry Entry to be filled - * @param ns Active namespace - * @param classname Optional class name - * @param flags Access flags - */ - void initialize(zend_function_entry *entry, const char *classname = nullptr, int flags = 0) const; - - /** - * Fill function info - * @param info Info object to be filled - * @param ns Active namespace - * @param classname Optional class name - */ - void initialize(zend_arg_info *info, const char *classname = nullptr) const; - - -protected: - /** - * Hidden pointer to the name and the function - * @var HiddenPointer - */ - HiddenPointer _ptr; - - /** - * Suggestion for the return type - * @var Type - */ - Type _return = Type::Null; - - /** - * Required number of arguments - * @var integer - */ - int _required = 0; - - /** - * Total number of arguments - * @var integer - */ - int _argc = 0; - - /** - * The arguments - * @var zend_arg_info[] - */ - zend_arg_info *_argv = nullptr; - - /** - * Private helper method to fill an argument object - * @param info object from the zend engine - * @param arg original object - */ - void fill(zend_arg_info *info, const Argument &arg) const - { - // fill members - info->name = arg.name().c_str(); - info->name_len = arg.name().size(); - -#if PHP_VERSION_ID >= 50400 - - // since php 5.4 there is a type-hint, but we only support arrays, objects and callables - switch (arg.type()) { - case Type::Array: info->type_hint = IS_ARRAY; break; - case Type::Callable: info->type_hint = IS_CALLABLE; break; - case Type::Object: info->type_hint = IS_OBJECT; break; - default: info->type_hint = IS_NULL; break; - } - -# if PHP_VERSION_ID >= 50600 - - // from PHP 5.6 and onwards, an is_variadic property can be set, this - // specifies whether this argument is the first argument that specifies - // the type for a variable length list of arguments. For now we only - // support methods and functions with a fixed number of arguments. - info->is_variadic = false; - -# endif - -#else - - // php 5.3 code - info->array_type_hint = type == Type::Array; - info->return_reference = false; - info->required_num_args = 0; // @todo is this correct? - -#endif - - // this parameter is a regular type - info->class_name = arg.type() == Type::Object ? arg.classname().c_str() : nullptr; - info->class_name_len = arg.type() == Type::Object ? arg.classname().size() : 0; - info->allow_null = arg.allowNull(); - info->pass_by_reference = arg.byReference(); - } -}; - -/** - * End of namespace - */ -} - diff --git a/src/classbase.cpp b/src/classbase.cpp deleted file mode 100644 index 18d81d5..0000000 --- a/src/classbase.cpp +++ /dev/null @@ -1,130 +0,0 @@ -/** - * ClassBase.cpp - * - * Implementation of the ClassBase class. - * - * @author Emiel Bruijntjes - * @copyright 2014 Copernica BV - */ -#include "includes.h" - -/** - * Set up namespace - */ -namespace Php { - -/** - * Protected constructor - * @param classname Class name - * @param flags Class flags - */ -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) _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); -} - -/** - * Protected constructor - * @param classname Class name - * @param type Class type - */ -ClassBase::ClassBase(const char *classname, ClassType type) -{ - // construct implementation - _impl = std::make_shared(classname, type); -} - -/** - * Function that can be called by a derived method when a certain function - * is not implemented - */ -void ClassBase::notImplemented() -{ - // throw an exception - throw NotImplemented(); -} - -/** - * 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 - */ -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 - * @param name Name of the method - * @param method The actual method - * @param flags Optional flags - * @param args Description of the supported arguments - */ -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 - */ -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 - */ -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 - */ -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); } - -/** - * Add an interface - * @param interface Interface object - */ -void ClassBase::implements(const ClassBase &interface) { _impl->implements(interface._impl); } - -/** - * Set the base class - * @param base Php::Class object that is the base - */ -void ClassBase::extends(const ClassBase &base) { _impl->extends(base._impl); } - -/** - * End namespace - */ -} - diff --git a/src/classimpl.cpp b/src/classimpl.cpp deleted file mode 100644 index d63956a..0000000 --- a/src/classimpl.cpp +++ /dev/null @@ -1,1429 +0,0 @@ -/** - * 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 -} - -/** - * @todo refactor so that methods become simpler - */ - -/** - * Retrieve our C++ implementation object - * @param entry - * @return ClassImpl - */ -static ClassImpl *self(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)); -} - -/** - * 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 - ParametersImpl 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 - process(exception TSRMLS_CC); - } -} - -/** - * 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 - ParametersImpl 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 - process(exception TSRMLS_CC); - } -} - -/** - * 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 = self(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 = self(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 = self(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 val1 - * @param val2 - * @param tsrm_ls - * @return int - */ -int ClassImpl::compare(zval *val1, zval *val2 TSRMLS_DC) -{ - // prevent exceptions - try - { - // retrieve the class entry linked to this object - auto *entry = zend_get_class_entry(val1 TSRMLS_CC); - - // other object must be of the same type - if (entry != zend_get_class_entry(val2 TSRMLS_CC)) throw NotImplemented(); - - // we need the C++ class meta-information object - ClassBase *meta = self(entry)->_base; - - // get the base objects - Base *object1 = ObjectImpl::find(val1 TSRMLS_CC)->object(); - Base *object2 = ObjectImpl::find(val2 TSRMLS_CC)->object(); - - // run the compare method - return meta->callCompare(object1, object2); - } - 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(val1, val2 TSRMLS_CC); - } - catch (Exception &exception) - { - // a Php::Exception was thrown by the extension __compare function, - // pass this on to user space - process(exception TSRMLS_CC); - - // what shall we return here... - return 1; - } -} - -/** - * Function to cast the object to a different type - * @param val - * @param retval - * @param type - * @param tsrm_ls - * @return int - */ -int ClassImpl::cast(zval *val, zval *retval, int type TSRMLS_DC) -{ - // get the base c++ object - Base *object = ObjectImpl::find(val TSRMLS_CC)->object(); - - // 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 = self(entry)->_base; - - // 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(object).detach(); break; - case Type::Float: result = meta->callToFloat(object).detach(); break; - case Type::Bool: result = meta->callToBool(object).detach(); break; - case Type::String: result = meta->callToString(object).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 original parameter overwritten? - if (val == retval) zval_dtor(val); - - // 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(val, retval, type TSRMLS_CC); - } - catch (Exception &exception) - { - // pass on the exception to php userspace - process(exception TSRMLS_CC); - - // 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 = self(entry); - ClassBase *meta = impl->_base; - - // retrieve the old object, which we are going to copy - ObjectImpl *old_object = ObjectImpl::find(val TSRMLS_CC); - - // create a new base c++ object - auto *cpp = meta->clone(old_object->object()); - - // 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 - ObjectImpl *new_object = new ObjectImpl(entry, cpp TSRMLS_CC); - - // store the object in the object cache - result.handle = new_object->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(ObjectImpl::find(object TSRMLS_CC)->object()); - - // 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 - process(exception TSRMLS_CC); - - // 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(ObjectImpl::find(object TSRMLS_CC)->object()); - - // 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) - process(exception TSRMLS_CC); - - // 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(ObjectImpl::find(object TSRMLS_CC)->object()); - - // 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 - process(exception TSRMLS_CC); - } - } - 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(ObjectImpl::find(object TSRMLS_CC)->object()); - - // 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) - process(exception TSRMLS_CC); - - // 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(ObjectImpl::find(object TSRMLS_CC)->object()); - - // 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) - process(exception TSRMLS_CC); - } - } - 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 = ObjectImpl::find(object TSRMLS_CC)->object(); - - // 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 = self(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 - process(exception TSRMLS_CC); - - // 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 = ObjectImpl::find(object TSRMLS_CC)->object(); - - // 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 = self(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 - process(exception TSRMLS_CC); - } -} - -/** - * 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 = ObjectImpl::find(object TSRMLS_CC)->object(); - - // 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 = self(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 - process(exception TSRMLS_CC); - - // 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 = self(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(ObjectImpl::find(object TSRMLS_CC)->object(), 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 - process(exception TSRMLS_CC); - } -} - -/** - * 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) -{ - // find object - ObjectImpl *obj = ObjectImpl::find(object); - - // get meta info - ClassImpl *impl = self(object->ce); - - // prevent exceptions - try - { - // call the destruct function - if (obj->object()) impl->_base->callDestruct(obj->object()); - } - 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 - process(exception TSRMLS_CC); - } -} - -/** - * 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 - ObjectImpl *obj = ObjectImpl::find(object); - - // no longer need it - obj->destruct(TSRMLS_C); -} - -/** - * 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 = self(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(); - - // create the object in the zend engine - ObjectImpl *object = new ObjectImpl(entry, cpp TSRMLS_CC); - - // store the object in the object cache - result.handle = object->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(ObjectImpl::find(object TSRMLS_CC)->object()); - - // user may throw an exception in the getIterator() function - try - { - // create an iterator - auto *iterator = new IteratorImpl(traversable->getIterator()); - - // return the implementation - return iterator->implementation(); - } - catch (Exception &exception) - { - // user threw an exception in its method - // implementation, send it to user space - process(exception TSRMLS_CC); - - // 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(ObjectImpl::find(object TSRMLS_CC)->object()); - - // 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(ObjectImpl::find(*object TSRMLS_CC)->object()); - - // 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; - } - - // do we have a base class? - if (_parent) - { - // check if the base class was already defined - if (_parent->_entry) entry.parent = _parent->_entry; - - // otherwise an error is reported - else std::cerr << "Derived class " << name() << " is initialized before base class " << _parent->name() << ": base class is ignored" << std::endl; - } - - // register the class - _entry = zend_register_internal_class(&entry TSRMLS_CC); - - // register the classes - for (auto &interface : _interfaces) - { - // register this interface - if (interface->_entry) zend_class_implements(_entry TSRMLS_CC, 1, interface->_entry); - - // otherwise report an error - else std::cerr << "Derived class " << name() << " is initialized before base class " << interface->name() << ": interface is ignored" << std::endl; - } - - // 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 deleted file mode 100644 index febdbca..0000000 --- a/src/classimpl.h +++ /dev/null @@ -1,445 +0,0 @@ -/** - * 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; - - /** - * Interfaces that are implemented - * @var std::list - */ - std::list> _interfaces; - - /** - * The parent/base class - * @var std::shared_ptr - */ - std::shared_ptr _parent; - - - /** - * 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 class name - * @return std::string - */ - const std::string &name() const - { - return _name; - } - - /** - * 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 - */ -#if PHP_VERSION_ID >= 50400 - static zval *readProperty(zval *object, zval *name, int type, const zend_literal *key TSRMLS_DC); -#else - static zval *readProperty(zval *object, zval *name, int type TSRMLS_DC); -#endif - - /** - * 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 - */ -#if PHP_VERSION_ID >= 50400 - static void writeProperty(zval *object, zval *name, zval *value, const zend_literal *key TSRMLS_DC); -#else - static void writeProperty(zval *object, zval *name, zval *value TSRMLS_DC); -#endif - - /** - * 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 - */ -#if PHP_VERSION_ID >= 50400 - static int hasProperty(zval *object, zval *name, int has_set_exists, const zend_literal *key TSRMLS_DC); -#else - static int hasProperty(zval *object, zval *name, int has_set_exists TSRMLS_DC); -#endif - - /** - * 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 - */ -#if PHP_VERSION_ID >= 50400 - static void unsetProperty(zval *object, zval *member, const zend_literal *key TSRMLS_DC); -#else - static void unsetProperty(zval *object, zval *member TSRMLS_DC); -#endif - - /** - * 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 - */ -#if PHP_VERSION_ID >= 50400 - static zend_function *getMethod(zval **object_ptr, char *method, int method_len, const zend_literal *key TSRMLS_DC); -#else - static zend_function *getMethod(zval **object_ptr, char *method, int method_len TSRMLS_DC); -#endif - - /** - * 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 & MethodModifiers, 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 & MethodModifiers, 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 & MethodModifiers, 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 & MethodModifiers, 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 & MethodModifiers, 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 & MethodModifiers, 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 & MethodModifiers, 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 & MethodModifiers, 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 & MethodModifiers) | 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 & MethodModifiers) | 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 & MethodModifiers) | 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 & MethodModifiers) | 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, (flags & (MethodModifiers | Static)) | Abstract , 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 & PropertyModifiers)); } - void property(const char *name, int16_t value, int flags = Php::Public) { _members.push_back(std::make_shared(name, value, flags & PropertyModifiers)); } - void property(const char *name, int32_t value, int flags = Php::Public) { _members.push_back(std::make_shared(name, value, flags & PropertyModifiers)); } - void property(const char *name, int64_t value, int flags = Php::Public) { _members.push_back(std::make_shared(name, value, flags & PropertyModifiers)); } - void property(const char *name, bool value, int flags = Php::Public) { _members.push_back(std::make_shared (name, value, flags & PropertyModifiers)); } - void property(const char *name, char value, int flags = Php::Public) { _members.push_back(std::make_shared (name, &value, 1, flags & PropertyModifiers)); } - void property(const char *name, const std::string &value, int flags = Php::Public) { _members.push_back(std::make_shared (name, value, flags & PropertyModifiers)); } - void property(const char *name, const char *value, int flags = Php::Public) { _members.push_back(std::make_shared (name, value, strlen(value), flags & PropertyModifiers)); } - void property(const char *name, double value, int flags = Php::Public) { _members.push_back(std::make_shared (name, value, flags & PropertyModifiers)); } - - /** - * 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); } - - /** - * Add an interface that is implemented - * @param interface The interface that is implemented - */ - void implements(const std::shared_ptr &interface) { _interfaces.push_back(interface); } - - /** - * Set the base class - * @param base The base class - */ - void extends(const std::shared_ptr &base) { _parent = base; } - -}; - -/** - * End namespace - */ -} - diff --git a/src/extension.cpp b/src/extension.cpp deleted file mode 100644 index 9685b32..0000000 --- a/src/extension.cpp +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Extension.cpp - * - * @author Emiel Bruijntjes - * @copyright 2013, 2014 Copernica BV - */ -#include "includes.h" - -/** - * Set up namespace - */ -namespace Php { - -/** - * Constructor that defines a number of functions right away - * @param name Extension name - * @param version Extension version string - */ -Extension::Extension(const char *name, const char *version) : - Namespace(""), _impl(new ExtensionImpl(this, name, version)) {} - -/** - * Destructor - */ -Extension::~Extension() -{ - // get rid of the implementation object - delete _impl; -} - -/** - * Register a function to be called when the PHP engine is ready - * @param callback - * @return Extension - */ -Extension &Extension::onStartup(const Callback &callback) -{ - // pass on to the implementation - _impl->onStartup(callback); - - // allow chaining - return *this; -} - -/** - * Register a function to be called when the PHP engine is going to stop - * @param callback - * @return Extension - */ -Extension &Extension::onShutdown(const Callback &callback) -{ - // pass on to the implementation - _impl->onShutdown(callback); - - // allow chaining - return *this; -} - -/** - * Register a callback that is called at the beginning of each pageview/request - * @param callback - */ -Extension &Extension::onRequest(const Callback &callback) -{ - // pass on to the implementation - _impl->onRequest(callback); - - // allow chaining - return *this; -} - -/** - * Register a callback that is called to cleanup things after a pageview/request - * @param callback - */ -Extension &Extension::onIdle(const Callback &callback) -{ - // pass on to the implementation - _impl->onIdle(callback); - - // allow chaining - return *this; -} - -/** - * Retrieve the module pointer - * - * This is the memory address that should be exported by the get_module() - * function. - * - * @return void* - */ -void *Extension::module() -{ - // pass on to the implementation - return _impl->module(); -} - -/** - * End of namespace - */ -} - diff --git a/src/extensionimpl.cpp b/src/extensionimpl.cpp deleted file mode 100644 index 9562362..0000000 --- a/src/extensionimpl.cpp +++ /dev/null @@ -1,300 +0,0 @@ -/** - * Extension.cpp - * - * @author Emiel Bruijntjes - * @copyright 2013 Copernica BV - */ -#include "includes.h" - -/** - * Set up namespace - */ -namespace Php { - -/** - * If this extension is compiled for a PHP version with multi - * threading support, we need an additional header file - */ -#ifdef ZTS -#include "TSRM.h" -#endif - -/** - * We're almost there, we now need to declare an instance of the - * structure defined above (if building for a single thread) or some - * sort of impossible to understand magic pointer-to-a-pointer (for - * multi-threading builds). We make this a static variable because - * this already is bad enough. - */ -ZEND_DECLARE_MODULE_GLOBALS(phpcpp) - -/** - * Function that must be defined to initialize the "globals" - * We do not have to initialize anything, but PHP needs to call this - * method (crazy) - * @param globals - */ -static void init_globals(zend_phpcpp_globals *globals) {} - -/** - * The *startup() and *shutdown() callback functions are passed a module_number - * variable. However, there does not seem to be a decent API call in Zend to - * get back the original module_entry linked to this number. So we have to - * look up entries in a hash table to find the right module entry. To make things - * even worse, the records in this hash table are copies of the original - * zend_module_entry structure, so we can also not hide the C++ extension - * object pointer in the entry that we created ourselves. - * - * We have an ugly solution, we keep track of a map of all C++ extension names - * and their associated extension object, and a map of all module number and - * the linked extension object. - * - * @var map - */ -static std::map name2extension; -static std::map number2extension; - -/** - * Handler function that is used in combination with zend_hash_apply() - * - * This function is called when we need to find an extension object based on - * an extension number. We loop through the list of all registered modules, and - * for each module we check if we know the extension based on the name - * - * @param zend_module_entry - */ -static int match_module(zend_module_entry *entry) -{ - // check if there is an extension with this name - auto iter = name2extension.find(entry->name); - if (iter == name2extension.end()) return ZEND_HASH_APPLY_KEEP; - - // we have the extension, store in combination with the number - number2extension[entry->module_number] = iter->second; - - // done - return ZEND_HASH_APPLY_KEEP; -} - -/** - * Find an extension based on the module number - * @param number - * @param tsrm_ls - * @return Extension* - */ -static ExtensionImpl *find(int number TSRMLS_DC) -{ - // do we already have an extension with this number? - auto iter = number2extension.find(number); - if (iter != number2extension.end()) return iter->second; - - // no, not yet, loop through all modules - zend_hash_apply(&module_registry, (apply_func_t)match_module TSRMLS_CC); - - // find again - iter = number2extension.find(number); - if (iter == number2extension.end()) return nullptr; - - // found! - return iter->second; -} - -/** - * Function that is called when the extension initializes - * @param type Module type - * @param number Module number - * @param tsrm_ls - * @return int 0 on success - */ -int ExtensionImpl::onStartup(int type, int module_number TSRMLS_DC) -{ - // initialize and allocate the "global" variables - ZEND_INIT_MODULE_GLOBALS(phpcpp, init_globals, NULL); - - // get the extension - auto *extension = find(module_number TSRMLS_CC); - - // initialize the extension - extension->initialize(TSRMLS_C); - - // is the callback registered? - if (extension->_onStartup) extension->_onStartup(); - - // done - return BOOL2SUCCESS(true); -} - -/** - * Function that is called when the extension is about to be stopped - * @param type Module type - * @param number Module number - * @param tsrm_ls - * @return int - */ -int ExtensionImpl::onShutdown(int type, int module_number TSRMLS_DC) -{ - // get the extension - auto *extension = find(module_number TSRMLS_CC); - - // is the callback registered? - if (extension->_onShutdown) extension->_onShutdown(); - - // done - return BOOL2SUCCESS(true); -} - -/** - * Function that is called when a request starts - * @param type Module type - * @param number Module number - * @param tsrm_ls - * @return int 0 on success - */ -int ExtensionImpl::onRequest(int type, int module_number TSRMLS_DC) -{ - // get the extension - auto *extension = find(module_number TSRMLS_CC); - - // is the callback registered? - if (extension->_onRequest) extension->_onRequest(); - - // done - return BOOL2SUCCESS(true); -} - -/** - * Function that is called when a request is ended - * @param type Module type - * @param number Module number - * @param tsrm_ls - * @return int 0 on success - */ -int ExtensionImpl::onIdle(int type, int module_number TSRMLS_DC) -{ - // get the extension - auto *extension = find(module_number TSRMLS_CC); - - // is the callback registered? - if (extension->_onIdle) extension->_onIdle(); - - // done - return BOOL2SUCCESS(true); -} - -/** - * Constructor - * @param data Pointer to the extension object created by the extension programmer - * @param name Name of the extension - * @param version Version number - */ -ExtensionImpl::ExtensionImpl(Extension *data, const char *name, const char *version) : _data(data) -{ - // keep extension pointer based on the name - name2extension[name] = this; - - // assign all members (apart from the globals) - _entry.size = sizeof(zend_module_entry); // size of the data - _entry.zend_api = ZEND_MODULE_API_NO; // api number - _entry.zend_debug = ZEND_DEBUG; // debug mode enabled? - _entry.zts = USING_ZTS; // is thread safety enabled? - _entry.ini_entry = NULL; // the php.ini record - _entry.deps = NULL; // dependencies on other modules - _entry.name = name; // extension name - _entry.functions = NULL; // functions supported by this module (none for now) - _entry.module_startup_func = &ExtensionImpl::onStartup; // startup function for the whole extension - _entry.module_shutdown_func = &ExtensionImpl::onShutdown; // shutdown function for the whole extension - _entry.request_startup_func = &ExtensionImpl::onRequest; // startup function per request - _entry.request_shutdown_func = &ExtensionImpl::onIdle; // shutdown function per request - _entry.info_func = NULL; // information for retrieving info - _entry.version = version; // version string - _entry.globals_size = 0; // size of the global variables - _entry.globals_ctor = NULL; // constructor for global variables - _entry.globals_dtor = NULL; // destructor for global variables - _entry.post_deactivate_func = NULL; // unknown function - _entry.module_started = 0; // module is not yet started - _entry.type = 0; // temporary or persistent module, will be filled by Zend engine - _entry.handle = NULL; // dlopen() handle, will be filled by Zend engine - _entry.module_number = 0; // module number will be filled in by Zend engine - _entry.build_id = (char *)ZEND_MODULE_BUILD_ID; // check if extension and zend engine are compatible - - // things that only need to be initialized -#ifdef ZTS - _entry.globals_id_ptr = NULL; -#else - _entry.globals_ptr = NULL; -#endif - -} - -/** - * Destructor - */ -ExtensionImpl::~ExtensionImpl() -{ - // deallocate functions - if (_entry.functions) delete[] _entry.functions; -} - -/** - * Retrieve the module entry - * @return zend_module_entry - */ -zend_module_entry *ExtensionImpl::module() -{ - // check if functions we're already defined - if (_entry.functions) return &_entry; - - // the number of functions - int count = _data->functions(); - - // skip if there are no functions - if (count == 0) return &_entry; - - // allocate memory for the functions - zend_function_entry *entries = new zend_function_entry[count + 1]; - - // index being processed - int i = 0; - - // apply a function to each function - _data->apply([&i, entries](const std::string &prefix, Function &function) { - - // initialize the function - function.initialize(prefix, &entries[i]); - - // move on to the next iteration - i++; - }); - - // last entry should be set to all zeros - zend_function_entry *last = &entries[count]; - - // all should be set to zero - memset(last, 0, sizeof(zend_function_entry)); - - // store functions in entry object - _entry.functions = entries; - - // return the entry - return &_entry; -} - -/** - * Initialize the extension after it was started - * @param tsrm_ls - */ -void ExtensionImpl::initialize(TSRMLS_D) -{ - // we need to register each class, find out all classes - _data->apply([TSRMLS_C](const std::string &prefix, ClassBase &c) { - - // forward to implementation class - c.implementation()->initialize(&c, prefix TSRMLS_CC); - }); -} - -/** - * End of namespace - */ -} - diff --git a/src/extensionimpl.h b/src/extensionimpl.h deleted file mode 100644 index cc37354..0000000 --- a/src/extensionimpl.h +++ /dev/null @@ -1,213 +0,0 @@ -/** - * ExtensionImpl.h - * - * Extension implementation for the Zend engine. - * - * @author Emiel Bruijntjes - * @copyright 2013, 2014 Copernica BV - */ - -/** - * Set up namespace - */ -namespace Php { - -/** - * Class definition - */ -class ExtensionImpl -{ -protected: - /** - * Pointer to the extension object that is filled by the extension programmer - * @var Extension - */ - Extension *_data; - - /** - * The information that is passed to the Zend engine - * - * Although it would be slightly faster to not make this a pointer, this - * would require that client code also includes the PHP header files, which - * we try to prevent with the PHP-CPP library, so we allocate it dynamically. - * - * @var zend_module_entry - */ - zend_module_entry _entry; - - /** - * Callback that is called after the engine is initialized and before the - * pageviews are going to be handled - * @var Callback - */ - Callback _onStartup; - - /** - * Callback that is called in front of each request - * @var Callback - */ - Callback _onRequest; - - /** - * Callback that is called right after each request - * @var Callback - */ - Callback _onIdle; - - /** - * Callback that is called right before the engine is closing down - * @var Callback - */ - Callback _onShutdown; - -public: - /** - * Constructor - * @param data Extension object created by the extension programmer - * @param name Name of the extension - * @param version Version number - */ - ExtensionImpl(Extension *data, const char *name, const char *version); - - /** - * No copy'ing and no moving - */ - ExtensionImpl(const ExtensionImpl &extension) = delete; - ExtensionImpl(ExtensionImpl &&extension) = delete; - - /** - * Destructor - */ - virtual ~ExtensionImpl(); - - /** - * Register a function to be called when the PHP engine is ready - * - * The callback will be called after all extensions are loaded, and all - * functions and classes are available, but before the first pageview/request - * is handled. You can register this callback if you want to be notified - * when the engine is ready, for example to initialize certain things. - * - * @param callback - */ - void onStartup(const Callback &callback) - { - // copy callback - _onStartup = callback; - } - - /** - * Register a function to be called when the PHP engine is going to stop - * - * The callback will be called right before the process is going to stop. - * You can register a function if you want to clean up certain things. - * - * @param callback - */ - void onShutdown(const Callback &callback) - { - // copy callback - _onShutdown = callback; - } - - /** - * Register a callback that is called at the beginning of each pageview/request - * - * You can register a callback if you want to initialize certain things - * at the beginning of each request. Remember that the extension can handle - * multiple requests after each other, and you may want to set back certain - * global variables to their initial variables in front of each request - * - * @param callback - */ - void onRequest(const Callback &callback) - { - // copy callback - _onRequest = callback; - } - - /** - * Register a callback that is called to cleanup things after a pageview/request - * - * The callback will be called after _each_ request, so that you can clean up - * certain things and make your extension ready to handle the next request. - * This method is called onIdle because the extension is idle in between - * requests. - * - * @param callback - */ - void onIdle(const Callback &callback) - { - // copy callback - _onIdle = callback; - } - - /** - * Retrieve the module entry - * - * This is the memory address that should be exported by the get_module() - * function. - * - * @return _zend_module_entry - */ - zend_module_entry *module(); - - /** - * Cast to a module entry - * @return _zend_module_entry* - */ - operator zend_module_entry * () - { - return module(); - } - -private: - /** - * Initialize the namespace after it was registered - * @param tsrm_ls - */ - void initialize(TSRMLS_D); - - /** - * Function that is called when the extension initializes - * @param type Module type - * @param number Module number - * @param tsrm_ls - * @return int 0 on success - */ - static int onStartup(int type, int module_number TSRMLS_DC); - - /** - * Function that is called when the extension is about to be stopped - * @param type Module type - * @param number Module number - * @param tsrm_ls - * @return int - */ - static int onShutdown(int type, int module_number TSRMLS_DC); - - /** - * Function that is called when a request starts - * @param type Module type - * @param number Module number - * @param tsrm_ls - * @return int 0 on success - */ - static int onRequest(int type, int module_number TSRMLS_DC); - - /** - * Function that is called when a request is ended - * @param type Module type - * @param number Module number - * @param tsrm_ls - * @return int 0 on success - */ - static int onIdle(int type, int module_number TSRMLS_DC); -}; - -/** - * End of namespace - */ -} - - diff --git a/src/floatmember.h b/src/floatmember.h deleted file mode 100644 index 9b5d4f2..0000000 --- a/src/floatmember.h +++ /dev/null @@ -1,67 +0,0 @@ -/** - * FloatMember.h - * - * Implementation for a property that is initially set to a boolean value - * - * @author Emiel Bruijntjes - * @copyright 2013, 2014 Copernica BV - */ - -/** - * Set up namespace - */ -namespace Php { - -/** - * Class definition - */ -class FloatMember : public Member -{ -private: - /** - * The value - * @var double - */ - double _value; - -public: - /** - * Constructor - * @param name - * @param value - * @param flags - */ - FloatMember(const char *name, double value, int flags) : Member(name, flags), _value(value) {} - - /** - * Destructor - */ - virtual ~FloatMember() {} - - /** - * Virtual method to declare class constant - * @param entry Class entry - * @param tsrm_ls - */ - virtual void constant(struct _zend_class_entry *entry TSRMLS_DC) override - { - zend_declare_class_constant_double(entry, _name.c_str(), _name.size(), _value TSRMLS_CC); - } - - /** - * Virtual method to declare the property - * @param entry Class entry' - * @param tsrm_ls - */ - virtual void declare(struct _zend_class_entry *entry TSRMLS_DC) override - { - // converstion to char* necessary for php 5.3 - zend_declare_property_double(entry, (char *)_name.c_str(), _name.size(), _value, _flags TSRMLS_CC); - } -}; - -/** - * End of namespace - */ -} - diff --git a/src/function.h b/src/function.h deleted file mode 100644 index 4c34ac7..0000000 --- a/src/function.h +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Function.h - * - * The Function class is an extension of the Callable class that - * forwards the function call directly to a native function in C/C++ - * - * @author Emiel Bruijntjes - * @copyright 2013 Copernica BV - */ - -/** - * Set up namespace - */ -namespace Php { - -/** - * Class definition - */ -class Function : public Callable -{ -public: - /** - * Constructor - * @param name Function name - * @param function The native C function - */ - Function(const char *name, const native_callback_0 &function, const Arguments &arguments = {}) : Callable(name, arguments), _type(0) { _function.f0 = function; } - Function(const char *name, const native_callback_1 &function, const Arguments &arguments = {}) : Callable(name, arguments), _type(1) { _function.f1 = function; } - Function(const char *name, const native_callback_2 &function, const Arguments &arguments = {}) : Callable(name, arguments), _type(2) { _function.f2 = function; } - Function(const char *name, const native_callback_3 &function, const Arguments &arguments = {}) : Callable(name, arguments), _type(3) { _function.f3 = function; } - - /** - * Copy constructor - * @param that - */ - Function(const Function &that) : Callable(that), _function(that._function), _type(that._type) {} - - /** - * Move constructor - * @param that - */ - Function(Function &&that) : Callable(std::move(that)), _function(that._function), _type(that._type) {} - - /** - * Destructor - */ - virtual ~Function() {} - - /** - * Method that gets called every time the function is executed - * @param params The parameters that were passed - * @return Variable Return value - */ - virtual Value invoke(Parameters ¶ms) override - { - switch (_type) { - case 0: _function.f0(); return Value(); - case 1: _function.f1(params); return Value(); - case 2: return _function.f2(); - case 3: return _function.f3(params); - default: return Value(); - } - } - - /** - * Fill a function entry - * @param prefix Active namespace prefix - * @param entry Entry to be filled - */ - void initialize(const std::string &prefix, zend_function_entry *entry) - { - // if there is a namespace prefix, we should adjust the name - if (prefix.size()) _ptr = HiddenPointer(this, prefix+"\\"+(const char *)_ptr); - - // call base initialize - Callable::initialize(entry); - } - -private: - /** - * Union of supported callbacks - * One of the callbacks will be set - */ - union { - native_callback_0 f0; - native_callback_1 f1; - native_callback_2 f2; - native_callback_3 f3; - } _function; - - /** - * The callback that is set - * @var integer - */ - int _type; -}; - -/** - * End of namespace - */ -} - diff --git a/src/global.cpp b/src/global.cpp deleted file mode 100644 index 7eea8e7..0000000 --- a/src/global.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Global.cpp - * - * Implementation for the global variable - * - * @author Emiel Bruijntjes - * @copyright 2013 Copernica BV - */ -#include "includes.h" - -/** - * Namespace - */ -namespace Php { - -/** - * Function that is called when the value is updated - * @return Value - */ -Global &Global::update() -{ - // skip if the variable already exists - if (_exists) return *this; - - // we need the TSRMLS variable - TSRMLS_FETCH(); - - // add the variable to the globals - zend_hash_add(EG(active_symbol_table), _name.c_str(), _name.size()+1, &_val, sizeof(zval*), NULL); - - // add one extra reference because the variable now is a global var too - Z_ADDREF_P(_val); - - // remember that the variable now exists - _exists = true; - - // done - return *this; -} - -/** - * End of namespace - */ -} - diff --git a/src/globals.cpp b/src/globals.cpp deleted file mode 100644 index 8132ef3..0000000 --- a/src/globals.cpp +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Globals.cpp - * - * Implementation of the globals class - * - * @author Emiel Bruijntjes - * @copyright 2013 Copernica BV - */ -#include "includes.h" - -/** - * Namespace - */ -namespace Php { - -/** - * Get access to the globals single instance - * @return Globals - */ -Globals &Globals::instance() -{ - static Globals globals; - return globals; -} - -/** - * The one and only instance - * @var Globals - */ -Globals &GLOBALS = Globals::instance(); - -/** - * Get access to a global variable - * @param name - * @return Global - */ -Global Globals::operator[](const char *name) -{ - // pointer to a zval - zval **varvalue; - - // we need the TSRMLS variable - TSRMLS_FETCH(); - - // check if the variable already exists - if (zend_hash_find(&EG(symbol_table), name, strlen(name)+1, (void**)&varvalue) == FAILURE) - { - // the variable does not already exist, return a global object - // that will automatically set the value when it is updated - return Global(name); - } - else - { - // we are in the happy situation that the variable exists, we turn - // this value into a reference value, and return that - return Global(name, *varvalue); - } -} - -/** - * Get access to a global variable - * @param name - * @return Global - */ -Global Globals::operator[](const std::string &name) -{ - // pointer to a zval - zval **varvalue; - - // we need the TSRMLS variable - TSRMLS_FETCH(); - - // check if the variable already exists - if (zend_hash_find(&EG(symbol_table), name.c_str(), name.size()+1, (void**)&varvalue) == FAILURE) - { - // the variable does not already exist, return a global object - // that will automatically set the value when it is updated - return Global(name); - } - else - { - // we are in the happy situation that the variable exists, we turn - // this value into a reference value, and return that - return Global(name, *varvalue); - } -} - -/** - * End of namespace - */ -} - diff --git a/src/hashiterator.h b/src/hashiterator.h deleted file mode 100644 index b1f409f..0000000 --- a/src/hashiterator.h +++ /dev/null @@ -1,250 +0,0 @@ -/** - * HashIterator.h - * - * This is an internal helper class that is used when iterating over a - * Php::Value object that holds a hash table (an array or an object that - * does not implement the Traversable interface - stl style. - * - * Thus, when you do c++ things like "for (auto &iter : value)", internally - * a ValueIterator object is being used. - * - * @author Emiel Bruijntjes - * @copyright 2014 Copernica BV - */ - -/** - * Set up namespace - */ -namespace Php { - -/** - * Class definition - */ -class HashIterator : public ValueIteratorImpl -{ -public: - /** - * Constructor - * @param hashtable The hashtable to iterate over - * @param first Should it start on the first position? - * @param tsrm_ls - */ - HashIterator(HashTable *hashtable, bool first) : _table(hashtable) - { - // reset the hash pointer to the internal position - if (hashtable && first) - { - // move to first position - zend_hash_internal_pointer_reset_ex(_table, &_position); - - // read current data - if (read()) return; - - // data was private, move on - increment(); - } - else - { - // start with invalid data - invalidate(); - } - } - - /** - * Copy constructor - * @param that - * @param tsrm_ls - */ - HashIterator(const HashIterator &that TSRMLS_DC) : - _table(that._table), _position(that._position) - { - // read current position - read(); - } - - /** - * Destructor - */ - virtual ~HashIterator() {} - - /** - * Clone the object - * @param tsrm_ls - * @return ValueIteratorImpl - */ - virtual ValueIteratorImpl *clone() - { - // create a new instance - return new HashIterator(*this); - } - - /** - * Increment position (pre-increment) - * @return bool - */ - virtual bool increment() override - { - // leap out if already on an invalid pos (behind the last pos) - if (!_position) return false; - - // move the iterator forward - if (zend_hash_move_forward_ex(_table, &_position) == SUCCESS) - { - // read current key and value - if (read()) return true; - - // data was private or invalid, move further - return increment(); - } - else - { - // invalidate current position - return invalidate(); - } - } - - /** - * Decrement position (pre-decrement) - * @return bool - */ - virtual bool decrement() override - { - // leap out if we're not even iterating over a hash table - if (!_table) return false; - - // if position is invalid, it is one position behind the last position - if (!_position) - { - // move to last position - zend_hash_internal_pointer_end_ex(_table, &_position); - } - else if (zend_hash_move_backwards_ex(_table, &_position) == FAILURE) - { - // invalidate current position - return invalidate(); - } - - // read current key and value - if (read()) return true; - - // data was private, move on - return decrement(); - } - - /** - * Compare with other iterator - * @param that - * @return bool - */ - virtual bool equals(const ValueIteratorImpl *that) const override - { - // this always is a hash iterator - HashIterator *other = (HashIterator *)that; - - // compare the positions - return _position == other->_position; - } - - /** - * Derefecence, this returns a std::pair with the current key and value - * @return std::pair - */ - virtual const std::pair ¤t() const override - { - return _current; - } - -private: - /** - * The hash table over which is being iterated - * @var HashTable - */ - HashTable *_table = nullptr; - - /** - * The position in the hash table - * @var HashPosition - */ - Bucket *_position = nullptr; - - /** - * The current key and value - * @var std::pair - */ - std::pair _current; - - /** - * Read current key and value - * @return bool - */ - bool read() - { - // zval to read the current key in - Value key; - -#if PHP_VERSION_ID >= 50500 - - // read in the current key - zend_hash_get_current_key_zval_ex(_table, key._val, &_position); - - // if the key is set to NULL, it means that the object is not at a valid position - if (key.isNull()) return invalidate(); - -#else - - // php 5.3 and php 5.4 need a different implementation because the function - // zend_hash_get_current_key_zval_ex is missing in php 5.3, declare variables - // we need for storing the key in - char *string_key; - unsigned int str_len; - unsigned long num_key; - - // get the current key - int type = zend_hash_get_current_key_ex(_table, &string_key, &str_len, &num_key, 0, &_position); - - // if key is not found, the iterator is at an invalid position - if (type == HASH_KEY_NON_EXISTANT) return invalidate(); - - // numeric keys are the easiest ones - if (type == HASH_KEY_IS_LONG) key = (int64_t)num_key; - else key = string_key; - -#endif - - // iterator is at a valid position, go fetch the data - // this is the variable we need for fetching the data - zval **value; - - // retrieve data - zend_hash_get_current_data_ex(_table, (void **) &value, &_position); - - // we can now update the current data - _current = std::make_pair(std::move(key), *value); - - // if the key is private (it starts with a null character) we should return - // false to report that the object is not in a completely valid state - return !_current.first.isString() || _current.first.rawValue()[0]; - } - - /** - * Invalidate the iterator - * @return bool - */ - bool invalidate() - { - // forget current position - _position = nullptr; - - // make the data a pair of null ptrs - _current = std::make_pair(nullptr,nullptr); - - // done - return false; - } -}; - -/** - * End namespace - */ -} - diff --git a/src/hashmember.cpp b/src/hashmember.cpp deleted file mode 100644 index f6f8483..0000000 --- a/src/hashmember.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/** - * HashMember.cpp - * - * @author Emiel Bruijntjes - * @copyright 2013 Copernica BV - */ -#include "includes.h" - -/** - * Set up namespace - */ -namespace Php { - -/** - * Custom output stream operator - * @param stream - * @param value - * @return ostream - */ -std::ostream &operator<<(std::ostream &stream, const HashMember &value) -{ - return stream << value.value(); -} - -/** - * Custom output stream operator - * @param stream - * @param value - * @return ostream - */ -std::ostream &operator<<(std::ostream &stream, const HashMember &value) -{ - return stream << value.value(); -} - -/** - * End of namespace - */ -} - diff --git a/src/includes.h b/src/includes.h deleted file mode 100644 index 0019244..0000000 --- a/src/includes.h +++ /dev/null @@ -1,121 +0,0 @@ -/** - * Includes.h - * - * Startup include file to compile the phpcpp library - * - * @author Emiel Bruijntjes - * @copyright 2013 Copernica BV - */ - -/** - * Include standard C and C++ libraries - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// for debug -#include - -#define ZTS -#define THREAD_T pthread_t -#define MUTEX_T pthread_mutex_t * - -/** - * PHP includes - */ -#pragma GCC system_header -#include -#include -#include - -/** - * Macro to convert results to success status - */ -#define BOOL2SUCCESS(b) ((b) ? SUCCESS : FAILURE) - -/** - * Include other files from this library - */ -#include "../include/exception.h" -#include "../include/streams.h" -#include "../include/type.h" -#include "../include/hashparent.h" -#include "../include/value.h" -#include "../include/valueiterator.h" -#include "../include/array.h" -#include "../include/object.h" -#include "../include/hiddenpointer.h" -#include "../include/globals.h" -#include "../include/argument.h" -#include "../include/byval.h" -#include "../include/byref.h" -#include "../include/global.h" -#include "../include/super.h" -#include "../include/hashmember.h" -#include "../include/parameters.h" -#include "../include/modifiers.h" -#include "../include/base.h" -#include "../include/countable.h" -#include "../include/arrayaccess.h" -#include "../include/serializable.h" -#include "../include/iterator.h" -#include "../include/traversable.h" -#include "../include/classtype.h" -#include "../include/classbase.h" -#include "../include/interface.h" -#include "../include/class.h" -#include "../include/namespace.h" -#include "../include/extension.h" -#include "../include/call.h" - -/** - * Interface files for internal use only - */ -#include "init.h" -#include "callable.h" -#include "function.h" -#include "method.h" -#include "member.h" -#include "nullmember.h" -#include "numericmember.h" -#include "boolmember.h" -#include "stringmember.h" -#include "floatmember.h" -#include "arithmetic.h" -#include "origexception.h" -#include "notimplemented.h" -#include "property.h" -#include "valueiteratorimpl.h" -#include "hashiterator.h" -#include "invaliditerator.h" -#include "traverseiterator.h" -#include "iteratorimpl.h" -#include "streambuf.h" -#include "classimpl.h" -#include "objectimpl.h" -#include "parametersimpl.h" -#include "extensionimpl.h" - -#ifndef ZVAL_COPY_VALUE -#define ZVAL_COPY_VALUE(z, v) \ - do { \ - (z)->value = (v)->value; \ - Z_TYPE_P(z) = Z_TYPE_P(v); \ - } while (0) -#endif - -#ifndef INIT_PZVAL_COPY -#define INIT_PZVAL_COPY(z, v) \ - do { \ - ZVAL_COPY_VALUE(z, v); \ - Z_SET_REFCOUNT_P(z, 1); \ - Z_UNSET_ISREF_P(z); \ - } while (0) -#endif diff --git a/src/init.h b/src/init.h deleted file mode 100644 index 8f914f5..0000000 --- a/src/init.h +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Init.h - * - * Variables and structured required by the Zend engine to work - * with global variables - * - * @author Emiel Bruijntjes - * @copyright 2013 Copernica BV - */ - -/** - * Namespace - */ -namespace Php { - -/** - * The way how PHP C API deals with "global" variables is peculiar. - * - * The following macros are supposed to turn into a structure that is going - * to be instantiated for each parallel running request, and for which the - * PHP engine allocates a certain amount of memory, and a magic pointer that - * is passed and should be forwarded to every thinkable PHP function. - * - * We don't use this architecture. We have our own environment object - * that makes much more sense, and that we use. However, the Zend engine - * expects this structure and this structure to exist. - */ -ZEND_BEGIN_MODULE_GLOBALS(phpcpp) -ZEND_END_MODULE_GLOBALS(phpcpp) - -/** - * And now we're going to define a macro. This also is a uncommon architecture - * from PHP to get access to a variable from the structure above. - */ -#ifdef ZTS -#define PHPCPP_G(v) TSRMG(phpcpp_globals_id, phpcpp_globals *, v) -#else -#define PHPCPP_G(v) (phpcpp_globals.v) -#endif - -/** - * We're almost there, we now need to declare an instance of the - * structure defined above (if building for a single thread) or some - * sort of impossible to understand magic pointer-to-a-pointer (for - * multi-threading builds). We make this a static variable because - * this already is bad enough. - */ -extern ZEND_DECLARE_MODULE_GLOBALS(phpcpp) - -/** - * End of namespace - */ -} - diff --git a/src/invaliditerator.h b/src/invaliditerator.h deleted file mode 100644 index 388eca8..0000000 --- a/src/invaliditerator.h +++ /dev/null @@ -1,82 +0,0 @@ -/** - * InvalidIterator.h - * - * Iterator class that is used for value objects that are not even - * iteratable. - * - * @author Emiel Bruijntjes - * @copyright 2014 Copernica BV - */ - -/** - * Set up namespace - */ -namespace Php { - -/** - * Class definition - */ -class InvalidIterator : public ValueIteratorImpl -{ -public: - /** - * Clone the object - * @param tsrm_ls - * @return ValueIteratorImpl - */ - virtual ValueIteratorImpl *clone() - { - // create a new instance - return new InvalidIterator(*this); - } - - /** - * Increment position (pre-increment) - * @param tsrm_ls - * @return bool - */ - virtual bool increment() override - { - return false; - } - - /** - * Decrement position (pre-decrement) - * @return bool - */ - virtual bool decrement() override - { - return false; - } - - /** - * Compare with other iterator - * @param that - * @return bool - */ - virtual bool equals(const ValueIteratorImpl *that) const override - { - // the other iterator is also an invalid-iterator, and all invalid - // iterators are equal - return true; - } - - /** - * Derefecence, this returns a std::pair with the current key and value - * @return std::pair - */ - virtual const std::pair ¤t() const override - { - // this method is never called, when it is, we create a static instance - static std::pair result; - - // return it - return result; - } -}; - -/** - * End namespace - */ -} - diff --git a/src/iteratorimpl.cpp b/src/iteratorimpl.cpp deleted file mode 100644 index 2750ddb..0000000 --- a/src/iteratorimpl.cpp +++ /dev/null @@ -1,183 +0,0 @@ -/** - * IteratorImpl.cpp - * - * Implementation file of the IteratorImpl class - * - * @author Emiel Bruijntjes - * @copyright 2014 Copernica BV - */ -#include "includes.h" - -/** - * Set up namespace - */ -namespace Php { - -/** - * Helper method to get access to ourselves - * @param iter - * @return IteratorImpl - */ -static IteratorImpl *self(zend_object_iterator *iter) -{ - return (IteratorImpl *)iter->data; -} - -/** - * Iterator destructor method - * @param iter - * @param tsrm_ls - */ -void IteratorImpl::destructor(zend_object_iterator *iter TSRMLS_DC) -{ - // delete the object - delete self(iter); -} - -/** - * Iterator valid function - * Returns FAILURE or SUCCESS - * @param iter - * @param tsrm_ls - * @return int - */ -int IteratorImpl::valid(zend_object_iterator *iter TSRMLS_DC) -{ - // check if valid - return self(iter)->valid() ? SUCCESS : FAILURE; -} - -/** - * Fetch the current item - * @param iter - * @param data - * @param tsrm_ls - */ -void IteratorImpl::current(zend_object_iterator *iter, zval ***data TSRMLS_DC) -{ - // get the actual iterator - IteratorImpl *iterator = self(iter); - - // retrieve the value (and store it in a member so that it is not - // destructed when the function returns) - iterator->_current = iterator->current(); - - // copy the value - *data = &iterator->_current._val; -} - -/** - * Fetch the key for the current element (optional, may be NULL). The key - * should be written into the provided zval* using the ZVAL_* macros. If - * this handler is not provided auto-incrementing integer keys will be - * used. - * @param iter - * @param key - * @param tsrm_ls - */ -void IteratorImpl::key(zend_object_iterator *iter, zval *key TSRMLS_DC) -{ - // retrieve the key - Value retval(self(iter)->key()); - - // detach the underlying zval - zval *val = retval.detach(); - - // copy it to the key - ZVAL_ZVAL(key, val, 1, 1); -} - -/** - * Function to retrieve the current key, php 5.3 style - * @param iter - * @param str_key - * @param str_key_len - * @param int_key - * @param tsrm_ls - * @return HASH_KEY_IS_STRING or HASH_KEY_IS_LONG - */ -int IteratorImpl::key(zend_object_iterator *iter, char **str_key, uint *str_key_len, ulong *int_key TSRMLS_DC) -{ - // retrieve the key - Value retval(self(iter)->key()); - - // is this a numeric string? - if (retval.isString()) - { - // copy the key and the from the value - *str_key = estrndup(retval.rawValue(), retval.size()); - *str_key_len = retval.size() + 1; - - // done - return HASH_KEY_IS_STRING; - } - else - { - // convert to a numeric - *int_key = retval.numericValue(); - - // done - return HASH_KEY_IS_LONG; - } -} - -/** - * Step forwards to the next element - * @param iter - * @param tsrm_ls - */ -void IteratorImpl::next(zend_object_iterator *iter TSRMLS_DC) -{ - // call the next method - self(iter)->next(); -} - -/** - * Rewind the iterator back to the start - * @param iter - * @param tsrm_ls - */ -void IteratorImpl::rewind(zend_object_iterator *iter TSRMLS_DC) -{ - // call the rewind method - self(iter)->rewind(); -} - -/** - * Get access to all iterator functions - * @return zend_object_iterator_funcs - */ -zend_object_iterator_funcs *IteratorImpl::functions() -{ - // static variable with all functions - static zend_object_iterator_funcs funcs; - - // static variable that knows if the funcs are already initialized - static bool initialized = false; - - // no need to set anything if already initialized - if (initialized) return &funcs; - - // set the members - funcs.dtor = &IteratorImpl::destructor; - funcs.valid = &IteratorImpl::valid; - funcs.get_current_data = &IteratorImpl::current; - funcs.get_current_key = &IteratorImpl::key; - funcs.move_forward = &IteratorImpl::next; - funcs.rewind = &IteratorImpl::rewind; - - // invalidate is not yet supported - funcs.invalidate_current = nullptr; - - // remember that functions are initialized - initialized = true; - - // done - return &funcs; -} - -/** - * End namespace - */ -} - diff --git a/src/iteratorimpl.h b/src/iteratorimpl.h deleted file mode 100644 index 0a815e2..0000000 --- a/src/iteratorimpl.h +++ /dev/null @@ -1,190 +0,0 @@ -/** - * Iterator.h - * - * Base class for iterators. Extension writers that want to create traversable - * classes, should override the Php::Traversable base class. This base class - * forces you to implement a getIterator() method that returns an instance of - * a Php::Iterator class. - * - * In this file you find the signature of the Php::Iterator class. It mostly has - * pure virtual methods, which means that you should create a derived class - * that implements all these methods. - * - * @author Emiel Bruijntjes - * @copyright 2014 Copernica BV - */ - -/** - * Set up namespace - */ -namespace Php { - -/** - * Class definition - */ -class IteratorImpl -{ -private: - /** - * Unique pointer to the iterator that is returned by the extension - * @var std::unique_ptr - */ - std::unique_ptr _iterator; - - /** - * The current() method that is called by the Zend engine wants a - * pointer-to-pointer-to-a-zval. Because of this, we have to keep the - * current value in memory after the current() method returns because - * the pointer would otherwise fall out of scope. This is (once again) - * odd behavior of the Zend engine, but we'll have to live with that - * @var Value - */ - Value _current; - - /** - * The object iterator as is needed by the Zend engine - * @var zend_object_iterator - */ - zend_object_iterator _impl; - - /** - * Get access to all iterator functions - * @return zend_object_iterator_funcs - */ - static zend_object_iterator_funcs *functions(); - - /** - * Is the iterator on a valid position - * @return bool - */ - bool valid() - { - return _iterator->valid(); - } - - /** - * The value at the current position - * @return Value - */ - Value current() - { - return _iterator->current(); - } - - /** - * The key at the current position - * @return Value - */ - Value key() - { - return _iterator->key(); - } - - /** - * Move to the next position - */ - void next() - { - return _iterator->next(); - } - - /** - * Rewind the iterator to the front position - */ - void rewind() - { - return _iterator->rewind(); - } - - /** - * Iterator destructor method - * @param iter - * @param tsrm_ls - */ - static void destructor(zend_object_iterator *iter TSRMLS_DC); - - /** - * Iterator valid function - * Returns FAILURE or SUCCESS - * @param iter - * @param tsrm_ls - * @return int - */ - static int valid(zend_object_iterator *iter TSRMLS_DC); - - /** - * Fetch the current item - * @param iter - * @param data - * @param tsrm_ls - */ - static void current(zend_object_iterator *iter, zval ***data TSRMLS_DC); - - /** - * Fetch the key for the current element (optional, may be NULL). The key - * should be written into the provided zval* using the ZVAL_* macros. If - * this handler is not provided auto-incrementing integer keys will be - * used. - * @param iter - * @param data - * @param tsrm_ls - */ - static void key(zend_object_iterator *iter, zval *data TSRMLS_DC); - - /** - * Function to retrieve the current key, php 5.3 style - * @param iter - * @param str_key - * @param str_key_len - * @param int_key - * @param tsrm_ls - * @return HASH_KEY_IS_STRING or HASH_KEY_IS_LONG - */ - static int key(zend_object_iterator *iter, char **str_key, unsigned int *str_key_len, unsigned long *int_key TSRMLS_DC); - - /** - * Step forwards to the next element - * @param iter - * @param tsrm_ls - */ - static void next(zend_object_iterator *iter TSRMLS_DC); - - /** - * Rewind the iterator back to the start - * @param iter - * @param tsrm_ls - */ - static void rewind(zend_object_iterator *iter TSRMLS_DC); - -public: - /** - * Constructor - * @param iterator The iterator that is implemented by the extension - */ - IteratorImpl(Iterator *iterator) : _iterator(iterator) - { - // initialize impl object - _impl.data = this; - _impl.index = 0; - _impl.funcs = functions(); - } - - /** - * Destructor - */ - virtual ~IteratorImpl() {} - - /** - * Internal method that returns the implementation object - * @return zend_object_iterator - */ - zend_object_iterator *implementation() - { - return &_impl; - } -}; - -/** - * End namespace - */ -} diff --git a/src/member.h b/src/member.h deleted file mode 100644 index 7aa01d8..0000000 --- a/src/member.h +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Member.h - * - * Base class for properties of a class - * - * @author Emiel Bruijntjes - * @copyright 2013, 2014 Copernica BV - */ - -/** - * Namespace - */ -namespace Php { - -/** - * Class definition - */ -class Member -{ -public: - /** - * Constructor - * @param name Name of the member - * @param flags Flag access to a class member (public, protected etc) - */ - Member(const char *name, int flags) : _name(name), _flags(flags) {} - - /** - * Destructor - */ - virtual ~Member() {} - - /** - * Initialize the member - * @param zend_class_entry - * @param tsrm_ls - */ - void initialize(struct _zend_class_entry *entry TSRMLS_DC) - { - if (_flags == Const) constant(entry TSRMLS_CC); - else declare(entry TSRMLS_CC); - } - -protected: - /** - * Internal method to declare the property as constant - * @param zend_class_entry - * @param tsrm_ls - */ - virtual void constant(struct _zend_class_entry *entry TSRMLS_DC) = 0; - - /** - * Internal method to declare the property - * @param zend_class_entry - * @param tsrm_ls - */ - virtual void declare(struct _zend_class_entry *entry TSRMLS_DC) = 0; - -protected: - /** - * The member name - * @var std::string - */ - std::string _name; - - /** - * The member flags - * @var int - */ - int _flags; -}; - -/** - * End of namespace - */ -} - diff --git a/src/members.cpp b/src/members.cpp deleted file mode 100644 index 1965807..0000000 --- a/src/members.cpp +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Members.cpp - * - * Implementation of the members class - * - * @author Emiel Bruijntjes - * @copyright 2013 Copernica BV - */ -#include "includes.h" - -/** - * Set up namespace - */ -namespace Php { - -///** -// * Destructor -// */ -//Members::~Members() -//{ -// // check if there are methods -// if (_methods) delete[] _methods; -//} -// -///** -// * Number of methods -// * @return integer -// */ -//int Members::methods() -//{ -// // result variable -// int result = 0; -// -// // loop through the functions -// for (auto it = begin(); it != end(); it++) -// { -// // check if this is a method -// if (it->isMethod()) result++; -// } -// -// // done -// return result; -//} -// -///** -// * Get access to the methods -// * @return Methods -// */ -//struct _zend_function_entry *Members::methods(const char *classname) -//{ -// // already set? -// if (_methods) return _methods; -// -// // the number of methods -// int count = methods(); -// -// // allocate memory for the functions -// _methods = new zend_function_entry[count + 1]; -// -// // keep iterator counter -// int i = 0; -// -// // loop through the functions -// for (auto it = begin(); it != end(); it++) -// { -// // skip if this is not a method -// if (!it->isMethod()) continue; -// -// // retrieve entry -// zend_function_entry *entry = &_methods[i++]; -// -// // let the function fill the entry -// it->initialize(classname, entry); -// } -// -// // last entry should be set to all zeros -// zend_function_entry *last = &_methods[i]; -// -// // all should be set to zero -// memset(last, 0, sizeof(zend_function_entry)); -// -// // done -// return _methods; -//} -// -/** - * End of namespace - */ -} - diff --git a/src/method.h b/src/method.h deleted file mode 100644 index dd18a9a..0000000 --- a/src/method.h +++ /dev/null @@ -1,139 +0,0 @@ -/** - * Method.h - * - * Internal class that represents a native class method, that can be called - * from PHP scripts. - * - * @author Emiel Bruijntjes - * @copyright 2014 Copernica BV - */ - -/** - * Set up namespace - */ -namespace Php { - -/** - * Class definition - */ -class Method : public Callable -{ -public: - /** - * Constructor - * - * @param name Method name - * @param callback Native callback - * @param flags Access flags - * @param args Argument description - */ - Method(const char *name, const method_callback_0 &callback, int flags, const Arguments &args) : Callable(name, args), _type(0), _flags(flags) { _callback.m0 = callback; } - Method(const char *name, const method_callback_1 &callback, int flags, const Arguments &args) : Callable(name, args), _type(1), _flags(flags) { _callback.m1 = callback; } - Method(const char *name, const method_callback_2 &callback, int flags, const Arguments &args) : Callable(name, args), _type(2), _flags(flags) { _callback.m2 = callback; } - Method(const char *name, const method_callback_3 &callback, int flags, const Arguments &args) : Callable(name, args), _type(3), _flags(flags) { _callback.m3 = callback; } - Method(const char *name, const method_callback_4 &callback, int flags, const Arguments &args) : Callable(name, args), _type(4), _flags(flags) { _callback.m4 = callback; } - Method(const char *name, const method_callback_5 &callback, int flags, const Arguments &args) : Callable(name, args), _type(5), _flags(flags) { _callback.m5 = callback; } - Method(const char *name, const method_callback_6 &callback, int flags, const Arguments &args) : Callable(name, args), _type(6), _flags(flags) { _callback.m6 = callback; } - Method(const char *name, const method_callback_7 &callback, int flags, const Arguments &args) : Callable(name, args), _type(7), _flags(flags) { _callback.m7 = callback; } - Method(const char *name, const native_callback_0 &callback, int flags, const Arguments &args) : Callable(name, args), _type(8), _flags(flags) { _callback.m8 = callback; } - Method(const char *name, const native_callback_1 &callback, int flags, const Arguments &args) : Callable(name, args), _type(9), _flags(flags) { _callback.m9 = callback; } - Method(const char *name, const native_callback_2 &callback, int flags, const Arguments &args) : Callable(name, args), _type(10), _flags(flags) { _callback.m10 = callback; } - Method(const char *name, const native_callback_3 &callback, int flags, const Arguments &args) : Callable(name, args), _type(11), _flags(flags) { _callback.m11 = callback; } - Method(const char *name, int flags, const Arguments &args) : Callable(name, args), _type(9999), _flags(flags) { _callback.m0 = nullptr; } - - /** - * Copy and move constructors - * @param that - */ - Method(const Method &that) : Callable(that), _type(that._type), _flags(that._flags), _callback(that._callback) {} - Method(Method &&that) : Callable(std::move(that)), _type(that._type), _flags(that._flags), _callback(that._callback) {} - - /** - * Destructor - * @param type - * @param callback - */ - virtual ~Method() {} - - /** - * Internal method to fill a function entry - * @param zend_function_entry - * @param classname - */ - void initialize(struct _zend_function_entry *entry, const std::string &classname) - { - // fix the flags, if neither public, private and protected is set, we use public, - // (this solves php warnings if only "final" or only "abstract" is set - if ((_flags & (Public|Private|Protected)) == 0) _flags |= Public; - - // call base - Callable::initialize(entry, classname.c_str(), _flags); - } - - /** - * Invoke the method - * @param parameters - * @return Value - */ - virtual Value invoke(Parameters ¶meters) override - { - // the object to call a method on - Base *base = parameters.object(); - - // find out which method to call, and call it - switch (_type) { - case 0: (base->*_callback.m0)(); return Value(); - case 1: (base->*_callback.m1)(parameters); return Value(); - case 2: return (base->*_callback.m2)(); - case 3: return (base->*_callback.m3)(parameters); - case 4: (base->*_callback.m4)(); return Value(); - case 5: (base->*_callback.m5)(parameters); return Value(); - case 6: return (base->*_callback.m6)(); - case 7: return (base->*_callback.m7)(parameters); - case 8: _callback.m8(); return Value(); - case 9: _callback.m9(parameters); return Value(); - case 10: return _callback.m10(); - case 11: return _callback.m11(parameters); - default: return Value(); - } - } - - -private: - /** - * Callback type - * @var int - */ - int _type; - - /** - * Access flags (protected, public, abstract, final, private, etc) - * @var int - */ - int _flags; - - /** - * The actual callback - * @var void* - */ - union { - method_callback_0 m0; - method_callback_1 m1; - method_callback_2 m2; - method_callback_3 m3; - method_callback_4 m4; - method_callback_5 m5; - method_callback_6 m6; - method_callback_7 m7; - native_callback_0 m8; - native_callback_1 m9; - native_callback_2 m10; - native_callback_3 m11; - } _callback; -}; - -/** - * End of namespace - */ -} - diff --git a/src/modifiers.cpp b/src/modifiers.cpp deleted file mode 100644 index 3720730..0000000 --- a/src/modifiers.cpp +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Modifiers.cpp - * - * In this file an enumeration type is with the possible - * member modifiers - * - * @author Martijn Otto - * @author Emiel Bruijntjes - * - * @copyright 2014 Copernica BV - */ -#include "includes.h" - -/** - * Set up namespace - */ -namespace Php { - -/** - * The modifiers are constants - */ -const int Static = 0x01; -const int Abstract = 0x02; -const int Final = 0x04; -const int Public = 0x100; -const int Protected = 0x200; -const int Private = 0x400; -const int Const = 0; - -/** - * Modifiers that are supported for methods and properties - */ -const int MethodModifiers = Final | Public | Protected | Private; -const int PropertyModifiers = Final | Public | Protected | Private | Const | Static; - -/** - * End namespace - */ -} diff --git a/src/namespace.cpp b/src/namespace.cpp deleted file mode 100644 index bea31a1..0000000 --- a/src/namespace.cpp +++ /dev/null @@ -1,134 +0,0 @@ -/** - * Namespace.cpp - * - * Implementation of the namespace class - * - * @author Emiel Bruijntjes - * @copyright 2014 Copernica BV - */ -#include "includes.h" - -/** - * Open namespace - */ -namespace Php { - -/** - * Add a native function directly to the extension - * @param name Name of the function - * @param function The function to add - * @param arguments Optional argument specification - * @return Namespace Same object to allow chaining - */ -Namespace &Namespace::add(const char *name, const native_callback_0 &function, const Arguments &arguments) -{ - // add a function - _functions.push_back(std::make_shared(name, function, arguments)); - - // allow chaining - return *this; -} - -/** - * Add a native function directly to the extension - * @param name Name of the function - * @param function The function to add - * @param arguments Optional argument specification - * @return Namespace Same object to allow chaining - */ -Namespace &Namespace::add(const char *name, const native_callback_1 &function, const Arguments &arguments) -{ - // add a function - _functions.push_back(std::make_shared(name, function, arguments)); - - // allow chaining - return *this; -} - -/** - * Add a native function directly to the extension - * @param name Name of the function - * @param function The function to add - * @param arguments Optional argument specification - * @return Namespace Same object to allow chaining - */ -Namespace &Namespace::add(const char *name, const native_callback_2 &function, const Arguments &arguments) -{ - // add a function - _functions.push_back(std::make_shared(name, function, arguments)); - - // allow chaining - return *this; -} - -/** - * Add a native function directly to the extension - * @param name Name of the function - * @param function The function to add - * @param arguments Optional argument specification - * @return Namespace Same object to allow chaining - */ -Namespace &Namespace::add(const char *name, const native_callback_3 &function, const Arguments &arguments) -{ - // add a function - _functions.push_back(std::make_shared(name, function, arguments)); - - // allow chaining - return *this; -} - -/** - * Apply a callback to each registered function - * - * The callback will be called with the name of the namespace, and - * a reference to the registered function. - * - * @param callback - */ -void Namespace::apply(const std::function &callback) -{ - // loop through the functions, and apply the callback - for (auto &function : _functions) callback(_name, *function); - - // loop through the other namespaces - for (auto &ns : _namespaces) ns->apply([this, callback](const std::string &ns, Function &func) { - - // if this is the root namespace, we don't have to change the prefix - if (_name.size() == 0) return callback(ns, func); - - // construct a new prefix - // @todo this could be slightly inefficient - return callback(_name + "\\" + ns, func); - }); -} - -/** - * Apply a callback to each registered class - * - * The callback will be called with the name of the namespace, and - * a reference to the registered class. - * - * @param callback - */ -void Namespace::apply(const std::function &callback) -{ - // loop through the classes, and apply the callback - for (auto &c : _classes) callback(_name, *c); - - // loop through the other namespaces - for (auto &ns : _namespaces) ns->apply([this, callback](const std::string &ns, ClassBase &clss) { - - // if this is the root namespace, we don't have to change the prefix - if (_name.size() == 0) return callback(ns, clss); - - // construct a new prefix - // @todo this could be slightly inefficient - return callback(_name + "\\" + ns, clss); - }); -} - -/** - * End namespace - */ -} - diff --git a/src/notimplemented.h b/src/notimplemented.h deleted file mode 100644 index ee99254..0000000 --- a/src/notimplemented.h +++ /dev/null @@ -1,51 +0,0 @@ -/** - * NotImplemented.h - * - * Exception that is thrown and catched by the library internally to detect - * if a magic method was implemented or not. - * - * Classes have magic methods (like __unset, __isset, etcetera). These methods - * can be implemented by the extension writer, but they do not have to be. - * - * The default implementation of the methods _could_ be to pass on the method - * to the original Zend engine, but the problem is that the magic methods from - * the PHP-CPP library do not have the same signature as the functions in the - * Zend engine. Passing them on directly is thus not possible. - * - * For that reason, the default implementation throw an exception that is - * immediately caught by the PHP-CPP library, so that it knows that the user - * has not overridden the methods, and the default Zend engine magic method - * can be called instead - * - * @author Emiel Bruijntjes - * @copyright 2014 Copernica BV - */ - -/** - * Set up namespace - */ -namespace Php { - -/** - * Class definition - */ -class NotImplemented : public std::exception -{ -public: - /** - * Constructor - */ - NotImplemented() : std::exception() {} - - /** - * Destructor - */ - virtual ~NotImplemented() throw() {} - -}; - -/** - * End namespace - */ -} - diff --git a/src/nullmember.h b/src/nullmember.h deleted file mode 100644 index e035897..0000000 --- a/src/nullmember.h +++ /dev/null @@ -1,59 +0,0 @@ -/** - * NullMember.h - * - * Implementation for a property that is initially set to NULL - * - * @author Emiel Bruijntjes - * @copyright 2013 Copernica BV - */ - -/** - * Set up namespace - */ -namespace Php { - -/** - * Class definition - */ -class NullMember : public Member -{ -public: - /** - * Constructor - * @param name - * @param flags - */ - NullMember(const char *name, int flags) : Member(name, flags) {} - - /** - * Destructor - */ - virtual ~NullMember() {} - - /** - * Internal method to declare the property as constant - * @param zend_class_entry - * @param tsrm_ls - */ - virtual void constant(struct _zend_class_entry *entry TSRMLS_DC) override - { - zend_declare_class_constant_null(entry, _name.c_str(), _name.size() TSRMLS_CC); - } - - /** - * Virtual method to declare the property - * @param entry Class entry - * @param tsrm_ls - */ - virtual void declare(struct _zend_class_entry *entry TSRMLS_DC) override - { - // char* cast is necessary for php 5.3 - zend_declare_property_null(entry, (char *)_name.c_str(), _name.size(), _flags TSRMLS_CC); - } -}; - -/** - * End of namespace - */ -} - diff --git a/src/numericmember.h b/src/numericmember.h deleted file mode 100644 index 0a040d3..0000000 --- a/src/numericmember.h +++ /dev/null @@ -1,67 +0,0 @@ -/** - * NumericMember.h - * - * Implementation for a property that is initially set to a numeric value - * - * @author Emiel Bruijntjes - * @copyright 2013, 2014 Copernica BV - */ - -/** - * Set up namespace - */ -namespace Php { - -/** - * Class definition - */ -class NumericMember : public Member -{ -private: - /** - * The value - * @var long - */ - long _value; - -public: - /** - * Constructor - * @param name - * @param value - * @param flags - */ - NumericMember(const char *name, long value, int flags) : Member(name, flags), _value(value) {} - - /** - * Destructor - */ - virtual ~NumericMember() {} - - /** - * Declare class constant - * @param entry Class entry - * @param tsrm_ls - */ - virtual void constant(struct _zend_class_entry *entry TSRMLS_DC) override - { - zend_declare_class_constant_long(entry, _name.c_str(), _name.size(), _value TSRMLS_CC); - } - - /** - * Virtual method to declare the property - * @param entry Class entry - * @param tsrm_ls - */ - virtual void declare(struct _zend_class_entry *entry TSRMLS_DC) override - { - // char* cast is necessary for php 5.3 - zend_declare_property_long(entry, (char *)_name.c_str(), _name.size(), _value, _flags TSRMLS_CC); - } -}; - -/** - * End of namespace - */ -} - diff --git a/src/object.cpp b/src/object.cpp deleted file mode 100644 index d7fc158..0000000 --- a/src/object.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Object.cpp - * - * @author Emiel Bruijntjes - * @copyright 2014 Copernica BV - */ -#include "includes.h" - -/** - * Set up namespace - */ -namespace Php { - -/** - * Constructor to create a new instance of a builtin class - * - * @param name Name of the class to instantiate - * @param base Implementation of the class - */ -Object::Object(const char *name, Base *base) -{ - // does the object already have a handle? - if (base->implementation()) - { - // the object is already instantiated, we can assign it the this object - operator=(Value(base)); - } - else - { - // we need the tsrm_ls variable - TSRMLS_FETCH(); - - // this is a brand new object that should be allocated, the C++ instance - // is already there (created by the extension) but it is not yet stored - // in PHP, find out the classname first - auto *entry = zend_fetch_class(name, strlen(name), 0 TSRMLS_CC); - if (!entry) throw Php::Exception(std::string("Unknown class name ") + name); - - // construct an implementation (this will also set the implementation - // member in the base object) - new ObjectImpl(entry, base TSRMLS_CC); - - // now we can store it - operator=(Value(base)); - } -} - -/** - * Internal method to instantiate an object - * @param name - */ -void Object::instantiate(const char *name) -{ - // we need the tsrm_ls variable - TSRMLS_FETCH(); - - // convert the name into a class_entry - auto *entry = zend_fetch_class(name, strlen(name), 0 TSRMLS_CC); - if (!entry) throw Php::Exception(std::string("Unknown class name ") + name); - - // initiate the zval (which was already allocated in the base constructor) - object_init_ex(_val, entry); - - // @todo should we call methods like allocating hashtables, copying and - // initializing properties, et cetera????? In all example you always - // see such complicated and next-to-impossible-to-understand - // sequences of functions being called, but this object_init_ex - // also seems to work... - - // @todo is this a memory leak? the base class first initializes a stdClass, - // and then we overwrite it with a specific class - -} - -/** - * End namespace - */ -} - diff --git a/src/objectimpl.h b/src/objectimpl.h deleted file mode 100644 index 41bed53..0000000 --- a/src/objectimpl.h +++ /dev/null @@ -1,204 +0,0 @@ -/** - * ObjectImpl.h - * - * Implementation class for Base objects that allow the objects to be stored - * in the Zend engine - * - * @author Emiel Bruijntjes - * @copyright 2014 Copernica BV - */ - -/** - * Set up namespace - */ -namespace Php { - -/** - * Class definition - */ -class ObjectImpl -{ -private: - /** - * Structure with a first element which is a mixed object, so that - * it can be casted to a zend_object - * @var MixedObject - */ - struct MixedObject - { - /** - * The actual object is the first member, so that casting - * the MixedObject to a zend_object will also result in a valid pointer - * @var zend_object - */ - zend_object php; - - /** - * Pointer to ourselves - * @var ObjectImpl - */ - ObjectImpl *self; - - - } _mixed; - - /** - * Pointer to the C++ implementation - * @var Base - */ - Base *_object; - - /** - * The object handle in the Zend engine - * @var int - */ - int _handle; - -public: - /** - * Constructor - * - * This will create a new object in the Zend engine. - * - * @param entry Zend class entry - * @param base C++ object that already exists - * @param tsrm_ls Optional threading data - */ - ObjectImpl(zend_class_entry *entry, Base *base TSRMLS_DC) - { - // copy properties to the mixed object - _mixed.php.ce = entry; - _mixed.self = this; - - // store the c++ object - _object = base; - - // initialize the object - zend_object_std_init(&_mixed.php, entry TSRMLS_CC); - -#if PHP_VERSION_ID < 50399 - - // tmp variable - zval *tmp; - - // initialize the properties, php 5.3 way - zend_hash_copy(_mixed.php.properties, &entry->default_properties, (copy_ctor_func_t) zval_property_ctor, &tmp, sizeof(zval*)); - -#else - - // version higher than 5.3 have an easier way to initialize - object_properties_init(&_mixed.php, entry); - -#endif - -#ifdef ZTS - - // when in thread safety mode, the destruct method and free method have - // an extra parameter holding thread information - using DestructType = void(zend_object*,unsigned int,void***); - using FreeType = void(zend_object*,void***); - -#else - - // not in thread mode: no special parameter for the tsrm_ls variable - using DestructType = void(zend_object*,unsigned int); - using FreeType = void(zend_object*); - -#endif - - // store the two destruct methods in temporary vars - 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... - _handle = zend_objects_store_put(&_mixed, (zend_objects_store_dtor_t)destructMethod, (zend_objects_free_object_storage_t)freeMethod, NULL TSRMLS_CC); - - // the object may remember that we are its implementation object - base->_impl = this; - } - - /** - * Destructor - */ - virtual ~ObjectImpl() - { - // deallocate the cpp object - delete _object; - } - - /** - * Destruct the object - * @param tsrm_ls - */ - void destruct(TSRMLS_D) - { - // pass on to the default destructor - zend_objects_free_object_storage(&_mixed.php TSRMLS_CC); - - // destruct the object - delete this; - } - - /** - * Find the object based on a zval - * @param val Zval object - * @param tsrm_ls Optional pointer to thread info - * @return ObjectImpl - */ - static ObjectImpl *find(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); - - // done - return object->self; - } - - /** - * Find the object based on a zend_object - * @param object Zend object pointer - * @return ObjectImpl - */ - static ObjectImpl *find(const zend_object *object) - { - // retrieve the old object, which we are going to copy - const MixedObject *mixed = (MixedObject *)object; - - // done - return mixed->self; - } - - /** - * Retrieve the base class of the original C++ object - * @return Base - */ - Base *object() - { - return _object; - } - - /** - * Pointer to the PHP object - * @return zend_object - */ - zend_object *php() - { - return &_mixed.php; - } - - /** - * Retrieve the handle object - * @return int - */ - int handle() const - { - return _handle; - } -}; - -/** - * End of namespace - */ -} - diff --git a/src/origexception.h b/src/origexception.h deleted file mode 100644 index 775e412..0000000 --- a/src/origexception.h +++ /dev/null @@ -1,145 +0,0 @@ -/** - * OrigException.h - * - * Class that wraps around an exception that was thrown by PHP code, - * and that could - but not necessarily has to - be caught by C++ - * - * @author Emiel Bruijntjes - * @copyright 2013 Copernica BV - */ - -/** - * Set up namespace - */ -namespace Php { - -/** - * Class definition - */ -class OrigException : public Value, public Exception -{ -private: - /** - * Is this a an exception that was caught by extension C++ code. - * - * When the object is initially created, we assume that it will be caught - * by C++ code. If it later turns out that the PHP-CPP can catch this - * exception after the extension C++ code ran, the variable is set back - * to false. - * - * @var bool - */ - bool _handled = true; - -#ifdef ZTS - /** - * When we run in multi-thread mode, we store the thread handle - * @var void*** - */ - TSRMLS_D; -#endif - -public: - /** - * Constructor - * @param val - */ - OrigException(zval *val TSRMLS_DC) : - Value(val), Exception("OrigException") - { -#ifdef ZTS - // copy tsrm_ls - this->TSRMLS_C = TSRMLS_C; -#endif - } - - /** - * Copy constructor - * @param exception - */ - OrigException(const OrigException &exception) : - Value(exception), Exception("OrigException"), _handled(exception._handled) - { -#ifdef ZTS - // copy tsrm_ls - TSRMLS_C = exception.TSRMLS_C; -#endif - } - - /** - * Move constructor - * @param exception - */ - OrigException(OrigException &&exception) : - Value(std::move(exception)), Exception("OrigException"), _handled(exception._handled) - { - // set other exception to handled so that it wont do anything on destruction - exception._handled = true; - -#ifdef ZTS - // copy tsrm_ls - TSRMLS_C = exception.TSRMLS_C; -#endif - } - - /** - * Destructor - */ - virtual ~OrigException() throw() - { - // if the exception was not handled by C++ code, we're not going to do anything - // and the exception stays active - if (!_handled) return; - - // the exception was handled, so we should clean it up - zend_clear_exception(TSRMLS_C); - } - - /** - * This is _not_ a native exception, it was thrown by a PHP script - * @return bool - */ - virtual bool native() const override - { - return false; - } - - /** - * Reactivate the exception - */ - void reactivate() - { - // it was not handled by extension C++ code - _handled = false; - } -}; - -/** - * Global function to process an exception - * @param exception - * @param tsrm_ls - */ -inline void process(Exception &exception TSRMLS_DC) -{ - // is this a native exception? - if (exception.native()) - { - // the exception is native, call the zend throw method - zend_throw_exception(zend_exception_get_default(TSRMLS_C), (char *)exception.what(), 0 TSRMLS_CC); - } - else - { - // this is not a native exception, so it was originally thrown by a - // php script, and then not caught by the c++ of the extensiont, we are - // going to tell to the exception that it is still active - OrigException &orig = static_cast(exception); - - // reactive the exception - orig.reactivate(); - } -} - -/** - * End of namespace - */ -} diff --git a/src/parametersimpl.h b/src/parametersimpl.h deleted file mode 100644 index fd14238..0000000 --- a/src/parametersimpl.h +++ /dev/null @@ -1,53 +0,0 @@ -/** - * ParametersImpl.h - * - * Extended parameters class that can be instantiated - * - * @author Emiel Bruijntjes - * @copyright 2013 Copernica BV - */ - -/** - * Set up namespace - */ -namespace Php { - -/** - * Class definition - */ -class ParametersImpl : public Parameters -{ -public: - /** - * Constructor - * @param this_ptr Pointer to the object - * @param argc Number of arguments - * @param tsrm_ls - */ - ParametersImpl(zval *this_ptr, int argc TSRMLS_DC) : Parameters(this_ptr ? ObjectImpl::find(this_ptr TSRMLS_CC)->object() : nullptr) - { - // reserve plenty of space - reserve(argc); - - // loop through the arguments - for (int i=0; i - * @copyright 2014 Copernica BV - */ - -/** - * Set up namespace - */ -namespace Php { - -/** - * Class definition - */ -class Property -{ -private: - /** - * The getter - * @var getter_callback - */ - union { - getter_callback_0 g0; - getter_callback_1 g1; - } _getter; - - /** - * The setter - * @var setter_callback - */ - union { - setter_callback_0 s0; - setter_callback_1 s1; - } _setter; - - /** - * Type of getter - * @var char - */ - int _gtype = 0; - - /** - * Type of setter - * @var char - */ - int _stype = 100; - -public: - /** - * Constructor - * @param getter - * @param setter - */ - Property(const getter_callback_0 &getter) : _gtype(0) - { - _getter.g0 = getter; - } - - /** - * Constructor - * @param getter - * @param setter - */ - Property(const getter_callback_1 &getter) : _gtype(1) - { - _getter.g1 = getter; - } - - - /** - * Constructor - * @param getter - * @param setter - */ - Property(const getter_callback_0 &getter, const setter_callback_0 &setter) : _gtype(0), _stype(0) - { - _getter.g0 = getter; - _setter.s0 = setter; - } - - /** - * Constructor - * @param getter - * @param setter - */ - Property(const getter_callback_1 &getter, const setter_callback_0 &setter) : _gtype(1), _stype(0) - { - _getter.g1 = getter; - _setter.s0 = setter; - } - - /** - * Constructor - * @param getter - * @param setter - */ - Property(const getter_callback_0 &getter, const setter_callback_1 &setter) : _gtype(0), _stype(1) - { - _getter.g0 = getter; - _setter.s1 = setter; - } - - /** - * Constructor - * @param getter - * @param setter - */ - Property(const getter_callback_1 &getter, const setter_callback_1 &setter) : _gtype(1), _stype(1) - { - _getter.g1 = getter; - _setter.s1 = setter; - } - - /** - * Copy constructor - * @param that - */ - Property(const Property &that) : - _getter(that._getter), _setter(that._setter), _gtype(that._gtype), _stype(that._stype) {} - - /** - * Destructor - */ - virtual ~Property() {} - - /** - * Get the property - * @param base Object to call it on - * @return Value - */ - Value get(Base *base) - { - if (_gtype == 0) return (base->*_getter.g0)(); - else return (base->*_getter.g1)(); - } - - /** - * Set the property - * @param base Object to call it on - * @param value New value - * @return bool - */ - bool set(Base *base, const Value &value) - { - switch (_stype) { - case 0: (base->*_setter.s0)(value); return true; - case 1: (base->*_setter.s1)(value); return true; - default: return false; - } - } -}; - -/** - * End of namespace - */ -} - diff --git a/src/streambuf.cpp b/src/streambuf.cpp deleted file mode 100644 index e258b4e..0000000 --- a/src/streambuf.cpp +++ /dev/null @@ -1,93 +0,0 @@ -/** - * StreamBuf.cpp - * - * Implementation file for the StreamBuf class - * - * @see http://www.mr-edd.co.uk/blog/beginners_guide_streambuf - * - * @author Emiel Bruijntjes - * @copyright 2014 Copernica BV - */ -#include "includes.h" - -/** - * Set up namespace - */ -namespace Php { - -/** - * Constructor - * @param error - */ -StreamBuf::StreamBuf(int error) : _error(error) -{ - // we reserve one byte, so that when overflow is called, we still have one - // byte extra in the buffer to put the overflowed byte int - setp(_buffer, _buffer+1024-1); -} - - -/** - * Method that is called when the internal buffer overflows - * @param c - * @return int - */ -int StreamBuf::overflow(int c) -{ - // for error buffers, overflow is simply discarded - if (_error) return c; - - // end-of-file has not output, we call EOF directly, and by using the - // comma operator we ensure that EOF is returned - if (c == EOF) return sync(), EOF; - - // because we lied the underlying buffer about the size of the buffer - // by one byte, there is no real overflow, and we can still add the byte - // to the end of the buffer - *pptr() = c; - - // increment buffer size - pbump(1); - - // and now we're going to syn the buffer - return sync() == -1 ? EOF : c; -} - -/** - * Called when the internal buffer should be synchronized - * @return int - */ -int StreamBuf::sync() -{ - // current buffer size - size_t size = pptr() - pbase(); - - // is this the error stream or the regular output stream? - if (_error) - { - // write to error (the zend_error() method is a varargs function, - // which means that we have to include a printf() like format as first - // parameter. We can not specify pbase() directly, because (1) it is - // not null terminated and (2) it could contain % signs and allow all - // sorts of buffer overflows. - zend_error(_error, "%.*s", (int)size, pbase()); - - } - else - { - // write to zend - zend_write(pbase(), size); - } - - // reset the buffer - pbump(-size); - - // done - return 0; -} - -/** - * End namespace - */ -} - \ No newline at end of file diff --git a/src/streambuf.h b/src/streambuf.h deleted file mode 100644 index ed1506a..0000000 --- a/src/streambuf.h +++ /dev/null @@ -1,80 +0,0 @@ -/** - * StreamBuf.h - * - * PHP output stream buffer which is used by the Php::out object to - * have an output stream just like the regular std::ostream buffers, - * but that sends all output to PHP output - * - * @author Emiel Bruijntjes - * @copyright 2014 Copernica BV - */ - -/** - * Set up namespace - */ -namespace Php { - -/** - * Class definition - */ -class StreamBuf : public std::streambuf -{ -public: - /** - * Constructor - * @param error the error type, or 0 for regular output - */ - StreamBuf(int error); - - /** - * No copying or moving - * @param that - */ - StreamBuf(const StreamBuf &that) = delete; - StreamBuf(StreamBuf &&that) = delete; - - /** - * Destructor - */ - virtual ~StreamBuf() {} - - /** - * No copying or moving - * @param that - */ - StreamBuf &operator=(const StreamBuf &that) = delete; - StreamBuf &operator=(StreamBuf &&that) = delete; - -protected: - /** - * Method that is called when the internal buffer overflows - * @param c - * @return int - */ - virtual int overflow(int c = EOF) override; - - /** - * Called when the internal buffer should be synchronized - * @return int - */ - virtual int sync() override; - -private: - /** - * The error type, or 0 for regular output - * @var int - */ - int _error; - - /** - * The internal buffer - * @var char[] - */ - char _buffer[1024]; -}; - -/** - * End namespace - */ -} - diff --git a/src/streams.cpp b/src/streams.cpp deleted file mode 100644 index ba6d916..0000000 --- a/src/streams.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Streams.cpp - * - * Implementation of the streams - * - * @author Emiel Bruijntjes - * @copyright 2014 Copernica BV - */ -#include "includes.h" - -/** - * Set up namespace - */ -namespace Php { - -/** - * Some static buffers for writing data - * @var StreamBuf - */ -static StreamBuf bufOut (0); -static StreamBuf bufError (E_ERROR); -static StreamBuf bufWarning (E_WARNING); -static StreamBuf bufNotice (E_NOTICE); -static StreamBuf bufDeprecated (E_DEPRECATED); - -/** - * Create the actual steams - * @var std::ostream - */ -std::ostream out (&bufOut); -std::ostream error (&bufError); -std::ostream warning (&bufWarning); -std::ostream notice (&bufNotice); -std::ostream deprecated (&bufDeprecated); - -/** - * End namespace - */ -} - diff --git a/src/stringmember.h b/src/stringmember.h deleted file mode 100644 index e58cd3d..0000000 --- a/src/stringmember.h +++ /dev/null @@ -1,83 +0,0 @@ -/** - * StringMember.h - * - * Implementation for a property that is initially set to a strnig value - * - * @author Emiel Bruijntjes - * @copyright 2013 Copernica BV - */ - -/** - * Set up namespace - */ -namespace Php { - -/** - * Class definition - */ -class StringMember : public Member -{ -private: - /** - * The value - * @var string - */ - std::string _value; - -public: - /** - * Constructor - * @param name - * @param value - * @param size - * @param flags - */ - StringMember(const char *name, const char *value, size_t size, int flags) : Member(name, flags), _value(value, size) {} - - /** - * Constructor - * @param name - * @param value - * @param flags - */ - StringMember(const char *name, const char *value, int flags) : StringMember(name, value, strlen(value), flags) {} - - /** - * Constructor - * @param name - * @param value - * @param flags - */ - StringMember(const char *name, const std::string &value, int flags) : Member(name, flags), _value(value) {} - - /** - * Destructor - */ - virtual ~StringMember() {} - - /** - * Virtual method to declare class constant - * @param entry Class entry - * @param tsrm_ls - */ - virtual void constant(struct _zend_class_entry *entry TSRMLS_DC) override - { - zend_declare_class_constant_stringl(entry, _name.c_str(), _name.size(), _value.c_str(), _value.size() TSRMLS_CC); - } - - /** - * Virtual method to declare the property - * @param entry Class entry - */ - virtual void declare(struct _zend_class_entry *entry TSRMLS_DC) override - { - // cast to char* is necessary for php 5.3 - zend_declare_property_stringl(entry, (char *)_name.c_str(), _name.size(), (char *)_value.c_str(), _value.size(), _flags TSRMLS_CC); - } -}; - -/** - * End of namespace - */ -} - diff --git a/src/super.cpp b/src/super.cpp deleted file mode 100644 index 506d4a5..0000000 --- a/src/super.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Super.cpp - * - * @copyright 2014 Copernica BV - * @author Emiel Bruijntjes - */ -#include "includes.h" - -/** - * Set up namespace - */ -namespace Php { - -/** - * A number of super-globals are always accessible - */ -Super POST (TRACK_VARS_POST, "_POST"); -Super GET (TRACK_VARS_GET, "_GET"); -Super COOKIE (TRACK_VARS_COOKIE, "_COOKIE"); -Super SERVER (TRACK_VARS_SERVER, "_SERVER"); -Super ENV (TRACK_VARS_ENV, "_ENV"); -Super FILES (TRACK_VARS_FILES, "_FILES"); -Super REQUEST (TRACK_VARS_REQUEST, "_REQUEST"); - -/** - * Array access operator - * This can be used for accessing associative arrays - * @param key - * @return Value - */ -Value Super::operator[](const std::string &key) -{ - // we need the tsrm_ls pointer - TSRMLS_FETCH(); - - // call zend_is_auto_global to ensure that the just-in-time globals are loaded - if (_name) { zend_is_auto_global(_name, strlen(_name) TSRMLS_CC); _name = nullptr; } - - // create a value object that wraps around the actual zval - Value value(PG(http_globals)[_index]); - - // pass on the call - return value[key]; -} - -/** - * Array access operator - * This can be used for accessing associative arrays - * @param key - * @return Value - */ -Value Super::operator[](const char *key) -{ - // we need the tsrm_ls pointer - TSRMLS_FETCH(); - - // call zend_is_auto_global to ensure that the just-in-time globals are loaded - if (_name) { zend_is_auto_global(_name, strlen(_name) TSRMLS_CC); _name = nullptr; } - - // create a value object that wraps around the actual zval - Value value(PG(http_globals)[_index]); - - // pass on the call - return value[key]; -} - -/** - * End namespace - */ -} - diff --git a/src/traverseiterator.h b/src/traverseiterator.h deleted file mode 100644 index 16f1ce7..0000000 --- a/src/traverseiterator.h +++ /dev/null @@ -1,259 +0,0 @@ -/** - * TraverseIterator.h - * - * When an object from PHP userspace implements its own iterator methods, - * and the Php::Value object is used to iterate over its properties, this - * TraversableIterator class is used to iterate over the properties - * - * @author Emiel Bruijntjes - * @copyright 2014 Copernica BV - */ - -/** - * Set up namespace - */ -namespace Php { - -/** - * Class definition - */ -class TraverseIterator : public ValueIteratorImpl -{ -public: - /** - * Constructor - * @param object - * @param begin - * @param tsrm_ls - */ - TraverseIterator(zval *object, bool begin TSRMLS_DC) : _object(object) - { - // leap out if this iterator starts at the end - if (!begin) return; - - // we need the class entry - auto *entry = zend_get_class_entry(object TSRMLS_CC); - - // create the iterator - _iter = entry->get_iterator(entry, object, false TSRMLS_CC); - - // rewind the iterator - _iter->funcs->rewind(_iter TSRMLS_CC); - - // read the first key/value pair - read(TSRMLS_C); - } - - /** - * Copy constructor - * @param that - * @param tsrm_ls - */ - TraverseIterator(const TraverseIterator &that TSRMLS_DC) : TraverseIterator(that._object, that._iter != nullptr TSRMLS_CC) - { - // @todo this is a broken implementation, the copy is at the start - // position, while we'd like to be at the same position - } - - /** - * Destructor - */ - virtual ~TraverseIterator() - { - // do nothing if iterator is already invalid - if (!_iter) return; - - // we need the tsrm pointer - TSRMLS_FETCH(); - - // call the iterator destructor - if (_iter) _iter->funcs->dtor(_iter TSRMLS_CC); - } - - /** - * Clone the object - * @param tsrm_ls - * @return ValueIteratorImpl* - */ - virtual ValueIteratorImpl *clone() override - { - // we need the tsrm_ls variable - TSRMLS_FETCH(); - - // construct iterator - return new TraverseIterator(*this TSRMLS_CC); - } - - /** - * Increment position (pre-increment) - * @param tsrm_ls - * @return bool - */ - virtual bool increment() override - { - // do we still have an iterator? - if (!_iter) return false; - - // we need the tsrm_ls variable - TSRMLS_FETCH(); - - // movw it forward - _iter->funcs->move_forward(_iter TSRMLS_CC); - - // and read current data - read(TSRMLS_C); - } - - /** - * Decrement position (pre-decrement) - * @return bool - */ - virtual bool decrement() override - { - // not possible with PHP iterators - throw Exception("Impossible to iterate backwards"); - } - - /** - * Compare with other iterator - * @param that - * @return bool - */ - virtual bool equals(const ValueIteratorImpl *that) const override - { - // of course if the objects are identical - if (this == that) return true; - - // cast to traverse-iterator - TraverseIterator *other = (TraverseIterator *)that; - - // if both objects are in an invalid state we consider them to be identical - if (!_iter && !other->_iter) return true; - - // although the iterators could be at the same pos, for simplicity - // we consider them different here - return false; - } - - /** - * Derefecence, this returns a std::pair with the current key and value - * @return std::pair - */ - virtual const std::pair ¤t() const - { - return _data; - } - -private: - /** - * The object that is iterated over - * @var _val - */ - zval *_object = nullptr; - - /** - * The iterator from Zend - * @var zend_object_iterator - */ - struct _zend_object_iterator *_iter = nullptr; - - /** - * Current data - * @var pair - */ - std::pair _data; - - - /** - * Read current data - * @param tsrm_ls - * @return bool - */ - bool read(TSRMLS_D) - { - // not possible when no iterator exists - if (!_iter) return false; - - // is the iterator at a valid position? - if (_iter->funcs->valid(_iter TSRMLS_CC) == FAILURE) return invalidate(TSRMLS_C); - -#if PHP_VERSION_ID >= 50500 - - // create a value object - Value val; - - // call the function to get the key - _iter->funcs->get_current_key(_iter, val._val TSRMLS_CC); - - // store the key - _data.first = val; - -#else - - // variable we need for fetching the key, and that will be assigned by - // the PHP engine (this is php 5.3 code) - char *str_key; unsigned int str_key_len; unsigned long int_key; - - // php 5.4 or php 5.3 code, fetch the current key - int type = _iter->funcs->get_current_key(_iter, &str_key, &str_key_len, &int_key TSRMLS_CC); - - // what sort of key do we have? - if (type == HASH_KEY_IS_LONG) - { - // we have an int key - _data.first = (int64_t)int_key; - } - else - { - // we have a string key that is already allocated - _data.first = str_key; - - // deallocate the data - efree(str_key); - } - -#endif - - // now we're going to fetch the value, for this we need a strange zval - // it is strange that this is a pointer-to-pointer, but that is how - // the Zend engine implements this. It is going to be filled with - // a pointer to a memory address that is guaranteed to hold a valid - // zval. - zval **zval; - - // get the current value - _iter->funcs->get_current_data(_iter, &zval TSRMLS_CC); - - // wrap the zval in a value object - _data.second = Value(*zval); - - // done - return true; - } - - /** - * Invalidate the object - * @param tsrm_ls - * @return bool - */ - bool invalidate(TSRMLS_D) - { - // skip if already invalid - if (!_iter) return false; - - // reset the iterator - _iter->funcs->dtor(_iter TSRMLS_CC); - - // set back to null - _iter = nullptr; - - // done - return false; - } -}; - -/** - * End namespace - */ -} - diff --git a/src/value.cpp b/src/value.cpp deleted file mode 100644 index 2ca1585..0000000 --- a/src/value.cpp +++ /dev/null @@ -1,1911 +0,0 @@ -/** - * Value.cpp - * - * Implementation for the Value class, which wraps a PHP userspace - * value (a 'zval' in Zend's terminology) into a C++ object - * - * Reminder for the implementer: - * - * A 'zval' is an object that represents a _value_ in the PHP user space, - * and thus not a variable. A 'value' or 'zval' can be used by many - * different variables at the same time. The 'refcount' property of the - * zval holds the number of variables ($a, $b, $c, et cetera) that are - * all linked to the same value. With this system, PHP can implement copy - * on write behavior. - * - * Next to the refcount, the zval also holds a is_ref property, which is - * set to true if all variables linked to the value are references of each - * other. Thus is $a, $b and $c all point to the same variable, and is_ref - * is set to true, changing the value means that the $a, $b and $c value - * are all updated. If is_res was false, a change to $a would not mean a - * change to $b, and the zval should have been copied first. - * - * - * @author Emiel Bruijntjes - * @copyright 2013, 2014 Copernica BV - */ -#include "includes.h" - -/** - * Set up namespace - */ -namespace Php { - -/** - * Constructor (value = NULL) - */ -Value::Value() -{ - // create a null zval - MAKE_STD_ZVAL(_val); - ZVAL_NULL(_val); -} - -/** - * Constructor for null ptr -*/ -Value::Value(std::nullptr_t value) -{ - // create a null zval - MAKE_STD_ZVAL(_val); - ZVAL_NULL(_val); -} - -/** - * Constructor based on integer value - * @param value - */ -Value::Value(int16_t value) -{ - // create an integer zval - MAKE_STD_ZVAL(_val); - ZVAL_LONG(_val, value); -} - -/** - * Constructor based on integer value - * @param value - */ -Value::Value(int32_t value) -{ - // create an integer zval - MAKE_STD_ZVAL(_val); - ZVAL_LONG(_val, value); -} - -/** - * Constructor based on int64_t value - * @param value - */ -Value::Value(int64_t value) -{ - // create an integer zval - MAKE_STD_ZVAL(_val); - ZVAL_LONG(_val, value); -} - -/** - * Constructor based on boolean value - * @param value - */ -Value::Value(bool value) -{ - // create a boolean zval - MAKE_STD_ZVAL(_val); - ZVAL_BOOL(_val, value); -} - -/** - * Constructor based on single character - * @param value - */ -Value::Value(char value) -{ - // create a string zval - MAKE_STD_ZVAL(_val); - ZVAL_STRINGL(_val, &value, 1, 1); -} - -/** - * Constructor based on string value - * @param value - */ -Value::Value(const std::string &value) -{ - // create a string zval - MAKE_STD_ZVAL(_val); - ZVAL_STRINGL(_val, value.c_str(), value.size(), 1); -} - -/** - * Constructor based on a byte array - * @param value - * @param size - */ -Value::Value(const char *value, int size) -{ - // create a string zval - MAKE_STD_ZVAL(_val); - ZVAL_STRINGL(_val, value, size < 0 ? strlen(value) : size, 1); -} - -/** - * Constructor based on decimal value - * @param value - */ -Value::Value(double value) -{ - // create a double zval - MAKE_STD_ZVAL(_val); - ZVAL_DOUBLE(_val, value); -} - -/** - * Wrap object around zval - * @param zval Value to wrap - * @param ref Force this to be a reference - */ -Value::Value(struct _zval_struct *val, bool ref) -{ - // just copy the zval into this object - _val = val; - - // if the variable is not already a reference, and it has more than one - // variable pointing to it, we should seperate it so that any changes - // we're going to make will not change the other variable - if (ref && Z_REFCOUNT_P(_val) > 1) - { - // separate the zval - SEPARATE_ZVAL_IF_NOT_REF(&_val); - } - - // we see ourselves as reference too - Z_ADDREF_P(_val); - - // we're ready if we do not have to force it as a reference - if (!ref || Z_ISREF_P(_val)) return; - - // make this a reference - Z_SET_ISREF_P(_val); -} - -/** - * Wrap around an object - * @param object - */ -Value::Value(const Base *object) -{ - // there are two options: the object was constructed from user space, - // and is already linked to a handle, or it was constructed from C++ - // space, and no handle does yet exist, find the implementation object - auto *impl = object->implementation(); - - // do we have a handle? - if (!impl) throw Php::Exception("Assigning an unassigned object to a variable"); - - // make a regular zval, and set it to an object - MAKE_STD_ZVAL(_val); - Z_TYPE_P(_val) = IS_OBJECT; - Z_OBJ_HANDLE_P(_val) = impl->handle(); - - // we need the tsrm_ls variable - TSRMLS_FETCH(); - - // we have to lookup the object in the object-table - zend_object_store_bucket *obj_bucket = &EG(objects_store).object_buckets[impl->handle()]; - - // this is copy-pasted from zend_objects.c - and it is necessary too! - if (!obj_bucket->bucket.obj.handlers) obj_bucket->bucket.obj.handlers = &std_object_handlers; - - // store the handlers in the zval too (cast is necessary for php 5.3) - Z_OBJ_HT_P(_val) = (zend_object_handlers*)obj_bucket->bucket.obj.handlers; - - // run the copy constructor - zval_copy_ctor(_val); -} - -/** - * Copy constructor - * @param value - */ -Value::Value(const Value &that) -{ - // is the other variable a reference? - if (Z_ISREF_P(that._val)) - { - // because this is supposed to be a COPY, we can not add ourselves - // to the variable but have to allocate a new variable - ALLOC_ZVAL(_val); - INIT_PZVAL_COPY(_val, that._val); - - // we have to call the copy constructor to copy the entire other zval - zval_copy_ctor(_val); - } - else - { - // simply use the same zval - _val = that._val; - } - - // that zval has one more reference - Z_ADDREF_P(_val); - - -// Below the old implementation - I thought really hard about it and I though -// it was a correct and very smart implementation. However, it does not work -// when you swap two variables. I changed it to the implementation above, but -// maybe that implementation introduces other bugs??? Let's keep the old -// implementation for a while in this file, but commented out -// -// // how many references does the other object have? -// if (Z_REFCOUNT_P(that._val) > 1 && !Z_ISREF_P(that._val)) -// { -// // there are already multiple variables linked to this value, and it -// // is not a reference. this implies that we can not turn this variable -// // into a reference, otherwise strange things could happen, we're going -// // to create a new zval -// ALLOC_ZVAL(_val); -// INIT_PZVAL_COPY(_val, that._val); -// zval_copy_ctor(_val); -// } -// else -// { -// // simply use the same zval -// _val = that._val; -// } -// -// // the other object only has one variable using it, or it is already -// // a variable by reference, we can safely add one more reference to it -// // and make it a variable by reference if it was not already a ref -// Z_ADDREF_P(_val); -// -// // make reference -// Z_SET_ISREF_P(_val); -} - -/** - * Move constructor - * @param value - */ -Value::Value(Value &&that) : _val(that._val) -{ - // clear the other object - that._val = nullptr; -} - -/** - * Destructor - */ -Value::~Value() -{ - // ignore if moved - if (!_val) return; - - // if there were two references or less, we're going to remove a reference - // and only one reference will remain, the object will then impossible be - // a reference - if (Z_REFCOUNT_P(_val) <= 2) Z_UNSET_ISREF_P(_val); - - // destruct the zval (this function will decrement the reference counter, - // and only destruct if there are no other references left) - zval_ptr_dtor(&_val); -} - -/** - * Detach the zval - * - * This will unlink the zval internal structure from the Value object, - * so that the destructor will not reduce the number of references and/or - * deallocate the zval structure. This is used for functions that have to - * return a zval pointer, that would otherwise be deallocated the moment - * the function returns. - * - * @return zval - */ -zval *Value::detach() -{ - // leap out if already detached - if (!_val) return nullptr; - - // copy return value - zval *result = _val; - - // decrement reference counter - Z_DELREF_P(_val); - - // reset internal object - _val = nullptr; - - // done - return result; -} - -/** - * Attach a different zval - * - * This will first detach the current zval, and link the Value object to - * a different zval. - * - * @param val - */ -void Value::attach(struct _zval_struct *val) -{ - // detach first - if (_val) detach(); - - // store the zval - _val = val; - - // add one more reference - Z_ADDREF_P(_val); -} - -/** - * Attach a different zval - * - * This will first detach the current zval, and link the Value object to - * a new zval - * - * @param hashtable - */ -void Value::attach(struct _hashtable *hashtable) -{ - // detach first - if (_val) detach(); - - // construct a new zval - MAKE_STD_ZVAL(_val); - - // store pointer to the hashtable, and mark the zval as an array - Z_ARRVAL_P(_val) = hashtable; - Z_TYPE_P(_val) = IS_ARRAY; - - // add a reference - Z_ADDREF_P(_val); -} - -/** - * Retrieve the refcount - * @return int - */ -int Value::refcount() -{ - return Z_REFCOUNT_P(_val); -} - -/** - * Move operator - * @param value - * @return Value - */ -Value &Value::operator=(Value &&value) -{ - // skip self assignment - if (this == &value) return *this; - - // is the object a reference? - if (Z_ISREF_P(_val)) - { - // @todo difference if the other object is a reference or not? - - // the current object is a reference, this means that we should - // keep the zval object, and copy the other value into it, get - // the current refcount - int refcount = Z_REFCOUNT_P(_val); - - // make the copy - *_val = *value._val; - - // restore reference and refcount setting - Z_SET_ISREF_TO_P(_val, true); - Z_SET_REFCOUNT_P(_val, refcount); - - // how many references did the old variable have? - if (Z_REFCOUNT_P(value._val) > 1) - { - // the other object already had multiple references, this - // implies that many other PHP variables are also referring - // to it, and we still need to store its contents, with one - // reference less - Z_DELREF_P(value._val); - - // and we need to run the copy constructor on the current - // value, because we're making a deep copy - zval_copy_ctor(_val); - } - else - { - // we need the tsrm_ls variable - TSRMLS_FETCH(); - - // the last and only reference to the other object was - // removed, we no longer need it - FREE_ZVAL(value._val); - - // the other object is no longer valid - value._val = nullptr; - } - } - else - { - // destruct the zval (this function will decrement the reference counter, - // and only destruct if there are no other references left) - zval_ptr_dtor(&_val); - - // just copy the zval completely - _val = value._val; - - // the other object is no longer valid - value._val = nullptr; - } - - // done - return *this; -} - -/** - * Assignment operator - * @param value - * @return Value - */ -Value &Value::operator=(const Value &value) -{ - // skip self assignment - if (this == &value) return *this; - - // is the object a reference? - if (Z_ISREF_P(_val)) - { - // the current object is a reference, this means that we should - // keep the zval object, and copy the other value into it, get - // the current refcount - int refcount = Z_REFCOUNT_P(_val); - - // clean up the current zval (but keep the zval structure) - zval_dtor(_val); - - // make the copy - *_val = *value._val; - zval_copy_ctor(_val); - - // restore refcount and reference setting - Z_SET_ISREF_TO_P(_val, true); - Z_SET_REFCOUNT_P(_val, refcount); - } - else - { - // destruct the zval (this function will decrement the reference counter, - // and only destruct if there are no other references left) - zval_ptr_dtor(&_val); - - // just copy the zval, and the refcounter - _val = value._val; - - // and we have one more reference - Z_ADDREF_P(_val); - } - - // update the object - return *this; -} - - -/** - * Assignment operator - * @param value - * @return Value - */ -Value &Value::operator=(std::nullptr_t value) -{ - // if this is not a reference variable, we should detach it to implement copy on write - SEPARATE_ZVAL_IF_NOT_REF(&_val); - - // deallocate current zval (without cleaning the zval structure) - zval_dtor(_val); - - // change to null value - ZVAL_NULL(_val); - - // update the object - return *this; -} - -/** - * Assignment operator - * @param value - * @return Value - */ -Value &Value::operator=(int16_t value) -{ - // if this is not a reference variable, we should detach it to implement copy on write - SEPARATE_ZVAL_IF_NOT_REF(&_val); - - // deallocate current zval (without cleaning the zval structure) - zval_dtor(_val); - - // set new value - ZVAL_LONG(_val, value); - - // update the object - return *this; -} - -/** - * Assignment operator - * @param value - * @return Value - */ -Value &Value::operator=(int32_t value) -{ - // if this is not a reference variable, we should detach it to implement copy on write - SEPARATE_ZVAL_IF_NOT_REF(&_val); - - // deallocate current zval (without cleaning the zval structure) - zval_dtor(_val); - - // set new value - ZVAL_LONG(_val, value); - - // update the object - return *this; -} - -/** - * Assignment operator - * @param value - * @return Value - */ -Value &Value::operator=(int64_t value) -{ - // if this is not a reference variable, we should detach it to implement copy on write - SEPARATE_ZVAL_IF_NOT_REF(&_val); - - // deallocate current zval (without cleaning the zval structure) - zval_dtor(_val); - - // set new value - ZVAL_LONG(_val, value); - - // update the object - return *this; -} - -/** - * Assignment operator - * @param value - * @return Value - */ -Value &Value::operator=(bool value) -{ - // if this is not a reference variable, we should detach it to implement copy on write - SEPARATE_ZVAL_IF_NOT_REF(&_val); - - // deallocate current zval (without cleaning the zval structure) - zval_dtor(_val); - - // set new value - ZVAL_BOOL(_val, value); - - // update the object - return *this; -} - -/** - * Assignment operator - * @param value - * @return Value - */ -Value &Value::operator=(char value) -{ - // if this is not a reference variable, we should detach it to implement copy on write - SEPARATE_ZVAL_IF_NOT_REF(&_val); - - // deallocate current zval (without cleaning the zval structure) - zval_dtor(_val); - - // set new value - ZVAL_STRINGL(_val, &value, 1, 1); - - // update the object - return *this; -} - -/** - * Assignment operator - * @param value - * @return Value - */ -Value &Value::operator=(const std::string &value) -{ - // if this is not a reference variable, we should detach it to implement copy on write - SEPARATE_ZVAL_IF_NOT_REF(&_val); - - // deallocate current zval (without cleaning the zval structure) - zval_dtor(_val); - - // set new value - ZVAL_STRINGL(_val, value.c_str(), value.size(), 1); - - // update the object - return *this; -} - -/** - * Assignment operator - * @param value - * @return Value - */ -Value &Value::operator=(const char *value) -{ - // if this is not a reference variable, we should detach it to implement copy on write - SEPARATE_ZVAL_IF_NOT_REF(&_val); - - // deallocate current zval (without cleaning the zval structure) - zval_dtor(_val); - - // set new value - ZVAL_STRING(_val, value, 1); - - // update the object - return *this; -} - -/** - * Assignment operator - * @param value - * @return Value - */ -Value &Value::operator=(double value) -{ - // if this is not a reference variable, we should detach it to implement copy on write - SEPARATE_ZVAL_IF_NOT_REF(&_val); - - // deallocate current zval (without cleaning the zval structure) - zval_dtor(_val); - - // set new value - ZVAL_DOUBLE(_val, value); - - // update the object - return *this; -} - -/** - * Add a value to the object - * @param value - * @return Value - */ -Value &Value::operator+=(const Value &value) { return Arithmetic(this).assign(value); } -Value &Value::operator+=(int16_t value) { return Arithmetic(this).assign(value); } -Value &Value::operator+=(int32_t value) { return Arithmetic(this).assign(value); } -Value &Value::operator+=(int64_t value) { return Arithmetic(this).assign(value); } -Value &Value::operator+=(bool value) { return Arithmetic(this).assign(value); } -Value &Value::operator+=(char value) { return Arithmetic(this).assign(value); } -Value &Value::operator+=(const std::string &value) { return Arithmetic(this).assign(value); } -Value &Value::operator+=(const char *value) { return Arithmetic(this).assign(value); } -Value &Value::operator+=(double value) { return Arithmetic(this).assign(value); } - -/** - * Subtract a value from the object - * @param value - * @return Value - */ -Value &Value::operator-=(const Value &value) { return Arithmetic(this).assign(value); } -Value &Value::operator-=(int16_t value) { return Arithmetic(this).assign(value); } -Value &Value::operator-=(int32_t value) { return Arithmetic(this).assign(value); } -Value &Value::operator-=(int64_t value) { return Arithmetic(this).assign(value); } -Value &Value::operator-=(bool value) { return Arithmetic(this).assign(value); } -Value &Value::operator-=(char value) { return Arithmetic(this).assign(value); } -Value &Value::operator-=(const std::string &value) { return Arithmetic(this).assign(value); } -Value &Value::operator-=(const char *value) { return Arithmetic(this).assign(value); } -Value &Value::operator-=(double value) { return Arithmetic(this).assign(value); } - -/** - * Multiply the object with a certain value - * @param value - * @return Value - */ -Value &Value::operator*=(const Value &value) { return Arithmetic(this).assign(value); } -Value &Value::operator*=(int16_t value) { return Arithmetic(this).assign(value); } -Value &Value::operator*=(int32_t value) { return Arithmetic(this).assign(value); } -Value &Value::operator*=(int64_t value) { return Arithmetic(this).assign(value); } -Value &Value::operator*=(bool value) { return Arithmetic(this).assign(value); } -Value &Value::operator*=(char value) { return Arithmetic(this).assign(value); } -Value &Value::operator*=(const std::string &value) { return Arithmetic(this).assign(value); } -Value &Value::operator*=(const char *value) { return Arithmetic(this).assign(value); } -Value &Value::operator*=(double value) { return Arithmetic(this).assign(value); } - -/** - * Divide the object with a certain value - * @param value - * @return Value - */ -Value &Value::operator/=(const Value &value) { return Arithmetic(this).assign(value); } -Value &Value::operator/=(int16_t value) { return Arithmetic(this).assign(value); } -Value &Value::operator/=(int32_t value) { return Arithmetic(this).assign(value); } -Value &Value::operator/=(int64_t value) { return Arithmetic(this).assign(value); } -Value &Value::operator/=(bool value) { return Arithmetic(this).assign(value); } -Value &Value::operator/=(char value) { return Arithmetic(this).assign(value); } -Value &Value::operator/=(const std::string &value) { return Arithmetic(this).assign(value); } -Value &Value::operator/=(const char *value) { return Arithmetic(this).assign(value); } -Value &Value::operator/=(double value) { return Arithmetic(this).assign(value); } - -/** - * Divide the object with a certain value and get the rest - * Note that this does not use the Arithmetic object, because no conversion between floats is necessary - * @param value - * @return Value - */ -Value &Value::operator%=(const Value &value) { return operator=(numericValue() % value.numericValue()); } -Value &Value::operator%=(int16_t value) { return operator=(numericValue() % value); } -Value &Value::operator%=(int32_t value) { return operator=(numericValue() % value); } -Value &Value::operator%=(int64_t value) { return operator=(numericValue() % value); } -Value &Value::operator%=(bool value) { return operator=(numericValue() % value); } -Value &Value::operator%=(char value) { return operator=(numericValue() % value); } -Value &Value::operator%=(const std::string &value) { return operator=(numericValue() % atoi(value.c_str())); } -Value &Value::operator%=(const char *value) { return operator=(numericValue() % atoi(value)); } -Value &Value::operator%=(double value) { return operator=(numericValue() % (int)value); } - -/** - * Assignment operator - * @param value - * @return Value - */ -Value Value::operator+(const Value &value) { return Arithmetic(this).apply(value); } -Value Value::operator+(int16_t value) { return Arithmetic(this).apply(value); } -Value Value::operator+(int32_t value) { return Arithmetic(this).apply(value); } -Value Value::operator+(int64_t value) { return Arithmetic(this).apply(value); } -Value Value::operator+(bool value) { return Arithmetic(this).apply(value); } -Value Value::operator+(char value) { return Arithmetic(this).apply(value); } -Value Value::operator+(const std::string &value) { return Arithmetic(this).apply(value); } -Value Value::operator+(const char *value) { return Arithmetic(this).apply(value); } -Value Value::operator+(double value) { return Arithmetic(this).apply(value); } - -/** - * Subtraction operator - * @param value - * @return Value - */ -Value Value::operator-(const Value &value) { return Arithmetic(this).apply(value); } -Value Value::operator-(int16_t value) { return Arithmetic(this).apply(value); } -Value Value::operator-(int32_t value) { return Arithmetic(this).apply(value); } -Value Value::operator-(int64_t value) { return Arithmetic(this).apply(value); } -Value Value::operator-(bool value) { return Arithmetic(this).apply(value); } -Value Value::operator-(char value) { return Arithmetic(this).apply(value); } -Value Value::operator-(const std::string &value) { return Arithmetic(this).apply(value); } -Value Value::operator-(const char *value) { return Arithmetic(this).apply(value); } -Value Value::operator-(double value) { return Arithmetic(this).apply(value); } - -/** - * Multiplication operator - * @param value - * @return Value - */ -Value Value::operator*(const Value &value) { return Arithmetic(this).apply(value); } -Value Value::operator*(int16_t value) { return Arithmetic(this).apply(value); } -Value Value::operator*(int32_t value) { return Arithmetic(this).apply(value); } -Value Value::operator*(int64_t value) { return Arithmetic(this).apply(value); } -Value Value::operator*(bool value) { return Arithmetic(this).apply(value); } -Value Value::operator*(char value) { return Arithmetic(this).apply(value); } -Value Value::operator*(const std::string &value) { return Arithmetic(this).apply(value); } -Value Value::operator*(const char *value) { return Arithmetic(this).apply(value); } -Value Value::operator*(double value) { return Arithmetic(this).apply(value); } - -/** - * Division operator - * @param value - * @return Value - */ -Value Value::operator/(const Value &value) { return Arithmetic(this).apply(value); } -Value Value::operator/(int16_t value) { return Arithmetic(this).apply(value); } -Value Value::operator/(int32_t value) { return Arithmetic(this).apply(value); } -Value Value::operator/(int64_t value) { return Arithmetic(this).apply(value); } -Value Value::operator/(bool value) { return Arithmetic(this).apply(value); } -Value Value::operator/(char value) { return Arithmetic(this).apply(value); } -Value Value::operator/(const std::string &value) { return Arithmetic(this).apply(value); } -Value Value::operator/(const char *value) { return Arithmetic(this).apply(value); } -Value Value::operator/(double value) { return Arithmetic(this).apply(value); } - -/** - * Modulus operator - * @param value - * @return Value - */ -Value Value::operator%(const Value &value) { return Value(numericValue() % value.numericValue()); } -Value Value::operator%(int16_t value) { return Value(numericValue() % value); } -Value Value::operator%(int32_t value) { return Value(numericValue() % value); } -Value Value::operator%(int64_t value) { return Value(numericValue() % value); } -Value Value::operator%(bool value) { return Value(numericValue() % value); } -Value Value::operator%(char value) { return Value(numericValue() % value); } -Value Value::operator%(const std::string &value) { return Value(numericValue() % atoi(value.c_str())); } -Value Value::operator%(const char *value) { return Value(numericValue() % atoi(value)); } -Value Value::operator%(double value) { return Value(numericValue() % (int)value); } - -/** - * Call the function in PHP - * We have ten variants of this function, depending on the number of parameters - * This call operator is only useful when the variable represents a callable - * @param p0-p10 Parameters of the function to be called. - * @return Value - */ -Value Value::operator()() const -{ - // call with zero parameters - return exec(0, NULL); -} - -/** - * Call the function - if the variable holds a callable thing - * @param p0 The first parameter - * @return Value - */ -Value Value::operator()(Value p0) const -{ - // array of parameters - zval **params[1] = { &p0._val }; - - // call the function - return exec(1, params); -} - -/** - * Call the function - if the variable holds a callable thing - * @param p0 The first parameter - * @param p1 The second parameter - * @return Value - */ -Value Value::operator()(Value p0, Value p1) const -{ - // array of parameters - zval **params[2] = { &p0._val, &p1._val }; - - // call the function - return exec(2, params); -} - -/** - * Call the function - if the variable holds a callable thing - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @return Value - */ -Value Value::operator()(Value p0, Value p1, Value p2) const -{ - // array of parameters - zval **params[3] = { &p0._val, &p1._val, &p2._val }; - - // call the function - return exec(3, params); -} - -/** - * Call the function - if the variable holds a callable thing - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @param p3 The fourth parameter - * @return Value - */ -Value Value::operator()(Value p0, Value p1, Value p2, Value p3) const -{ - // array of parameters - zval **params[4] = { &p0._val, &p1._val, &p2._val, &p3._val }; - - // call the function - return exec(4, params); -} - -/** - * Call the function - if the variable holds a callable thing - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @param p3 The fourth parameter - * @param p4 The fifth parameter - * @return Value - */ -Value Value::operator()(Value p0, Value p1, Value p2, Value p3, Value p4) const -{ - // array of parameters - zval **params[5] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val }; - - // call the function - return exec(5, params); -} - -/** - * Call the function - if the variable holds a callable thing - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @param p3 The fourth parameter - * @param p4 The fifth parameter - * @param p5 The sixth parameter - * @return Value - */ -Value Value::operator()(Value p0, Value p1, Value p2, Value p3, Value p4, Value p5) const -{ - // array of parameters - zval **params[6] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val }; - - // call the function - return exec(6, params); -} - -/** - * Call the function - if the variable holds a callable thing - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @param p3 The fourth parameter - * @param p4 The fifth parameter - * @param p5 The sixth parameter - * @param p6 The seventh parameter - * @return Value - */ -Value Value::operator()(Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6) const -{ - // array of parameters - zval **params[7] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val }; - - // call the function - return exec(7, params); -} - -/** - * Call the function - if the variable holds a callable thing - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @param p3 The fourth parameter - * @param p4 The fifth parameter - * @param p5 The sixth parameter - * @param p6 The seventh parameter - * @param p7 The eighth parameter - * @return Value - */ -Value Value::operator()(Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7) const -{ - // array of parameters - zval **params[8] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val, &p7._val }; - - // call the function - return exec(8, params); -} - -/** - * Call the function - if the variable holds a callable thing - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @param p3 The fourth parameter - * @param p4 The fifth parameter - * @param p5 The sixth parameter - * @param p6 The seventh parameter - * @param p7 The eighth parameter - * @param p8 The ninth parameter - * @return Value - */ -Value Value::operator()(Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7, Value p8) const -{ - // array of parameters - zval **params[9] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val, &p7._val, &p8._val }; - - // call the function - return exec(9, params); -} - -/** - * Call the function - if the variable holds a callable thing - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @param p3 The fourth parameter - * @param p4 The fifth parameter - * @param p5 The sixth parameter - * @param p6 The seventh parameter - * @param p7 The eighth parameter - * @param p8 The ninth parameter - * @param p9 The tenth parameter - * @return Value - */ -Value Value::operator()(Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7, Value p8, Value p9) const -{ - // array of parameters - zval **params[10] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val, &p7._val, &p8._val, &p9._val }; - - // call the function - return exec(10, params); -} - -/** - * Call the method - if the variable holds an object with the given method - * @param name name of the method to call - * @return Value - */ -Value Value::call(const char *name) -{ - // call with zero parameters - return exec(name, 0, NULL); -} - -/** - * Call the method - if the variable holds an object with the given method - * @param name name of the method to call - * @param p0 The first parameter - * @return Value - */ -Value Value::call(const char *name, Value p0) -{ - // array of parameters - zval **params[] = { &p0._val }; - - // call with zero parameters - return exec(name, 1, params); -} - -/** - * Call the method - if the variable holds an object with the given method - * @param name name of the method to call - * @param p0 The first parameter - * @param p1 The second parameter - * @return Value - */ -Value Value::call(const char *name, Value p0, Value p1) -{ - // array of parameters - zval **params[] = { &p0._val, &p1._val }; - - // call with zero parameters - return exec(name, 2, params); -} - -/** - * Call the method - if the variable holds an object with the given method - * @param name name of the method to call - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @return Value - */ -Value Value::call(const char *name, Value p0, Value p1, Value p2) -{ - // array of parameters - zval **params[] = { &p0._val, &p1._val, &p2._val }; - - // call with zero parameters - return exec(name, 3, params); -} - -/** - * Call the method - if the variable holds an object with the given method - * @param name name of the method to call - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @param p3 The fourth parameter - * @return Value - */ -Value Value::call(const char *name, Value p0, Value p1, Value p2, Value p3) -{ - // array of parameters - zval **params[] = { &p0._val, &p1._val, &p2._val, &p3._val }; - - // call with zero parameters - return exec(name, 4, params); -} - -/** - * Call the method - if the variable holds an object with the given method - * @param name name of the method to call - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @param p3 The fourth parameter - * @param p4 The fifth parameter - * @return Value - */ -Value Value::call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4) -{ - // array of parameters - zval **params[] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val }; - - // call with zero parameters - return exec(name, 5, params); -} - -/** - * Call the method - if the variable holds an object with the given method - * @param name name of the method to call - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @param p3 The fourth parameter - * @param p4 The fifth parameter - * @param p5 The sixth parameter - * @return Value - */ -Value Value::call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5) -{ - // array of parameters - zval **params[] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val }; - - // call with zero parameters - return exec(name, 6, params); -} - -/** - * Call the method - if the variable holds an object with the given method - * @param name name of the method to call - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @param p3 The fourth parameter - * @param p4 The fifth parameter - * @param p5 The sixth parameter - * @param p6 The seventh parameter - * @return Value - */ -Value Value::call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6) -{ - // array of parameters - zval **params[] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val }; - - // call with zero parameters - return exec(name, 7, params); -} - -/** - * Call the method - if the variable holds an object with the given method - * @param name name of the method to call - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @param p3 The fourth parameter - * @param p4 The fifth parameter - * @param p5 The sixth parameter - * @param p6 The seventh parameter - * @param p7 The eighth parameter - * @return Value - */ -Value Value::call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7) -{ - // array of parameters - zval **params[] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val, &p7._val }; - - // call with zero parameters - return exec(name, 8, params); -} - -/** - * Call the method - if the variable holds an object with the given method - * @param name name of the method to call - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @param p3 The fourth parameter - * @param p4 The fifth parameter - * @param p5 The sixth parameter - * @param p6 The seventh parameter - * @param p7 The eighth parameter - * @param p8 The ninth parameter - * @return Value - */ -Value Value::call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7, Value p8) -{ - // array of parameters - zval **params[] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val, &p7._val, &p8._val }; - - // call with zero parameters - return exec(name, 9, params); -} - -/** - * Call the method - if the variable holds an object with the given method - * @param name name of the method to call - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @param p3 The fourth parameter - * @param p4 The fifth parameter - * @param p5 The sixth parameter - * @param p6 The seventh parameter - * @param p7 The eighth parameter - * @param p8 The ninth parameter - * @param p9 The tenth parameter - * @return Value - */ -Value Value::call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7, Value p8, Value p9) -{ - // array of parameters - zval **params[] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val, &p7._val, &p8._val, &p9._val }; - - // call with zero parameters - return exec(name, 10, params); -} - -/** - * Helper function that runs the actual call - * @param object The object to call it on - * @param method The function or method to call - * @param args Number of arguments - * @param params The parameters - * @return Value - */ -static Value do_exec(zval **object, zval *method, int argc, zval ***params) -{ - // the return zval - zval *retval = nullptr; - - // we need the tsrm_ls variable - TSRMLS_FETCH(); - - // the current exception - zval *oldException = EG(exception); - - // call the function - if (call_user_function_ex(CG(function_table), object, method, &retval, argc, params, 1, NULL TSRMLS_CC) != SUCCESS) - { - // throw an exception, the function does not exist - throw Exception("Invalid call to "+Value(method).stringValue()); - - // unreachable, but let's return at least something to prevent compiler warnings - return nullptr; - } - else - { - // was an exception thrown inside the function? In that case we throw a C++ new exception - // to give the C++ code the chance to catch it - if (oldException != EG(exception) && EG(exception)) throw OrigException(EG(exception) TSRMLS_CC); - - // no (additional) exception was thrown - return retval ? Value(retval) : nullptr; - } -} - -/** - * Call function with a number of parameters - * @param argc Number of parameters - * @param argv The parameters - * @return Value - */ -Value Value::exec(int argc, zval ***params) const -{ - // call helper function - return do_exec(nullptr, _val, argc, params); -} - -/** - * Call method with a number of parameters - * @param name Name of method to call - * @param argc Number of parameters - * @param argv The parameters - * @return Value - */ -Value Value::exec(const char *name, int argc, struct _zval_struct ***params) -{ - // wrap the name in a Php::Value object to get a zval - Value method(name); - - // call helper function - return do_exec(&_val, method._val, argc, params); -} - -/** - * The type of object - * @return Type - */ -Type Value::type() const -{ - // return regular type - return (Type)Z_TYPE_P(_val); -} - -/** - * Change the internal type - * @param type - * @return Value - */ -Value &Value::setType(Type type) -{ - // skip if nothing changes - if (this->type() == type) return *this; - - // if this is not a reference variable, we should detach it to implement copy on write - SEPARATE_ZVAL_IF_NOT_REF(&_val); - - // run the conversion - switch (type) { - case Type::Null: convert_to_null(_val); break; - case Type::Numeric: convert_to_long(_val); break; - case Type::Float: convert_to_double(_val); break; - case Type::Bool: convert_to_boolean(_val); break; - case Type::Array: convert_to_array(_val); break; - case Type::Object: convert_to_object(_val); break; - case Type::String: convert_to_string(_val); break; - case Type::Resource: throw Php::Exception("Resource types can not be handled by the PHP-CPP library"); break; - case Type::Constant: throw Php::Exception("Constant types can not be assigned to a PHP-CPP library variable"); break; - case Type::ConstantArray: throw Php::Exception("Constant types can not be assigned to a PHP-CPP library variable"); break; - case Type::Callable: throw Php::Exception("Callable types can not be assigned to a PHP-CPP library variable"); break; - } - - // done - return *this; -} - -/** - * Check if the variable holds something that is callable - * @return bool - */ -bool Value::isCallable() const -{ - // we need the tsrm_ls variable - TSRMLS_FETCH(); - - // we can not rely on the type, because strings can be callable as well - return zend_is_callable(_val, 0, NULL TSRMLS_CC); -} - -/** - * Make a clone of the type - * @return Value - */ -Value Value::clone() const -{ - // the zval that will hold the copy - zval *copy; - - // allocate memory - ALLOC_ZVAL(copy); - - // copy the data - INIT_PZVAL_COPY(copy, _val); - - // run the copy constructor to ensure that everything gets copied - zval_copy_ctor(copy); - - // done - return Value(copy); -} - -/** - * Clone the zval to a different type - * @param type - * @return Value - */ -Value Value::clone(Type type) const -{ - // regular clone if nothing changes - if (this->type() == type) return clone(); - - // make a clone - return clone().setType(type); -} - -/** - * Retrieve the value as integer - * @return long - */ -int64_t Value::numericValue() const -{ - // already a long? - if (isNumeric()) return Z_LVAL_P(_val); - - // make a clone - return clone(Type::Numeric).numericValue(); -} - -/** - * Retrieve the value as boolean - * @return bool - */ -bool Value::boolValue() const -{ - // already a bool? - if (isBool()) return Z_BVAL_P(_val); - - // make a clone - return clone(Type::Bool).boolValue(); -} - -/** - * Retrieve the value as string - * @return string - */ -std::string Value::stringValue() const -{ - // already a string? - if (isString()) return std::string(Z_STRVAL_P(_val), Z_STRLEN_P(_val)); - - // make a clone - return clone(Type::String).stringValue(); -} - -/** - * Access to the raw buffer - * @return char * - */ -char *Value::buffer() const -{ - // must be a string - if (!isString()) return nullptr; - - // already a string? - return Z_STRVAL_P(_val); -} - -/** - * Reserve enough space - * @param size - * @return char* - */ -char *Value::reserve(size_t size) -{ - // must be a string - setType(Type::String); - - // is the current buffer too small? - if (Z_STRLEN_P(_val) < (int)size) - { - // is there already a buffer? - if (!Z_STRVAL_P(_val)) Z_STRVAL_P(_val) = (char *)emalloc(size+1); - - // reallocate an existing buffer - else Z_STRVAL_P(_val) = (char *)erealloc(Z_STRVAL_P(_val), size+1); - - // last byte should be zero - Z_STRVAL_P(_val)[size] = 0; - } - - // store size - Z_STRLEN_P(_val) = size; - - // done - return Z_STRVAL_P(_val); -} - -/** - * Access to the raw buffer - * @return const char * - */ -const char *Value::rawValue() const -{ - // must be a string - if (isString()) return Z_STRVAL_P(_val); - - // make a clone and return that buffer - return clone(Type::String).rawValue(); -} - -/** - * Retrieve the value as decimal - * @return double - */ -double Value::floatValue() const -{ - // already a double - if (isFloat()) return Z_DVAL_P(_val); - - // make a clone - return clone(Type::Float).floatValue(); -} - -/** - * The number of members in case of an array or object - * @return int - */ -int Value::size() const -{ - // is it an array? - if (isArray()) - { - // get the number of elements - return zend_hash_num_elements(Z_ARRVAL_P(_val)); - } - - // or an object? - else if (isObject()) - { - // the count_elements member function should be defined - if (!Z_OBJ_HT_P(_val)->count_elements) return 0; - - // create a variable to hold the result - long result; - - // we need the tsrm_ls variable - TSRMLS_FETCH(); - - // call the function - return Z_OBJ_HT_P(_val)->count_elements(_val, &result TSRMLS_CC) == SUCCESS ? result : 0; - } - - // not an array, return string size if this is a string - else if (isString()) - { - // get string size - return Z_STRLEN_P(_val); - } - - // in all other situations, we convert the variable to a string - else - { - // make a copy - Value copy(*this); - - // convert the copy to a string - copy.setType(Type::String); - - // return the string size - return copy.size(); - } -} - -/** - * Convert the object to a map with string index and Php::Value value - * @return std::map - */ -std::map Value::mapValue() const -{ - // result variable - std::map result; - - // iterate over the object - for (auto &iter : *this) result[iter.first.rawValue()] = iter.second; - - // done - return result; -} - -/** - * Internal helper method to retrieve an iterator - * @param begin Should the iterator start at the begin - * @return iterator - */ -ValueIterator Value::createIterator(bool begin) const -{ - // check type - if (isArray()) return ValueIterator(new HashIterator(Z_ARRVAL_P(_val), begin)); - - // get access to the hast table - if (isObject()) - { - // we need the TSRMLS_CC variable - TSRMLS_FETCH(); - - // is a special iterator method defined in the class entry? - auto *entry = zend_get_class_entry(_val TSRMLS_CC); - - // check if there is an iterator - if (entry->get_iterator) - { - // the object implements Traversable interface, we have to use a - // special iterator to user that interface too - return ValueIterator(new TraverseIterator(_val, begin TSRMLS_CC)); - } - else - { - // construct a regular iterator - return ValueIterator(new HashIterator(Z_OBJ_HT_P(_val)->get_properties(_val TSRMLS_CC), begin)); - } - } - - // invalid - return ValueIterator(new InvalidIterator()); -} - -/** - * Return an iterator for iterating over the values - * This is only meaningful for Value objects that hold an array or an object - * @return iterator - */ -ValueIterator Value::begin() const -{ - return createIterator(true); -} - -/** - * Return an iterator for iterating over the values - * This is only meaningful for Value objects that hold an array or an object - * @return iterator - */ -ValueIterator Value::end() const -{ - return createIterator(false); -} - -/** - * Does the array contain a certain index? - * @param index - * @return bool - */ -bool Value::contains(int index) const -{ - // must be an array - if (!isArray()) return false; - - // unused variable - zval **result; - - // check if this index is already in the array - return zend_hash_index_find(Z_ARRVAL_P(_val), index, (void**)&result) != FAILURE; -} - -/** - * Does the array contain a certain key - * @param key - * @param size - * @return boolean - */ -bool Value::contains(const char *key, int size) const -{ - // calculate size - if (size < 0) size = strlen(key); - - // deal with arrays - if (isArray()) - { - // unused variable - zval **result; - - // check if index is already in the array - return zend_hash_find(Z_ARRVAL_P(_val), key, size+1, (void **)&result) != FAILURE; - } - else if (isObject()) - { - // we need the tsrmls_cc variable - TSRMLS_FETCH(); - - // retrieve the class entry - auto *entry = zend_get_class_entry(_val TSRMLS_CC); - - // read the property (cast necessary for php 5.3) - zval *property = zend_read_property(entry, _val, (char *)key, size, 0 TSRMLS_CC); - - // check if valid - return property != nullptr; - } - else - { - // scalar variable - return false; - } -} - -/** - * Get access to a certain array member - * @param index - * @return Value - */ -Value Value::get(int index) const -{ - // must be an array - if (!isArray()) return Value(); - - // zval to retrieve - zval **result; - - // check if index is in the array - if (zend_hash_index_find(Z_ARRVAL_P(_val), index, (void **)&result) == FAILURE) return Value(); - - // wrap the value - return Value(*result); -} - -/** - * Get access to a certain assoc member - * @param key - * @param size - * @return Value - */ -Value Value::get(const char *key, int size) const -{ - // must be an array - if (!isArray() && !isObject()) return Value(); - - // calculate size - if (size < 0) size = strlen(key); - - // are we in an object or an array? - if (isArray()) - { - // the result value - zval **result; - - // check if this index is already in the array, otherwise we return NULL - if (zend_hash_find(Z_ARRVAL_P(_val), key, size + 1, (void **)&result) == FAILURE) return Value(); - - // wrap the value - return Value(*result); - } - else - { - // we need the tsrm_ls variable - TSRMLS_FETCH(); - - // retrieve the class entry - auto *entry = zend_get_class_entry(_val TSRMLS_CC); - - // read the property (case necessary for php 5.3) - zval *property = zend_read_property(entry, _val, (char *)key, size, 1 TSRMLS_CC); - - // wrap in value - return Value(property); - } -} - -/** - * Set a certain property without performing any checks - * This method can be used when it is already known that the object is an array - * @param index - * @param value - * @return Value - */ -void Value::setRaw(int index, const Value &value) -{ - // if this is not a reference variable, we should detach it to implement copy on write - SEPARATE_ZVAL_IF_NOT_REF(&_val); - - // add the value (this will decrement refcount on any current variable) - add_index_zval(_val, index, value._val); - - // the variable has one more reference (the array entry) - Z_ADDREF_P(value._val); -} - -/** - * Set a certain property - * @param index - * @param value - * @return Value - */ -void Value::set(int index, const Value &value) -{ - // the current value - zval **current; - - // check if this index is already in the array, otherwise we return NULL - if (isArray() && zend_hash_index_find(Z_ARRVAL_P(_val), index, (void **)¤t) != FAILURE) - { - // skip if nothing is going to change - if (value._val == *current) return; - } - - // must be an array - setType(Type::Array); - - // set property - setRaw(index, value); -} - -/** - * Set a certain property without running any checks - * @param key - * @param size - * @param value - */ -void Value::setRaw(const char *key, int size, const Value &value) -{ - // is this an object? - if (isObject()) - { - // if this is not a reference variable, we should detach it to implement copy on write - SEPARATE_ZVAL_IF_NOT_REF(&_val); - - // we need the tsrm_ls variable - TSRMLS_FETCH(); - - // retrieve the class entry - auto *entry = zend_get_class_entry(_val TSRMLS_CC); - - // update the property (cast necessary for php 5.3) - zend_update_property(entry, _val, (char *)key, size, value._val TSRMLS_CC); - } - else - { - // if this is not a reference variable, we should detach it to implement copy on write - SEPARATE_ZVAL_IF_NOT_REF(&_val); - - // add the value (this will reduce the refcount of the current value) - add_assoc_zval_ex(_val, key, size+1, value._val); - - // the variable has one more reference (the array entry) - Z_ADDREF_P(value._val); - } -} - -/** - * Set a certain property - * @param key - * @param size - * @param value - * @return Value - */ -void Value::set(const char *key, int size, const Value &value) -{ - // the current value - zval **current; - - // check if this index is already in the array, otherwise we return NULL - if (isArray() && zend_hash_find(Z_ARRVAL_P(_val), key, size + 1, (void **)¤t) != FAILURE) - { - // skip if nothing is going to change - if (value._val == *current) return; - } - - // this should be an object or an array - if (!isObject()) setType(Type::Array); - - // done - setRaw(key, size, value); -} - -/** - * Array access operator - * This can be used for accessing arrays - * @param index - * @return HashMember - */ -HashMember Value::operator[](int index) -{ - return HashMember(this, index); -} - -/** - * Array access operato - * This can be used for accessing associative arrays - * @param key - * @return HashMember - */ -HashMember Value::operator[](const std::string &key) -{ - return HashMember(this, key); -} - -/** - * Array access operator - * This can be used for accessing associative arrays - * @param key - * @return HashMember - */ -HashMember Value::operator[](const char *key) -{ - return HashMember(this, key); -} - -/** - * Retrieve the original implementation - * - * This only works for classes that were implemented using PHP-CPP, - * it returns nullptr for all other classes - * - * @return Base* - */ -Base *Value::implementation() const -{ - // must be an object - if (!isObject()) return nullptr; - - // we need the tsrm_ls variable - TSRMLS_FETCH(); - - // retrieve the mixed object that contains the base - return ObjectImpl::find(_val TSRMLS_CC)->object(); -} - -/** - * Custom output stream operator - * @param stream - * @param value - * @return ostream - */ -std::ostream &operator<<(std::ostream &stream, const Value &value) -{ - return stream << value.stringValue(); -} - -/** - * End of namespace - */ -} - diff --git a/src/valueiterator.cpp b/src/valueiterator.cpp deleted file mode 100644 index 65c687c..0000000 --- a/src/valueiterator.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/** - * ValueIterator.cpp - * - * Implementation of the value iterator - * - * @author Emiel Bruijntjes - * @copyright 2014 Copernica BV - */ -#include "includes.h" - -/** - * Set up namespace - */ -namespace Php { - -/** - * Copy constructor - * @param that - * @param tsrm_ls - */ -ValueIterator::ValueIterator(const ValueIterator &that) : _impl(that._impl->clone()) {} - -/** - * Destructor - */ -ValueIterator::~ValueIterator() -{ - // get rid of implementation - delete _impl; -} - -/** - * Increment position - * @return ValueIterator - */ -ValueIterator &ValueIterator::operator++() -{ - // increment implementation - _impl->increment(); - - // done - return *this; -} - -/** - * Decrement position - * @return ValueIterator - */ -ValueIterator &ValueIterator::operator--() -{ - // decrement implementation - _impl->decrement(); - - // done - return *this; -} - -/** - * Compare with other iterator - * @param that - * @return bool - */ -bool ValueIterator::operator==(const ValueIterator &that) const -{ - return _impl->equals(that._impl); -} - -/** - * Compare with other iterator - * @param that - * @return bool - */ -bool ValueIterator::operator!=(const ValueIterator &that) const -{ - return !_impl->equals(that._impl); -} - -/** - * Derefecence, this returns a std::pair with the current key and value - * @return std::pair - */ -const std::pair &ValueIterator::operator*() const -{ - return _impl->current(); -} - -/** - * Dereference, this returns a std::pair with the current key and value - * @return std::pair - */ -const std::pair *ValueIterator::operator->() const -{ - return &_impl->current(); -} - -/** - * End namespace - */ -} - diff --git a/src/valueiteratorimpl.h b/src/valueiteratorimpl.h deleted file mode 100644 index 82c888d..0000000 --- a/src/valueiteratorimpl.h +++ /dev/null @@ -1,71 +0,0 @@ -/** - * ValueIteratorImpl.h - * - * Interface that describes what an implementation of a value iterator should - * look like. This is an internal class that extension developers do not - * need. It is used internally inside the ValueIterator class. - * - * @author Emiel Bruijntjes - * @copyright 2014 Copernica BV - */ - -/** - * Set up namespace - */ -namespace Php { - -/** - * Class definition - */ -class ValueIteratorImpl -{ -public: - /** - * Constructor - */ - ValueIteratorImpl() {} - - /** - * Destructor - */ - virtual ~ValueIteratorImpl() {} - - /** - * Clone the object - * @param tsrm_ls - * @return ValueIteratorImpl* - */ - virtual ValueIteratorImpl *clone() = 0; - - /** - * Increment position (pre-increment) - * @param tsrm_ls - * @return bool - */ - virtual bool increment() = 0; - - /** - * Decrement position (pre-decrement) - * @return bool - */ - virtual bool decrement() = 0; - - /** - * Compare with other iterator - * @param that - * @return bool - */ - virtual bool equals(const ValueIteratorImpl *that) const = 0; - - /** - * Derefecence, this returns a std::pair with the current key and value - * @return std::pair - */ - virtual const std::pair ¤t() const = 0; -}; - -/** - * End namespace - */ -} - diff --git a/zend/arithmetic.h b/zend/arithmetic.h new file mode 100644 index 0000000..20e346e --- /dev/null +++ b/zend/arithmetic.h @@ -0,0 +1,286 @@ +/** + * Arithmethic.h + * + * Helper class that takes care of arithmetic operations on PHP variables + * + * @author Emiel Bruijntjes + * @copyright 2013 Copernica BV + */ + +/** + * Set up namespace + */ +namespace Php { + +/** + * Class definition + */ +template < template class F> +class Arithmetic +{ +public: + /** + * Constructor + * @param value The original object + */ + Arithmetic(Value *value) : _value(value) {} + + /** + * Destructor + */ + virtual ~Arithmetic() {} + + /** + * Apply a number, and return a new value object after running the arithmetic function + * @param value + * @return Value + */ + Value apply(const Value &value) + { + // is this a type a floating point type? + if (value.isFloat()) return apply(value.floatValue()); + + // we are going to treat it as a numeric (non floating) type + return apply(value.numericValue()); + } + + /** + * Apply a number, and return a new value object after running the arithmetic function + * @param value + * @return Value + */ + Value apply(int16_t value) + { + // check if the current object is a floating point number + if (_value->isFloat()) return Value(F()(_value->floatValue(), value)); + + // apply to natural numbers + return Value(F()(_value->numericValue(), value)); + } + + /** + * Apply a number, and return a new value object after running the arithmetic function + * @param value + * @return Value + */ + Value apply(int32_t value) + { + // check if the current object is a floating point number + if (_value->isFloat()) return Value(F()(_value->floatValue(), value)); + + // apply to natural numbers + return Value(F()(_value->numericValue(), value)); + } + + /** + * Apply a number, and return a new value object after running the arithmetic function + * @param value + * @return Value + */ + Value apply(int64_t value) + { + // check if the current object is a floating point number + if (_value->isFloat()) return Value(F()(_value->floatValue(), value)); + + // apply to natural numbers + return Value(F()(_value->numericValue(), value)); + } + + /** + * Apply a boolean (treat is as 0 or 1), and return a new value object after running the arithmetic function + * @param value + * @return Value + */ + Value apply(bool value) + { + // check if the current object is a floating point number + if (_value->isFloat()) return Value(F()(_value->floatValue(), value?1:0)); + + // apply to natural numbers + return Value(F()(_value->numericValue(), value?1:0)); + } + + /** + * Apply a character (value between '0' and '9'), and return a new value object after running the arithmetic function + * @param value + * @return Value + */ + Value apply(char value) + { + // convert to an integer + int v = value < '0' || value > '9' ? 0 : value - '0'; + + // check if the current object is a floating point number + if (_value->isFloat()) return Value(F()(_value->floatValue(), v)); + + // apply to natural numbers + return Value(F()(_value->numericValue(), v)); + } + + /** + * Apply a string (representing a number), and return a new value object after running the arithmetic function + * @param value + * @return Value + */ + Value apply(const std::string &value) + { + // convert string to integer + return apply(atoi(value.c_str())); + } + + /** + * Apply a string (representing a number), and return a new value object after running the arithmetic function + * @param value + * @return Value + */ + Value apply(const char *value) + { + // convert string to integer + return apply(atoi(value)); + } + + /** + * Apply a string (representing a number), and return a new value object after running the arithmetic function + * @param value + * @return Value + */ + Value apply(double value) + { + return Value(F()(_value->floatValue(), value)); + } + + /** + * Assign a different value object, applying the arithmetic operation + * @param value + * @return Value + */ + Value &assign(const Value &value) + { + // is this a type a floating point type? + if (value.isFloat()) return assign(value.floatValue()); + + // we are going to treat it as a numeric (non floating) type + return assign(value.numericValue()); + } + + /** + * Assign a 16bit number, applying the arithmetic operation + * @param value + * @return Value + */ + Value &assign(int16_t value) + { + // is the current object a floating point type? + if (_value->isFloat()) return _value->operator=(F()(_value->floatValue(), value)); + + // do a numeric operation + return _value->operator=(F()(_value->numericValue(), value)); + } + + /** + * Assign 32bit integer, applying the arithmetic operation + * @param value + * @return Value + */ + Value &assign(int32_t value) + { + // is the current object a floating point type? + if (_value->isFloat()) return _value->operator=(F()(_value->floatValue(), value)); + + // do a numeric operation + return _value->operator=(F()(_value->numericValue(), value)); + } + + /** + * Assign 64bit integer, applying the arithmetic operation + * @param value + * @return Value + */ + Value &assign(int64_t value) + { + // is the current object a floating point type? + if (_value->isFloat()) return _value->operator=(F()(_value->floatValue(), value)); + + // do a numeric operation + return _value->operator=(F()(_value->numericValue(), value)); + } + + /** + * Assign 64bit integer - which is treated as 1 or 0 - applying the arithmetic operation + * @param value + * @return Value + */ + Value &assign(bool value) + { + // is the current object a floating point type? + if (_value->isFloat()) return _value->operator=(F()(_value->floatValue(), value?1:0)); + + // do a numeric operation + return _value->operator=(F()(_value->numericValue(), value?1:0)); + } + + /** + * Assign a single character - which is treated as an int, and applying the arithmetic function + * @param value + * @return Value + */ + Value &assign(char value) + { + // convert to an integer + int v = value < '0' || value > '9' ? 0 : value - '0'; + + // is the current object a floating point type? + if (_value->isFloat()) return _value->operator=(F()(_value->floatValue(), v)); + + // do a numeric operation + return _value->operator=(F()(_value->numericValue(), v)); + } + + /** + * Assign a a string - treating it as an integer, and applying the arithmetic function + * @param value + * @return Value + */ + Value &assign(const std::string &value) + { + // assign integer + return assign(atoi(value.c_str())); + } + + /** + * Assign a string - treating it as an integer, and applying the arithmetic function + * @param value + * @return Value + */ + Value &assign(const char *value) + { + // assign integer + return assign(atoi(value)); + } + + /** + * Assign a double, applying the arithmetic operation + * @param value + * @return Value + */ + Value &assign(double value) + { + // do float operation + return _value->operator=(F()(_value->floatValue(), value)); + } + +private: + /** + * Pointer to the original value object + * @var Value + */ + Value *_value; + + +}; + +/** + * End of namespace + */ +} + diff --git a/zend/base.cpp b/zend/base.cpp new file mode 100644 index 0000000..5d15011 --- /dev/null +++ b/zend/base.cpp @@ -0,0 +1,287 @@ +/** + * Base.cpp + * + * Implementation file for the base of all classes + * + * @copyright 2014 Copernica BV + */ +#include "includes.h" + +/** + * Set up namespace + */ +namespace Php { + +/** + * Store the object in the PHP object cache + * @param entry Class entry + * @param tsrm_ls + * @return MixedObject + */ +//MixedObject *Base::store(zend_class_entry *entry TSRMLS_DC) +//{ +// // allocate memory for the object +// MixedObject *result = (MixedObject *)emalloc(sizeof(MixedObject)); +// +// // store the new c++ object +// result->cpp = this; +// +// // store the class entry in the newly created object +// result->php.ce = entry; +// +// // initialize the object +// zend_object_std_init(&result->php, entry TSRMLS_CC); +// +//#if PHP_VERSION_ID < 50399 +// +// // tmp variable +// zval *tmp; +// +// // initialize the properties, php 5.3 way +// zend_hash_copy(result->php.properties, &entry->default_properties, (copy_ctor_func_t) zval_property_ctor, &tmp, sizeof(zval*)); +// +//#else +// +// // version higher than 5.3 have an easier way to initialize +// object_properties_init(&result->php, entry); +// +//#endif +// +//#ifdef ZTS +// +// // when in thread safety mode, the destruct method and free method have +// // an extra parameter holding thread information +// using DestructType = void(zend_object*,unsigned int,void***); +// using FreeType = void(zend_object*,void***); +// +//#else +// +// // not in thread mode: no special parameter for the tsrm_ls variable +// using DestructType = void(zend_object*,unsigned int); +// using FreeType = void(zend_object*); +// +//#endif +// +// // store the two destruct methods in temporary vars +// 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... +// _handle = zend_objects_store_put(result, (zend_objects_store_dtor_t)destructMethod, (zend_objects_free_object_storage_t)freeMethod, NULL TSRMLS_CC); +// +// // done +// return result; +//} + +/** + * Overridable method that is called right before an object is destructed + */ +void Base::__destruct() const +{ + // throw exception, so that the PHP-CPP library will check if the user + // somehow registered an explicit __destruct method + throw NotImplemented(); +} + +/** + * Overridable method that is called to check if a property is set + * + * The default implementation does nothing, and the script will fall back + * to accessing the regular object properties + * + * @param key + * @return bool + */ +bool Base::__isset(const Php::Value &key) const +{ + // throw an exception that will be caught in the ClassBase class, + // so that the default implementation of the unset function can be called + throw NotImplemented(); +} + +/** + * Overridable method that is called to set a new property + * + * The default implementation does nothing, and the script will fall back + * to accessing the regular object properties + * + * @param key + * @param value + */ +void Base::__set(const Php::Value &key, const Php::Value &value) const +{ + // throw an exception that will be caught in the ClassBase class, + // so that the default implementation of the unset function can be called + throw NotImplemented(); +} + +/** + * Retrieve a property + * + * The default implementation does nothing, and the script will fall back + * to accessing the regular object properties + * + * @param key + * @return value + */ +Php::Value Base::__get(const Php::Value &key) const +{ + // throw an exception that will be caught in the ClassBase class, + // so that the default implementation of the function can be called + throw NotImplemented(); + + // unreachable code + return nullptr; +} + +/** + * Remove a member + * + * The default implementation does nothing, and the script will fall back + * to accessing the regular object properties + * + * @param key + */ +void Base::__unset(const Php::Value &key) const +{ + // throw an exception that will be caught in the ClassBase class, + // so that the default implementation of the function can be called + throw NotImplemented(); +} + +/** + * Call a method + * + * This method is called when a method is called from the PHP script that + * was not explicitly defined. You can use this to catch variable method + * names, or to support all thinkable method names. + * + * @param method Name of the method that was called + * @param params The parameters that were passed to the function + * @return Value The return value + */ +Value Base::__call(const char *method, Parameters ¶ms) const +{ + // throw an exception that will be caught in the ClassBase class, + // so that the default implementation of the function can be called + throw NotImplemented(); + + // unreachable code + return nullptr; +} + +/** + * Call the class as if it was a function + * + * This method is called when a an object is used with () operators: + * $object(). You can override this method to make objects callable. + * + * @param params The parameters that were passed to the function + * @return Value The return value + */ +Value Base::__invoke(Parameters ¶ms) const +{ + // throw an exception that will be caught in the ClassBase class, + // so that the default implementation of the function can be called + throw NotImplemented(); + + // unreachable code + return nullptr; +} + +/** + * Cast the object to a string + * + * This method is called when an object is casted to a string, or when + * it is used in a string context + * + * @return Value The object as a string + */ +Value Base::__toString() const +{ + // throw an exception that will be caught in the ClassBase class, + // so that the default implementation of the function can be called + throw NotImplemented(); + + // unreachable code + return nullptr; +} + +/** + * Cast the object to an integer + * + * This method is called when an object is casted to an integer, or when + * it is used in an integer context + * + * @return int Integer value + */ +Value Base::__toInteger() const +{ + // throw an exception that will be caught in the ClassBase class, + // so that the default implementation of the function can be called + throw NotImplemented(); + + // unreachable code + return 0; +} + +/** + * Cast the object to a float + * + * This method is called when an object is casted to a float, or when it + * is used in a float context + * + * @return double Floating point value + */ +Value Base::__toFloat() const +{ + // throw an exception that will be caught in the ClassBase class, + // so that the default implementation of the function can be called + throw NotImplemented(); + + // unreachable code + return 0.0; +} + +/** + * Cast the object to a boolean + * + * This method is called when an object is casted to a bool, or when it + * is used in a boolean context + * + * @return bool + */ +Value Base::__toBool() const +{ + // throw an exception that will be caught in the ClassBase class, + // so that the default implementation of the function can be called + throw NotImplemented(); + + // unreachable code + return false; +} + +/** + * Compare the object with a different object + * + * Check how a different object compares to this object + * + * @param that Object to compare with + * @return int + */ +int Base::__compare(const Base &that) const +{ + // throw an exception that will be caught in the ClassBase class, + // so that the default implementation of the function can be called + throw NotImplemented(); + + // unreachable code + return 1; +} + +/** + * End namespace + */ +} + diff --git a/zend/boolmember.h b/zend/boolmember.h new file mode 100644 index 0000000..5b5d43d --- /dev/null +++ b/zend/boolmember.h @@ -0,0 +1,67 @@ +/** + * BoolMember.h + * + * Implementation for a property that is initially set to a boolean value + * + * @author Emiel Bruijntjes + * @copyright 2013 Copernica BV + */ + +/** + * Set up namespace + */ +namespace Php { + +/** + * Class definition + */ +class BoolMember : public Member +{ +private: + /** + * The value + * @var bool + */ + bool _value; + +public: + /** + * Constructor + * @param name + * @param value + * @param flags + */ + BoolMember(const char *name, bool value, int flags) : Member(name, flags), _value(value) {} + + /** + * Destructor + */ + virtual ~BoolMember() {} + + /** + * Virtual method to declare a class constant + * @param entry Class entry + * @param tsrm_ls + */ + virtual void constant(struct _zend_class_entry *entry TSRMLS_DC) override + { + zend_declare_class_constant_bool(entry, _name.c_str(), _name.size(), _value TSRMLS_CC); + } + + /** + * Virtual method to declare the property + * @param entry Class entry + * @param tsrm_ls + */ + virtual void declare(struct _zend_class_entry *entry TSRMLS_DC) override + { + // char* cast is necessary for php 5.3 + zend_declare_property_bool(entry, (char *)_name.c_str(), _name.size(), _value, _flags TSRMLS_CC); + } +}; + +/** + * End of namespace + */ +} + diff --git a/zend/callable.cpp b/zend/callable.cpp new file mode 100644 index 0000000..737b85b --- /dev/null +++ b/zend/callable.cpp @@ -0,0 +1,133 @@ +/** + * Function.cpp + * + * Implementation for the function class + * + * @author Emiel Bruijntjes + * @copyright 2013 Copernica BV + */ +#include "includes.h" + +/** + * Set up namespace + */ +namespace Php { + +/** + * Function that is called by the Zend engine every time that a function gets called + * @param ht + * @param return_value + * @param return_value_ptr + * @param this_ptr + * @param return_value_used + * @param tsrm_ls + * @return integer + */ +static void invoke_callable(INTERNAL_FUNCTION_PARAMETERS) +{ + // find the function name + const char *name = get_active_function_name(TSRMLS_C); + + // uncover the hidden pointer inside the function name + Callable *callable = HiddenPointer(name); + + // wrap the return value + Value result(return_value, true); + + // construct parameters + ParametersImpl params(this_ptr, ZEND_NUM_ARGS() TSRMLS_CC); + + // the function could throw an exception + try + { + // get the result + result = callable->invoke(params); + } + catch (Exception &exception) + { + // process the exception + process(exception TSRMLS_CC); + } +} + +/** + * Fill a function entry + * + * This method is called when the extension is registering itself, when the + * function or method introces himself + * + * @param entry Entry to be filled + * @param classname Optional class name + * @param flags Is this a public property? + */ +void Callable::initialize(zend_function_entry *entry, const char *classname, int flags) const +{ + // fill the members of the entity, and hide a pointer to the current object in the name + entry->fname = (const char *)_ptr; + entry->handler = invoke_callable; + entry->arg_info = _argv; + entry->num_args = _argc; + entry->flags = flags; + + // we should fill the first argument as well + initialize((zend_arg_info *)entry->arg_info, classname); +} + +/** + * Fill a function entry + * @param info Info to be filled + * @param classname Optional classname + */ +void Callable::initialize(zend_arg_info *info, const char *classname) const +{ +#if PHP_VERSION_ID >= 50400 + // up until php 5.3, the first info object is filled with alternative information, + // later it is casted to a zend_internal_function object + auto *finfo = (zend_internal_function_info *)info; + + // fill in all the members, note that return reference is false by default, + // because we do not support returning references in PHP-CPP, although Zend + // engine allows it. Inside the name we hide a pointer to the current object + finfo->_name = _ptr; + finfo->_name_len = strlen(_ptr); + finfo->_class_name = classname; + + // number of required arguments, and the expected return type + finfo->required_num_args = _required; + finfo->_type_hint = (unsigned char)_return; + + // we do not support return-by-reference + finfo->return_reference = false; + +# if PHP_VERSION_ID >= 50600 + // since php 5.6 there are _allow_null and _is_variadic properties. It's + // not exactly clear what they do (@todo find this out) so for now we set + // them to false + finfo->_allow_null = false; + finfo->_is_variadic = false; + +# else + // passing by reference is not used (only for php 5.4 and php 5.5) + finfo->pass_rest_by_reference = false; +# endif + +#else + // php 5.3 code + info->name = nullptr; + info->name_len = 0; + info->class_name = nullptr; + info->class_name_len = 0; + info->array_type_hint = false; + info->allow_null = false; + info->pass_by_reference = false; + info->return_reference = false; + info->required_num_args = _required; +#endif +} + +/** + * End of namespace + */ +} + + diff --git a/zend/callable.h b/zend/callable.h new file mode 100644 index 0000000..65ce719 --- /dev/null +++ b/zend/callable.h @@ -0,0 +1,190 @@ +/** + * Callable.h + * + * Object represents a callable function or method that is defined with the CPP + * API. + * + * @author Emiel Bruijntjes + * @copyright 2013 Copernica BV + */ + +/** + * Set up namespace + */ +namespace Php { + +/** + * Class definition + */ +class Callable +{ +public: + /** + * Constructor + * @param name Function or method name + * @param arguments Information about the arguments + */ + Callable(const char *name, const Arguments &arguments = {}) : _ptr(this, name) + { + // construct vector for arguments + _argc = arguments.size(); + _argv = new zend_arg_info[_argc+1]; + + // the first record is initialized with information about the function, + // so we skip that here + int i=1; + + // loop through the arguments + for (auto it = arguments.begin(); it != arguments.end(); it++) + { + // increment counter with number of required parameters + if (it->required()) _required++; + + // fill the arg info + fill(&_argv[i], *it); + } + } + + /** + * Copy constructor + * @param that + */ + Callable(const Callable &that) : + _ptr(that._ptr), + _return(that._return), + _required(that._required), + _argc(that._argc), + _argv(nullptr) {} + + /** + * Move constructor + * @param that + */ + Callable(Callable &&that) : + _ptr(std::move(that._ptr)), + _return(that._return), + _required(that._required), + _argc(that._argc), + _argv(that._argv) + { + // invalidate other object + that._argv = nullptr; + } + + /** + * Destructor + */ + virtual ~Callable() + { + if (_argv) delete[] _argv; + } + + /** + * Method that gets called every time the function is executed + * @param params The parameters that were passed + * @return Variable Return value + */ + virtual Value invoke(Parameters ¶ms) = 0; + + /** + * Fill a function entry + * @param entry Entry to be filled + * @param ns Active namespace + * @param classname Optional class name + * @param flags Access flags + */ + void initialize(zend_function_entry *entry, const char *classname = nullptr, int flags = 0) const; + + /** + * Fill function info + * @param info Info object to be filled + * @param ns Active namespace + * @param classname Optional class name + */ + void initialize(zend_arg_info *info, const char *classname = nullptr) const; + + +protected: + /** + * Hidden pointer to the name and the function + * @var HiddenPointer + */ + HiddenPointer _ptr; + + /** + * Suggestion for the return type + * @var Type + */ + Type _return = Type::Null; + + /** + * Required number of arguments + * @var integer + */ + int _required = 0; + + /** + * Total number of arguments + * @var integer + */ + int _argc = 0; + + /** + * The arguments + * @var zend_arg_info[] + */ + zend_arg_info *_argv = nullptr; + + /** + * Private helper method to fill an argument object + * @param info object from the zend engine + * @param arg original object + */ + void fill(zend_arg_info *info, const Argument &arg) const + { + // fill members + info->name = arg.name().c_str(); + info->name_len = arg.name().size(); + +#if PHP_VERSION_ID >= 50400 + + // since php 5.4 there is a type-hint, but we only support arrays, objects and callables + switch (arg.type()) { + case Type::Array: info->type_hint = IS_ARRAY; break; + case Type::Callable: info->type_hint = IS_CALLABLE; break; + case Type::Object: info->type_hint = IS_OBJECT; break; + default: info->type_hint = IS_NULL; break; + } + +# if PHP_VERSION_ID >= 50600 + + // from PHP 5.6 and onwards, an is_variadic property can be set, this + // specifies whether this argument is the first argument that specifies + // the type for a variable length list of arguments. For now we only + // support methods and functions with a fixed number of arguments. + info->is_variadic = false; + +# endif + +#else + + // php 5.3 code + info->array_type_hint = type == Type::Array; + info->return_reference = false; + info->required_num_args = 0; // @todo is this correct? + +#endif + + // this parameter is a regular type + info->class_name = arg.type() == Type::Object ? arg.classname().c_str() : nullptr; + info->class_name_len = arg.type() == Type::Object ? arg.classname().size() : 0; + info->allow_null = arg.allowNull(); + info->pass_by_reference = arg.byReference(); + } +}; + +/** + * End of namespace + */ +} + diff --git a/zend/classbase.cpp b/zend/classbase.cpp new file mode 100644 index 0000000..18d81d5 --- /dev/null +++ b/zend/classbase.cpp @@ -0,0 +1,130 @@ +/** + * ClassBase.cpp + * + * Implementation of the ClassBase class. + * + * @author Emiel Bruijntjes + * @copyright 2014 Copernica BV + */ +#include "includes.h" + +/** + * Set up namespace + */ +namespace Php { + +/** + * Protected constructor + * @param classname Class name + * @param flags Class flags + */ +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) _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); +} + +/** + * Protected constructor + * @param classname Class name + * @param type Class type + */ +ClassBase::ClassBase(const char *classname, ClassType type) +{ + // construct implementation + _impl = std::make_shared(classname, type); +} + +/** + * Function that can be called by a derived method when a certain function + * is not implemented + */ +void ClassBase::notImplemented() +{ + // throw an exception + throw NotImplemented(); +} + +/** + * 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 + */ +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 + * @param name Name of the method + * @param method The actual method + * @param flags Optional flags + * @param args Description of the supported arguments + */ +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 + */ +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 + */ +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 + */ +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); } + +/** + * Add an interface + * @param interface Interface object + */ +void ClassBase::implements(const ClassBase &interface) { _impl->implements(interface._impl); } + +/** + * Set the base class + * @param base Php::Class object that is the base + */ +void ClassBase::extends(const ClassBase &base) { _impl->extends(base._impl); } + +/** + * End namespace + */ +} + diff --git a/zend/classimpl.cpp b/zend/classimpl.cpp new file mode 100644 index 0000000..d63956a --- /dev/null +++ b/zend/classimpl.cpp @@ -0,0 +1,1429 @@ +/** + * 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 +} + +/** + * @todo refactor so that methods become simpler + */ + +/** + * Retrieve our C++ implementation object + * @param entry + * @return ClassImpl + */ +static ClassImpl *self(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)); +} + +/** + * 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 + ParametersImpl 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 + process(exception TSRMLS_CC); + } +} + +/** + * 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 + ParametersImpl 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 + process(exception TSRMLS_CC); + } +} + +/** + * 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 = self(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 = self(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 = self(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 val1 + * @param val2 + * @param tsrm_ls + * @return int + */ +int ClassImpl::compare(zval *val1, zval *val2 TSRMLS_DC) +{ + // prevent exceptions + try + { + // retrieve the class entry linked to this object + auto *entry = zend_get_class_entry(val1 TSRMLS_CC); + + // other object must be of the same type + if (entry != zend_get_class_entry(val2 TSRMLS_CC)) throw NotImplemented(); + + // we need the C++ class meta-information object + ClassBase *meta = self(entry)->_base; + + // get the base objects + Base *object1 = ObjectImpl::find(val1 TSRMLS_CC)->object(); + Base *object2 = ObjectImpl::find(val2 TSRMLS_CC)->object(); + + // run the compare method + return meta->callCompare(object1, object2); + } + 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(val1, val2 TSRMLS_CC); + } + catch (Exception &exception) + { + // a Php::Exception was thrown by the extension __compare function, + // pass this on to user space + process(exception TSRMLS_CC); + + // what shall we return here... + return 1; + } +} + +/** + * Function to cast the object to a different type + * @param val + * @param retval + * @param type + * @param tsrm_ls + * @return int + */ +int ClassImpl::cast(zval *val, zval *retval, int type TSRMLS_DC) +{ + // get the base c++ object + Base *object = ObjectImpl::find(val TSRMLS_CC)->object(); + + // 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 = self(entry)->_base; + + // 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(object).detach(); break; + case Type::Float: result = meta->callToFloat(object).detach(); break; + case Type::Bool: result = meta->callToBool(object).detach(); break; + case Type::String: result = meta->callToString(object).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 original parameter overwritten? + if (val == retval) zval_dtor(val); + + // 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(val, retval, type TSRMLS_CC); + } + catch (Exception &exception) + { + // pass on the exception to php userspace + process(exception TSRMLS_CC); + + // 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 = self(entry); + ClassBase *meta = impl->_base; + + // retrieve the old object, which we are going to copy + ObjectImpl *old_object = ObjectImpl::find(val TSRMLS_CC); + + // create a new base c++ object + auto *cpp = meta->clone(old_object->object()); + + // 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 + ObjectImpl *new_object = new ObjectImpl(entry, cpp TSRMLS_CC); + + // store the object in the object cache + result.handle = new_object->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(ObjectImpl::find(object TSRMLS_CC)->object()); + + // 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 + process(exception TSRMLS_CC); + + // 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(ObjectImpl::find(object TSRMLS_CC)->object()); + + // 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) + process(exception TSRMLS_CC); + + // 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(ObjectImpl::find(object TSRMLS_CC)->object()); + + // 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 + process(exception TSRMLS_CC); + } + } + 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(ObjectImpl::find(object TSRMLS_CC)->object()); + + // 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) + process(exception TSRMLS_CC); + + // 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(ObjectImpl::find(object TSRMLS_CC)->object()); + + // 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) + process(exception TSRMLS_CC); + } + } + 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 = ObjectImpl::find(object TSRMLS_CC)->object(); + + // 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 = self(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 + process(exception TSRMLS_CC); + + // 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 = ObjectImpl::find(object TSRMLS_CC)->object(); + + // 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 = self(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 + process(exception TSRMLS_CC); + } +} + +/** + * 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 = ObjectImpl::find(object TSRMLS_CC)->object(); + + // 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 = self(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 + process(exception TSRMLS_CC); + + // 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 = self(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(ObjectImpl::find(object TSRMLS_CC)->object(), 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 + process(exception TSRMLS_CC); + } +} + +/** + * 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) +{ + // find object + ObjectImpl *obj = ObjectImpl::find(object); + + // get meta info + ClassImpl *impl = self(object->ce); + + // prevent exceptions + try + { + // call the destruct function + if (obj->object()) impl->_base->callDestruct(obj->object()); + } + 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 + process(exception TSRMLS_CC); + } +} + +/** + * 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 + ObjectImpl *obj = ObjectImpl::find(object); + + // no longer need it + obj->destruct(TSRMLS_C); +} + +/** + * 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 = self(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(); + + // create the object in the zend engine + ObjectImpl *object = new ObjectImpl(entry, cpp TSRMLS_CC); + + // store the object in the object cache + result.handle = object->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(ObjectImpl::find(object TSRMLS_CC)->object()); + + // user may throw an exception in the getIterator() function + try + { + // create an iterator + auto *iterator = new IteratorImpl(traversable->getIterator()); + + // return the implementation + return iterator->implementation(); + } + catch (Exception &exception) + { + // user threw an exception in its method + // implementation, send it to user space + process(exception TSRMLS_CC); + + // 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(ObjectImpl::find(object TSRMLS_CC)->object()); + + // 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(ObjectImpl::find(*object TSRMLS_CC)->object()); + + // 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; + } + + // do we have a base class? + if (_parent) + { + // check if the base class was already defined + if (_parent->_entry) entry.parent = _parent->_entry; + + // otherwise an error is reported + else std::cerr << "Derived class " << name() << " is initialized before base class " << _parent->name() << ": base class is ignored" << std::endl; + } + + // register the class + _entry = zend_register_internal_class(&entry TSRMLS_CC); + + // register the classes + for (auto &interface : _interfaces) + { + // register this interface + if (interface->_entry) zend_class_implements(_entry TSRMLS_CC, 1, interface->_entry); + + // otherwise report an error + else std::cerr << "Derived class " << name() << " is initialized before base class " << interface->name() << ": interface is ignored" << std::endl; + } + + // 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/zend/classimpl.h b/zend/classimpl.h new file mode 100644 index 0000000..febdbca --- /dev/null +++ b/zend/classimpl.h @@ -0,0 +1,445 @@ +/** + * 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; + + /** + * Interfaces that are implemented + * @var std::list + */ + std::list> _interfaces; + + /** + * The parent/base class + * @var std::shared_ptr + */ + std::shared_ptr _parent; + + + /** + * 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 class name + * @return std::string + */ + const std::string &name() const + { + return _name; + } + + /** + * 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 + */ +#if PHP_VERSION_ID >= 50400 + static zval *readProperty(zval *object, zval *name, int type, const zend_literal *key TSRMLS_DC); +#else + static zval *readProperty(zval *object, zval *name, int type TSRMLS_DC); +#endif + + /** + * 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 + */ +#if PHP_VERSION_ID >= 50400 + static void writeProperty(zval *object, zval *name, zval *value, const zend_literal *key TSRMLS_DC); +#else + static void writeProperty(zval *object, zval *name, zval *value TSRMLS_DC); +#endif + + /** + * 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 + */ +#if PHP_VERSION_ID >= 50400 + static int hasProperty(zval *object, zval *name, int has_set_exists, const zend_literal *key TSRMLS_DC); +#else + static int hasProperty(zval *object, zval *name, int has_set_exists TSRMLS_DC); +#endif + + /** + * 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 + */ +#if PHP_VERSION_ID >= 50400 + static void unsetProperty(zval *object, zval *member, const zend_literal *key TSRMLS_DC); +#else + static void unsetProperty(zval *object, zval *member TSRMLS_DC); +#endif + + /** + * 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 + */ +#if PHP_VERSION_ID >= 50400 + static zend_function *getMethod(zval **object_ptr, char *method, int method_len, const zend_literal *key TSRMLS_DC); +#else + static zend_function *getMethod(zval **object_ptr, char *method, int method_len TSRMLS_DC); +#endif + + /** + * 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 & MethodModifiers, 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 & MethodModifiers, 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 & MethodModifiers, 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 & MethodModifiers, 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 & MethodModifiers, 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 & MethodModifiers, 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 & MethodModifiers, 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 & MethodModifiers, 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 & MethodModifiers) | 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 & MethodModifiers) | 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 & MethodModifiers) | 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 & MethodModifiers) | 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, (flags & (MethodModifiers | Static)) | Abstract , 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 & PropertyModifiers)); } + void property(const char *name, int16_t value, int flags = Php::Public) { _members.push_back(std::make_shared(name, value, flags & PropertyModifiers)); } + void property(const char *name, int32_t value, int flags = Php::Public) { _members.push_back(std::make_shared(name, value, flags & PropertyModifiers)); } + void property(const char *name, int64_t value, int flags = Php::Public) { _members.push_back(std::make_shared(name, value, flags & PropertyModifiers)); } + void property(const char *name, bool value, int flags = Php::Public) { _members.push_back(std::make_shared (name, value, flags & PropertyModifiers)); } + void property(const char *name, char value, int flags = Php::Public) { _members.push_back(std::make_shared (name, &value, 1, flags & PropertyModifiers)); } + void property(const char *name, const std::string &value, int flags = Php::Public) { _members.push_back(std::make_shared (name, value, flags & PropertyModifiers)); } + void property(const char *name, const char *value, int flags = Php::Public) { _members.push_back(std::make_shared (name, value, strlen(value), flags & PropertyModifiers)); } + void property(const char *name, double value, int flags = Php::Public) { _members.push_back(std::make_shared (name, value, flags & PropertyModifiers)); } + + /** + * 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); } + + /** + * Add an interface that is implemented + * @param interface The interface that is implemented + */ + void implements(const std::shared_ptr &interface) { _interfaces.push_back(interface); } + + /** + * Set the base class + * @param base The base class + */ + void extends(const std::shared_ptr &base) { _parent = base; } + +}; + +/** + * End namespace + */ +} + diff --git a/zend/extension.cpp b/zend/extension.cpp new file mode 100644 index 0000000..9685b32 --- /dev/null +++ b/zend/extension.cpp @@ -0,0 +1,103 @@ +/** + * Extension.cpp + * + * @author Emiel Bruijntjes + * @copyright 2013, 2014 Copernica BV + */ +#include "includes.h" + +/** + * Set up namespace + */ +namespace Php { + +/** + * Constructor that defines a number of functions right away + * @param name Extension name + * @param version Extension version string + */ +Extension::Extension(const char *name, const char *version) : + Namespace(""), _impl(new ExtensionImpl(this, name, version)) {} + +/** + * Destructor + */ +Extension::~Extension() +{ + // get rid of the implementation object + delete _impl; +} + +/** + * Register a function to be called when the PHP engine is ready + * @param callback + * @return Extension + */ +Extension &Extension::onStartup(const Callback &callback) +{ + // pass on to the implementation + _impl->onStartup(callback); + + // allow chaining + return *this; +} + +/** + * Register a function to be called when the PHP engine is going to stop + * @param callback + * @return Extension + */ +Extension &Extension::onShutdown(const Callback &callback) +{ + // pass on to the implementation + _impl->onShutdown(callback); + + // allow chaining + return *this; +} + +/** + * Register a callback that is called at the beginning of each pageview/request + * @param callback + */ +Extension &Extension::onRequest(const Callback &callback) +{ + // pass on to the implementation + _impl->onRequest(callback); + + // allow chaining + return *this; +} + +/** + * Register a callback that is called to cleanup things after a pageview/request + * @param callback + */ +Extension &Extension::onIdle(const Callback &callback) +{ + // pass on to the implementation + _impl->onIdle(callback); + + // allow chaining + return *this; +} + +/** + * Retrieve the module pointer + * + * This is the memory address that should be exported by the get_module() + * function. + * + * @return void* + */ +void *Extension::module() +{ + // pass on to the implementation + return _impl->module(); +} + +/** + * End of namespace + */ +} + diff --git a/zend/extensionimpl.cpp b/zend/extensionimpl.cpp new file mode 100644 index 0000000..9562362 --- /dev/null +++ b/zend/extensionimpl.cpp @@ -0,0 +1,300 @@ +/** + * Extension.cpp + * + * @author Emiel Bruijntjes + * @copyright 2013 Copernica BV + */ +#include "includes.h" + +/** + * Set up namespace + */ +namespace Php { + +/** + * If this extension is compiled for a PHP version with multi + * threading support, we need an additional header file + */ +#ifdef ZTS +#include "TSRM.h" +#endif + +/** + * We're almost there, we now need to declare an instance of the + * structure defined above (if building for a single thread) or some + * sort of impossible to understand magic pointer-to-a-pointer (for + * multi-threading builds). We make this a static variable because + * this already is bad enough. + */ +ZEND_DECLARE_MODULE_GLOBALS(phpcpp) + +/** + * Function that must be defined to initialize the "globals" + * We do not have to initialize anything, but PHP needs to call this + * method (crazy) + * @param globals + */ +static void init_globals(zend_phpcpp_globals *globals) {} + +/** + * The *startup() and *shutdown() callback functions are passed a module_number + * variable. However, there does not seem to be a decent API call in Zend to + * get back the original module_entry linked to this number. So we have to + * look up entries in a hash table to find the right module entry. To make things + * even worse, the records in this hash table are copies of the original + * zend_module_entry structure, so we can also not hide the C++ extension + * object pointer in the entry that we created ourselves. + * + * We have an ugly solution, we keep track of a map of all C++ extension names + * and their associated extension object, and a map of all module number and + * the linked extension object. + * + * @var map + */ +static std::map name2extension; +static std::map number2extension; + +/** + * Handler function that is used in combination with zend_hash_apply() + * + * This function is called when we need to find an extension object based on + * an extension number. We loop through the list of all registered modules, and + * for each module we check if we know the extension based on the name + * + * @param zend_module_entry + */ +static int match_module(zend_module_entry *entry) +{ + // check if there is an extension with this name + auto iter = name2extension.find(entry->name); + if (iter == name2extension.end()) return ZEND_HASH_APPLY_KEEP; + + // we have the extension, store in combination with the number + number2extension[entry->module_number] = iter->second; + + // done + return ZEND_HASH_APPLY_KEEP; +} + +/** + * Find an extension based on the module number + * @param number + * @param tsrm_ls + * @return Extension* + */ +static ExtensionImpl *find(int number TSRMLS_DC) +{ + // do we already have an extension with this number? + auto iter = number2extension.find(number); + if (iter != number2extension.end()) return iter->second; + + // no, not yet, loop through all modules + zend_hash_apply(&module_registry, (apply_func_t)match_module TSRMLS_CC); + + // find again + iter = number2extension.find(number); + if (iter == number2extension.end()) return nullptr; + + // found! + return iter->second; +} + +/** + * Function that is called when the extension initializes + * @param type Module type + * @param number Module number + * @param tsrm_ls + * @return int 0 on success + */ +int ExtensionImpl::onStartup(int type, int module_number TSRMLS_DC) +{ + // initialize and allocate the "global" variables + ZEND_INIT_MODULE_GLOBALS(phpcpp, init_globals, NULL); + + // get the extension + auto *extension = find(module_number TSRMLS_CC); + + // initialize the extension + extension->initialize(TSRMLS_C); + + // is the callback registered? + if (extension->_onStartup) extension->_onStartup(); + + // done + return BOOL2SUCCESS(true); +} + +/** + * Function that is called when the extension is about to be stopped + * @param type Module type + * @param number Module number + * @param tsrm_ls + * @return int + */ +int ExtensionImpl::onShutdown(int type, int module_number TSRMLS_DC) +{ + // get the extension + auto *extension = find(module_number TSRMLS_CC); + + // is the callback registered? + if (extension->_onShutdown) extension->_onShutdown(); + + // done + return BOOL2SUCCESS(true); +} + +/** + * Function that is called when a request starts + * @param type Module type + * @param number Module number + * @param tsrm_ls + * @return int 0 on success + */ +int ExtensionImpl::onRequest(int type, int module_number TSRMLS_DC) +{ + // get the extension + auto *extension = find(module_number TSRMLS_CC); + + // is the callback registered? + if (extension->_onRequest) extension->_onRequest(); + + // done + return BOOL2SUCCESS(true); +} + +/** + * Function that is called when a request is ended + * @param type Module type + * @param number Module number + * @param tsrm_ls + * @return int 0 on success + */ +int ExtensionImpl::onIdle(int type, int module_number TSRMLS_DC) +{ + // get the extension + auto *extension = find(module_number TSRMLS_CC); + + // is the callback registered? + if (extension->_onIdle) extension->_onIdle(); + + // done + return BOOL2SUCCESS(true); +} + +/** + * Constructor + * @param data Pointer to the extension object created by the extension programmer + * @param name Name of the extension + * @param version Version number + */ +ExtensionImpl::ExtensionImpl(Extension *data, const char *name, const char *version) : _data(data) +{ + // keep extension pointer based on the name + name2extension[name] = this; + + // assign all members (apart from the globals) + _entry.size = sizeof(zend_module_entry); // size of the data + _entry.zend_api = ZEND_MODULE_API_NO; // api number + _entry.zend_debug = ZEND_DEBUG; // debug mode enabled? + _entry.zts = USING_ZTS; // is thread safety enabled? + _entry.ini_entry = NULL; // the php.ini record + _entry.deps = NULL; // dependencies on other modules + _entry.name = name; // extension name + _entry.functions = NULL; // functions supported by this module (none for now) + _entry.module_startup_func = &ExtensionImpl::onStartup; // startup function for the whole extension + _entry.module_shutdown_func = &ExtensionImpl::onShutdown; // shutdown function for the whole extension + _entry.request_startup_func = &ExtensionImpl::onRequest; // startup function per request + _entry.request_shutdown_func = &ExtensionImpl::onIdle; // shutdown function per request + _entry.info_func = NULL; // information for retrieving info + _entry.version = version; // version string + _entry.globals_size = 0; // size of the global variables + _entry.globals_ctor = NULL; // constructor for global variables + _entry.globals_dtor = NULL; // destructor for global variables + _entry.post_deactivate_func = NULL; // unknown function + _entry.module_started = 0; // module is not yet started + _entry.type = 0; // temporary or persistent module, will be filled by Zend engine + _entry.handle = NULL; // dlopen() handle, will be filled by Zend engine + _entry.module_number = 0; // module number will be filled in by Zend engine + _entry.build_id = (char *)ZEND_MODULE_BUILD_ID; // check if extension and zend engine are compatible + + // things that only need to be initialized +#ifdef ZTS + _entry.globals_id_ptr = NULL; +#else + _entry.globals_ptr = NULL; +#endif + +} + +/** + * Destructor + */ +ExtensionImpl::~ExtensionImpl() +{ + // deallocate functions + if (_entry.functions) delete[] _entry.functions; +} + +/** + * Retrieve the module entry + * @return zend_module_entry + */ +zend_module_entry *ExtensionImpl::module() +{ + // check if functions we're already defined + if (_entry.functions) return &_entry; + + // the number of functions + int count = _data->functions(); + + // skip if there are no functions + if (count == 0) return &_entry; + + // allocate memory for the functions + zend_function_entry *entries = new zend_function_entry[count + 1]; + + // index being processed + int i = 0; + + // apply a function to each function + _data->apply([&i, entries](const std::string &prefix, Function &function) { + + // initialize the function + function.initialize(prefix, &entries[i]); + + // move on to the next iteration + i++; + }); + + // last entry should be set to all zeros + zend_function_entry *last = &entries[count]; + + // all should be set to zero + memset(last, 0, sizeof(zend_function_entry)); + + // store functions in entry object + _entry.functions = entries; + + // return the entry + return &_entry; +} + +/** + * Initialize the extension after it was started + * @param tsrm_ls + */ +void ExtensionImpl::initialize(TSRMLS_D) +{ + // we need to register each class, find out all classes + _data->apply([TSRMLS_C](const std::string &prefix, ClassBase &c) { + + // forward to implementation class + c.implementation()->initialize(&c, prefix TSRMLS_CC); + }); +} + +/** + * End of namespace + */ +} + diff --git a/zend/extensionimpl.h b/zend/extensionimpl.h new file mode 100644 index 0000000..cc37354 --- /dev/null +++ b/zend/extensionimpl.h @@ -0,0 +1,213 @@ +/** + * ExtensionImpl.h + * + * Extension implementation for the Zend engine. + * + * @author Emiel Bruijntjes + * @copyright 2013, 2014 Copernica BV + */ + +/** + * Set up namespace + */ +namespace Php { + +/** + * Class definition + */ +class ExtensionImpl +{ +protected: + /** + * Pointer to the extension object that is filled by the extension programmer + * @var Extension + */ + Extension *_data; + + /** + * The information that is passed to the Zend engine + * + * Although it would be slightly faster to not make this a pointer, this + * would require that client code also includes the PHP header files, which + * we try to prevent with the PHP-CPP library, so we allocate it dynamically. + * + * @var zend_module_entry + */ + zend_module_entry _entry; + + /** + * Callback that is called after the engine is initialized and before the + * pageviews are going to be handled + * @var Callback + */ + Callback _onStartup; + + /** + * Callback that is called in front of each request + * @var Callback + */ + Callback _onRequest; + + /** + * Callback that is called right after each request + * @var Callback + */ + Callback _onIdle; + + /** + * Callback that is called right before the engine is closing down + * @var Callback + */ + Callback _onShutdown; + +public: + /** + * Constructor + * @param data Extension object created by the extension programmer + * @param name Name of the extension + * @param version Version number + */ + ExtensionImpl(Extension *data, const char *name, const char *version); + + /** + * No copy'ing and no moving + */ + ExtensionImpl(const ExtensionImpl &extension) = delete; + ExtensionImpl(ExtensionImpl &&extension) = delete; + + /** + * Destructor + */ + virtual ~ExtensionImpl(); + + /** + * Register a function to be called when the PHP engine is ready + * + * The callback will be called after all extensions are loaded, and all + * functions and classes are available, but before the first pageview/request + * is handled. You can register this callback if you want to be notified + * when the engine is ready, for example to initialize certain things. + * + * @param callback + */ + void onStartup(const Callback &callback) + { + // copy callback + _onStartup = callback; + } + + /** + * Register a function to be called when the PHP engine is going to stop + * + * The callback will be called right before the process is going to stop. + * You can register a function if you want to clean up certain things. + * + * @param callback + */ + void onShutdown(const Callback &callback) + { + // copy callback + _onShutdown = callback; + } + + /** + * Register a callback that is called at the beginning of each pageview/request + * + * You can register a callback if you want to initialize certain things + * at the beginning of each request. Remember that the extension can handle + * multiple requests after each other, and you may want to set back certain + * global variables to their initial variables in front of each request + * + * @param callback + */ + void onRequest(const Callback &callback) + { + // copy callback + _onRequest = callback; + } + + /** + * Register a callback that is called to cleanup things after a pageview/request + * + * The callback will be called after _each_ request, so that you can clean up + * certain things and make your extension ready to handle the next request. + * This method is called onIdle because the extension is idle in between + * requests. + * + * @param callback + */ + void onIdle(const Callback &callback) + { + // copy callback + _onIdle = callback; + } + + /** + * Retrieve the module entry + * + * This is the memory address that should be exported by the get_module() + * function. + * + * @return _zend_module_entry + */ + zend_module_entry *module(); + + /** + * Cast to a module entry + * @return _zend_module_entry* + */ + operator zend_module_entry * () + { + return module(); + } + +private: + /** + * Initialize the namespace after it was registered + * @param tsrm_ls + */ + void initialize(TSRMLS_D); + + /** + * Function that is called when the extension initializes + * @param type Module type + * @param number Module number + * @param tsrm_ls + * @return int 0 on success + */ + static int onStartup(int type, int module_number TSRMLS_DC); + + /** + * Function that is called when the extension is about to be stopped + * @param type Module type + * @param number Module number + * @param tsrm_ls + * @return int + */ + static int onShutdown(int type, int module_number TSRMLS_DC); + + /** + * Function that is called when a request starts + * @param type Module type + * @param number Module number + * @param tsrm_ls + * @return int 0 on success + */ + static int onRequest(int type, int module_number TSRMLS_DC); + + /** + * Function that is called when a request is ended + * @param type Module type + * @param number Module number + * @param tsrm_ls + * @return int 0 on success + */ + static int onIdle(int type, int module_number TSRMLS_DC); +}; + +/** + * End of namespace + */ +} + + diff --git a/zend/floatmember.h b/zend/floatmember.h new file mode 100644 index 0000000..9b5d4f2 --- /dev/null +++ b/zend/floatmember.h @@ -0,0 +1,67 @@ +/** + * FloatMember.h + * + * Implementation for a property that is initially set to a boolean value + * + * @author Emiel Bruijntjes + * @copyright 2013, 2014 Copernica BV + */ + +/** + * Set up namespace + */ +namespace Php { + +/** + * Class definition + */ +class FloatMember : public Member +{ +private: + /** + * The value + * @var double + */ + double _value; + +public: + /** + * Constructor + * @param name + * @param value + * @param flags + */ + FloatMember(const char *name, double value, int flags) : Member(name, flags), _value(value) {} + + /** + * Destructor + */ + virtual ~FloatMember() {} + + /** + * Virtual method to declare class constant + * @param entry Class entry + * @param tsrm_ls + */ + virtual void constant(struct _zend_class_entry *entry TSRMLS_DC) override + { + zend_declare_class_constant_double(entry, _name.c_str(), _name.size(), _value TSRMLS_CC); + } + + /** + * Virtual method to declare the property + * @param entry Class entry' + * @param tsrm_ls + */ + virtual void declare(struct _zend_class_entry *entry TSRMLS_DC) override + { + // converstion to char* necessary for php 5.3 + zend_declare_property_double(entry, (char *)_name.c_str(), _name.size(), _value, _flags TSRMLS_CC); + } +}; + +/** + * End of namespace + */ +} + diff --git a/zend/function.h b/zend/function.h new file mode 100644 index 0000000..4c34ac7 --- /dev/null +++ b/zend/function.h @@ -0,0 +1,102 @@ +/** + * Function.h + * + * The Function class is an extension of the Callable class that + * forwards the function call directly to a native function in C/C++ + * + * @author Emiel Bruijntjes + * @copyright 2013 Copernica BV + */ + +/** + * Set up namespace + */ +namespace Php { + +/** + * Class definition + */ +class Function : public Callable +{ +public: + /** + * Constructor + * @param name Function name + * @param function The native C function + */ + Function(const char *name, const native_callback_0 &function, const Arguments &arguments = {}) : Callable(name, arguments), _type(0) { _function.f0 = function; } + Function(const char *name, const native_callback_1 &function, const Arguments &arguments = {}) : Callable(name, arguments), _type(1) { _function.f1 = function; } + Function(const char *name, const native_callback_2 &function, const Arguments &arguments = {}) : Callable(name, arguments), _type(2) { _function.f2 = function; } + Function(const char *name, const native_callback_3 &function, const Arguments &arguments = {}) : Callable(name, arguments), _type(3) { _function.f3 = function; } + + /** + * Copy constructor + * @param that + */ + Function(const Function &that) : Callable(that), _function(that._function), _type(that._type) {} + + /** + * Move constructor + * @param that + */ + Function(Function &&that) : Callable(std::move(that)), _function(that._function), _type(that._type) {} + + /** + * Destructor + */ + virtual ~Function() {} + + /** + * Method that gets called every time the function is executed + * @param params The parameters that were passed + * @return Variable Return value + */ + virtual Value invoke(Parameters ¶ms) override + { + switch (_type) { + case 0: _function.f0(); return Value(); + case 1: _function.f1(params); return Value(); + case 2: return _function.f2(); + case 3: return _function.f3(params); + default: return Value(); + } + } + + /** + * Fill a function entry + * @param prefix Active namespace prefix + * @param entry Entry to be filled + */ + void initialize(const std::string &prefix, zend_function_entry *entry) + { + // if there is a namespace prefix, we should adjust the name + if (prefix.size()) _ptr = HiddenPointer(this, prefix+"\\"+(const char *)_ptr); + + // call base initialize + Callable::initialize(entry); + } + +private: + /** + * Union of supported callbacks + * One of the callbacks will be set + */ + union { + native_callback_0 f0; + native_callback_1 f1; + native_callback_2 f2; + native_callback_3 f3; + } _function; + + /** + * The callback that is set + * @var integer + */ + int _type; +}; + +/** + * End of namespace + */ +} + diff --git a/zend/global.cpp b/zend/global.cpp new file mode 100644 index 0000000..7eea8e7 --- /dev/null +++ b/zend/global.cpp @@ -0,0 +1,45 @@ +/** + * Global.cpp + * + * Implementation for the global variable + * + * @author Emiel Bruijntjes + * @copyright 2013 Copernica BV + */ +#include "includes.h" + +/** + * Namespace + */ +namespace Php { + +/** + * Function that is called when the value is updated + * @return Value + */ +Global &Global::update() +{ + // skip if the variable already exists + if (_exists) return *this; + + // we need the TSRMLS variable + TSRMLS_FETCH(); + + // add the variable to the globals + zend_hash_add(EG(active_symbol_table), _name.c_str(), _name.size()+1, &_val, sizeof(zval*), NULL); + + // add one extra reference because the variable now is a global var too + Z_ADDREF_P(_val); + + // remember that the variable now exists + _exists = true; + + // done + return *this; +} + +/** + * End of namespace + */ +} + diff --git a/zend/globals.cpp b/zend/globals.cpp new file mode 100644 index 0000000..8132ef3 --- /dev/null +++ b/zend/globals.cpp @@ -0,0 +1,92 @@ +/** + * Globals.cpp + * + * Implementation of the globals class + * + * @author Emiel Bruijntjes + * @copyright 2013 Copernica BV + */ +#include "includes.h" + +/** + * Namespace + */ +namespace Php { + +/** + * Get access to the globals single instance + * @return Globals + */ +Globals &Globals::instance() +{ + static Globals globals; + return globals; +} + +/** + * The one and only instance + * @var Globals + */ +Globals &GLOBALS = Globals::instance(); + +/** + * Get access to a global variable + * @param name + * @return Global + */ +Global Globals::operator[](const char *name) +{ + // pointer to a zval + zval **varvalue; + + // we need the TSRMLS variable + TSRMLS_FETCH(); + + // check if the variable already exists + if (zend_hash_find(&EG(symbol_table), name, strlen(name)+1, (void**)&varvalue) == FAILURE) + { + // the variable does not already exist, return a global object + // that will automatically set the value when it is updated + return Global(name); + } + else + { + // we are in the happy situation that the variable exists, we turn + // this value into a reference value, and return that + return Global(name, *varvalue); + } +} + +/** + * Get access to a global variable + * @param name + * @return Global + */ +Global Globals::operator[](const std::string &name) +{ + // pointer to a zval + zval **varvalue; + + // we need the TSRMLS variable + TSRMLS_FETCH(); + + // check if the variable already exists + if (zend_hash_find(&EG(symbol_table), name.c_str(), name.size()+1, (void**)&varvalue) == FAILURE) + { + // the variable does not already exist, return a global object + // that will automatically set the value when it is updated + return Global(name); + } + else + { + // we are in the happy situation that the variable exists, we turn + // this value into a reference value, and return that + return Global(name, *varvalue); + } +} + +/** + * End of namespace + */ +} + diff --git a/zend/hashiterator.h b/zend/hashiterator.h new file mode 100644 index 0000000..b1f409f --- /dev/null +++ b/zend/hashiterator.h @@ -0,0 +1,250 @@ +/** + * HashIterator.h + * + * This is an internal helper class that is used when iterating over a + * Php::Value object that holds a hash table (an array or an object that + * does not implement the Traversable interface - stl style. + * + * Thus, when you do c++ things like "for (auto &iter : value)", internally + * a ValueIterator object is being used. + * + * @author Emiel Bruijntjes + * @copyright 2014 Copernica BV + */ + +/** + * Set up namespace + */ +namespace Php { + +/** + * Class definition + */ +class HashIterator : public ValueIteratorImpl +{ +public: + /** + * Constructor + * @param hashtable The hashtable to iterate over + * @param first Should it start on the first position? + * @param tsrm_ls + */ + HashIterator(HashTable *hashtable, bool first) : _table(hashtable) + { + // reset the hash pointer to the internal position + if (hashtable && first) + { + // move to first position + zend_hash_internal_pointer_reset_ex(_table, &_position); + + // read current data + if (read()) return; + + // data was private, move on + increment(); + } + else + { + // start with invalid data + invalidate(); + } + } + + /** + * Copy constructor + * @param that + * @param tsrm_ls + */ + HashIterator(const HashIterator &that TSRMLS_DC) : + _table(that._table), _position(that._position) + { + // read current position + read(); + } + + /** + * Destructor + */ + virtual ~HashIterator() {} + + /** + * Clone the object + * @param tsrm_ls + * @return ValueIteratorImpl + */ + virtual ValueIteratorImpl *clone() + { + // create a new instance + return new HashIterator(*this); + } + + /** + * Increment position (pre-increment) + * @return bool + */ + virtual bool increment() override + { + // leap out if already on an invalid pos (behind the last pos) + if (!_position) return false; + + // move the iterator forward + if (zend_hash_move_forward_ex(_table, &_position) == SUCCESS) + { + // read current key and value + if (read()) return true; + + // data was private or invalid, move further + return increment(); + } + else + { + // invalidate current position + return invalidate(); + } + } + + /** + * Decrement position (pre-decrement) + * @return bool + */ + virtual bool decrement() override + { + // leap out if we're not even iterating over a hash table + if (!_table) return false; + + // if position is invalid, it is one position behind the last position + if (!_position) + { + // move to last position + zend_hash_internal_pointer_end_ex(_table, &_position); + } + else if (zend_hash_move_backwards_ex(_table, &_position) == FAILURE) + { + // invalidate current position + return invalidate(); + } + + // read current key and value + if (read()) return true; + + // data was private, move on + return decrement(); + } + + /** + * Compare with other iterator + * @param that + * @return bool + */ + virtual bool equals(const ValueIteratorImpl *that) const override + { + // this always is a hash iterator + HashIterator *other = (HashIterator *)that; + + // compare the positions + return _position == other->_position; + } + + /** + * Derefecence, this returns a std::pair with the current key and value + * @return std::pair + */ + virtual const std::pair ¤t() const override + { + return _current; + } + +private: + /** + * The hash table over which is being iterated + * @var HashTable + */ + HashTable *_table = nullptr; + + /** + * The position in the hash table + * @var HashPosition + */ + Bucket *_position = nullptr; + + /** + * The current key and value + * @var std::pair + */ + std::pair _current; + + /** + * Read current key and value + * @return bool + */ + bool read() + { + // zval to read the current key in + Value key; + +#if PHP_VERSION_ID >= 50500 + + // read in the current key + zend_hash_get_current_key_zval_ex(_table, key._val, &_position); + + // if the key is set to NULL, it means that the object is not at a valid position + if (key.isNull()) return invalidate(); + +#else + + // php 5.3 and php 5.4 need a different implementation because the function + // zend_hash_get_current_key_zval_ex is missing in php 5.3, declare variables + // we need for storing the key in + char *string_key; + unsigned int str_len; + unsigned long num_key; + + // get the current key + int type = zend_hash_get_current_key_ex(_table, &string_key, &str_len, &num_key, 0, &_position); + + // if key is not found, the iterator is at an invalid position + if (type == HASH_KEY_NON_EXISTANT) return invalidate(); + + // numeric keys are the easiest ones + if (type == HASH_KEY_IS_LONG) key = (int64_t)num_key; + else key = string_key; + +#endif + + // iterator is at a valid position, go fetch the data + // this is the variable we need for fetching the data + zval **value; + + // retrieve data + zend_hash_get_current_data_ex(_table, (void **) &value, &_position); + + // we can now update the current data + _current = std::make_pair(std::move(key), *value); + + // if the key is private (it starts with a null character) we should return + // false to report that the object is not in a completely valid state + return !_current.first.isString() || _current.first.rawValue()[0]; + } + + /** + * Invalidate the iterator + * @return bool + */ + bool invalidate() + { + // forget current position + _position = nullptr; + + // make the data a pair of null ptrs + _current = std::make_pair(nullptr,nullptr); + + // done + return false; + } +}; + +/** + * End namespace + */ +} + diff --git a/zend/hashmember.cpp b/zend/hashmember.cpp new file mode 100644 index 0000000..f6f8483 --- /dev/null +++ b/zend/hashmember.cpp @@ -0,0 +1,40 @@ +/** + * HashMember.cpp + * + * @author Emiel Bruijntjes + * @copyright 2013 Copernica BV + */ +#include "includes.h" + +/** + * Set up namespace + */ +namespace Php { + +/** + * Custom output stream operator + * @param stream + * @param value + * @return ostream + */ +std::ostream &operator<<(std::ostream &stream, const HashMember &value) +{ + return stream << value.value(); +} + +/** + * Custom output stream operator + * @param stream + * @param value + * @return ostream + */ +std::ostream &operator<<(std::ostream &stream, const HashMember &value) +{ + return stream << value.value(); +} + +/** + * End of namespace + */ +} + diff --git a/zend/includes.h b/zend/includes.h new file mode 100644 index 0000000..f547eb2 --- /dev/null +++ b/zend/includes.h @@ -0,0 +1,121 @@ +/** + * Includes.h + * + * Startup include file to compile the phpcpp library + * + * @author Emiel Bruijntjes + * @copyright 2013 Copernica BV + */ + +/** + * Include standard C and C++ libraries + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// for debug +#include + +//#define ZTS +//#define THREAD_T pthread_t +//#define MUTEX_T pthread_mutex_t * + +/** + * PHP includes + */ +#pragma GCC system_header +#include +#include +#include + +/** + * Macro to convert results to success status + */ +#define BOOL2SUCCESS(b) ((b) ? SUCCESS : FAILURE) + +/** + * Include other files from this library + */ +#include "../include/exception.h" +#include "../include/streams.h" +#include "../include/type.h" +#include "../include/hashparent.h" +#include "../include/value.h" +#include "../include/valueiterator.h" +#include "../include/array.h" +#include "../include/object.h" +#include "../include/hiddenpointer.h" +#include "../include/globals.h" +#include "../include/argument.h" +#include "../include/byval.h" +#include "../include/byref.h" +#include "../include/global.h" +#include "../include/super.h" +#include "../include/hashmember.h" +#include "../include/parameters.h" +#include "../include/modifiers.h" +#include "../include/base.h" +#include "../include/countable.h" +#include "../include/arrayaccess.h" +#include "../include/serializable.h" +#include "../include/iterator.h" +#include "../include/traversable.h" +#include "../include/classtype.h" +#include "../include/classbase.h" +#include "../include/interface.h" +#include "../include/class.h" +#include "../include/namespace.h" +#include "../include/extension.h" +#include "../include/call.h" + +/** + * Interface files for internal use only + */ +#include "init.h" +#include "callable.h" +#include "function.h" +#include "method.h" +#include "member.h" +#include "nullmember.h" +#include "numericmember.h" +#include "boolmember.h" +#include "stringmember.h" +#include "floatmember.h" +#include "arithmetic.h" +#include "origexception.h" +#include "notimplemented.h" +#include "property.h" +#include "valueiteratorimpl.h" +#include "hashiterator.h" +#include "invaliditerator.h" +#include "traverseiterator.h" +#include "iteratorimpl.h" +#include "streambuf.h" +#include "classimpl.h" +#include "objectimpl.h" +#include "parametersimpl.h" +#include "extensionimpl.h" + +#ifndef ZVAL_COPY_VALUE +#define ZVAL_COPY_VALUE(z, v) \ + do { \ + (z)->value = (v)->value; \ + Z_TYPE_P(z) = Z_TYPE_P(v); \ + } while (0) +#endif + +#ifndef INIT_PZVAL_COPY +#define INIT_PZVAL_COPY(z, v) \ + do { \ + ZVAL_COPY_VALUE(z, v); \ + Z_SET_REFCOUNT_P(z, 1); \ + Z_UNSET_ISREF_P(z); \ + } while (0) +#endif diff --git a/zend/init.h b/zend/init.h new file mode 100644 index 0000000..8f914f5 --- /dev/null +++ b/zend/init.h @@ -0,0 +1,54 @@ +/** + * Init.h + * + * Variables and structured required by the Zend engine to work + * with global variables + * + * @author Emiel Bruijntjes + * @copyright 2013 Copernica BV + */ + +/** + * Namespace + */ +namespace Php { + +/** + * The way how PHP C API deals with "global" variables is peculiar. + * + * The following macros are supposed to turn into a structure that is going + * to be instantiated for each parallel running request, and for which the + * PHP engine allocates a certain amount of memory, and a magic pointer that + * is passed and should be forwarded to every thinkable PHP function. + * + * We don't use this architecture. We have our own environment object + * that makes much more sense, and that we use. However, the Zend engine + * expects this structure and this structure to exist. + */ +ZEND_BEGIN_MODULE_GLOBALS(phpcpp) +ZEND_END_MODULE_GLOBALS(phpcpp) + +/** + * And now we're going to define a macro. This also is a uncommon architecture + * from PHP to get access to a variable from the structure above. + */ +#ifdef ZTS +#define PHPCPP_G(v) TSRMG(phpcpp_globals_id, phpcpp_globals *, v) +#else +#define PHPCPP_G(v) (phpcpp_globals.v) +#endif + +/** + * We're almost there, we now need to declare an instance of the + * structure defined above (if building for a single thread) or some + * sort of impossible to understand magic pointer-to-a-pointer (for + * multi-threading builds). We make this a static variable because + * this already is bad enough. + */ +extern ZEND_DECLARE_MODULE_GLOBALS(phpcpp) + +/** + * End of namespace + */ +} + diff --git a/zend/invaliditerator.h b/zend/invaliditerator.h new file mode 100644 index 0000000..388eca8 --- /dev/null +++ b/zend/invaliditerator.h @@ -0,0 +1,82 @@ +/** + * InvalidIterator.h + * + * Iterator class that is used for value objects that are not even + * iteratable. + * + * @author Emiel Bruijntjes + * @copyright 2014 Copernica BV + */ + +/** + * Set up namespace + */ +namespace Php { + +/** + * Class definition + */ +class InvalidIterator : public ValueIteratorImpl +{ +public: + /** + * Clone the object + * @param tsrm_ls + * @return ValueIteratorImpl + */ + virtual ValueIteratorImpl *clone() + { + // create a new instance + return new InvalidIterator(*this); + } + + /** + * Increment position (pre-increment) + * @param tsrm_ls + * @return bool + */ + virtual bool increment() override + { + return false; + } + + /** + * Decrement position (pre-decrement) + * @return bool + */ + virtual bool decrement() override + { + return false; + } + + /** + * Compare with other iterator + * @param that + * @return bool + */ + virtual bool equals(const ValueIteratorImpl *that) const override + { + // the other iterator is also an invalid-iterator, and all invalid + // iterators are equal + return true; + } + + /** + * Derefecence, this returns a std::pair with the current key and value + * @return std::pair + */ + virtual const std::pair ¤t() const override + { + // this method is never called, when it is, we create a static instance + static std::pair result; + + // return it + return result; + } +}; + +/** + * End namespace + */ +} + diff --git a/zend/iteratorimpl.cpp b/zend/iteratorimpl.cpp new file mode 100644 index 0000000..2750ddb --- /dev/null +++ b/zend/iteratorimpl.cpp @@ -0,0 +1,183 @@ +/** + * IteratorImpl.cpp + * + * Implementation file of the IteratorImpl class + * + * @author Emiel Bruijntjes + * @copyright 2014 Copernica BV + */ +#include "includes.h" + +/** + * Set up namespace + */ +namespace Php { + +/** + * Helper method to get access to ourselves + * @param iter + * @return IteratorImpl + */ +static IteratorImpl *self(zend_object_iterator *iter) +{ + return (IteratorImpl *)iter->data; +} + +/** + * Iterator destructor method + * @param iter + * @param tsrm_ls + */ +void IteratorImpl::destructor(zend_object_iterator *iter TSRMLS_DC) +{ + // delete the object + delete self(iter); +} + +/** + * Iterator valid function + * Returns FAILURE or SUCCESS + * @param iter + * @param tsrm_ls + * @return int + */ +int IteratorImpl::valid(zend_object_iterator *iter TSRMLS_DC) +{ + // check if valid + return self(iter)->valid() ? SUCCESS : FAILURE; +} + +/** + * Fetch the current item + * @param iter + * @param data + * @param tsrm_ls + */ +void IteratorImpl::current(zend_object_iterator *iter, zval ***data TSRMLS_DC) +{ + // get the actual iterator + IteratorImpl *iterator = self(iter); + + // retrieve the value (and store it in a member so that it is not + // destructed when the function returns) + iterator->_current = iterator->current(); + + // copy the value + *data = &iterator->_current._val; +} + +/** + * Fetch the key for the current element (optional, may be NULL). The key + * should be written into the provided zval* using the ZVAL_* macros. If + * this handler is not provided auto-incrementing integer keys will be + * used. + * @param iter + * @param key + * @param tsrm_ls + */ +void IteratorImpl::key(zend_object_iterator *iter, zval *key TSRMLS_DC) +{ + // retrieve the key + Value retval(self(iter)->key()); + + // detach the underlying zval + zval *val = retval.detach(); + + // copy it to the key + ZVAL_ZVAL(key, val, 1, 1); +} + +/** + * Function to retrieve the current key, php 5.3 style + * @param iter + * @param str_key + * @param str_key_len + * @param int_key + * @param tsrm_ls + * @return HASH_KEY_IS_STRING or HASH_KEY_IS_LONG + */ +int IteratorImpl::key(zend_object_iterator *iter, char **str_key, uint *str_key_len, ulong *int_key TSRMLS_DC) +{ + // retrieve the key + Value retval(self(iter)->key()); + + // is this a numeric string? + if (retval.isString()) + { + // copy the key and the from the value + *str_key = estrndup(retval.rawValue(), retval.size()); + *str_key_len = retval.size() + 1; + + // done + return HASH_KEY_IS_STRING; + } + else + { + // convert to a numeric + *int_key = retval.numericValue(); + + // done + return HASH_KEY_IS_LONG; + } +} + +/** + * Step forwards to the next element + * @param iter + * @param tsrm_ls + */ +void IteratorImpl::next(zend_object_iterator *iter TSRMLS_DC) +{ + // call the next method + self(iter)->next(); +} + +/** + * Rewind the iterator back to the start + * @param iter + * @param tsrm_ls + */ +void IteratorImpl::rewind(zend_object_iterator *iter TSRMLS_DC) +{ + // call the rewind method + self(iter)->rewind(); +} + +/** + * Get access to all iterator functions + * @return zend_object_iterator_funcs + */ +zend_object_iterator_funcs *IteratorImpl::functions() +{ + // static variable with all functions + static zend_object_iterator_funcs funcs; + + // static variable that knows if the funcs are already initialized + static bool initialized = false; + + // no need to set anything if already initialized + if (initialized) return &funcs; + + // set the members + funcs.dtor = &IteratorImpl::destructor; + funcs.valid = &IteratorImpl::valid; + funcs.get_current_data = &IteratorImpl::current; + funcs.get_current_key = &IteratorImpl::key; + funcs.move_forward = &IteratorImpl::next; + funcs.rewind = &IteratorImpl::rewind; + + // invalidate is not yet supported + funcs.invalidate_current = nullptr; + + // remember that functions are initialized + initialized = true; + + // done + return &funcs; +} + +/** + * End namespace + */ +} + diff --git a/zend/iteratorimpl.h b/zend/iteratorimpl.h new file mode 100644 index 0000000..0a815e2 --- /dev/null +++ b/zend/iteratorimpl.h @@ -0,0 +1,190 @@ +/** + * Iterator.h + * + * Base class for iterators. Extension writers that want to create traversable + * classes, should override the Php::Traversable base class. This base class + * forces you to implement a getIterator() method that returns an instance of + * a Php::Iterator class. + * + * In this file you find the signature of the Php::Iterator class. It mostly has + * pure virtual methods, which means that you should create a derived class + * that implements all these methods. + * + * @author Emiel Bruijntjes + * @copyright 2014 Copernica BV + */ + +/** + * Set up namespace + */ +namespace Php { + +/** + * Class definition + */ +class IteratorImpl +{ +private: + /** + * Unique pointer to the iterator that is returned by the extension + * @var std::unique_ptr + */ + std::unique_ptr _iterator; + + /** + * The current() method that is called by the Zend engine wants a + * pointer-to-pointer-to-a-zval. Because of this, we have to keep the + * current value in memory after the current() method returns because + * the pointer would otherwise fall out of scope. This is (once again) + * odd behavior of the Zend engine, but we'll have to live with that + * @var Value + */ + Value _current; + + /** + * The object iterator as is needed by the Zend engine + * @var zend_object_iterator + */ + zend_object_iterator _impl; + + /** + * Get access to all iterator functions + * @return zend_object_iterator_funcs + */ + static zend_object_iterator_funcs *functions(); + + /** + * Is the iterator on a valid position + * @return bool + */ + bool valid() + { + return _iterator->valid(); + } + + /** + * The value at the current position + * @return Value + */ + Value current() + { + return _iterator->current(); + } + + /** + * The key at the current position + * @return Value + */ + Value key() + { + return _iterator->key(); + } + + /** + * Move to the next position + */ + void next() + { + return _iterator->next(); + } + + /** + * Rewind the iterator to the front position + */ + void rewind() + { + return _iterator->rewind(); + } + + /** + * Iterator destructor method + * @param iter + * @param tsrm_ls + */ + static void destructor(zend_object_iterator *iter TSRMLS_DC); + + /** + * Iterator valid function + * Returns FAILURE or SUCCESS + * @param iter + * @param tsrm_ls + * @return int + */ + static int valid(zend_object_iterator *iter TSRMLS_DC); + + /** + * Fetch the current item + * @param iter + * @param data + * @param tsrm_ls + */ + static void current(zend_object_iterator *iter, zval ***data TSRMLS_DC); + + /** + * Fetch the key for the current element (optional, may be NULL). The key + * should be written into the provided zval* using the ZVAL_* macros. If + * this handler is not provided auto-incrementing integer keys will be + * used. + * @param iter + * @param data + * @param tsrm_ls + */ + static void key(zend_object_iterator *iter, zval *data TSRMLS_DC); + + /** + * Function to retrieve the current key, php 5.3 style + * @param iter + * @param str_key + * @param str_key_len + * @param int_key + * @param tsrm_ls + * @return HASH_KEY_IS_STRING or HASH_KEY_IS_LONG + */ + static int key(zend_object_iterator *iter, char **str_key, unsigned int *str_key_len, unsigned long *int_key TSRMLS_DC); + + /** + * Step forwards to the next element + * @param iter + * @param tsrm_ls + */ + static void next(zend_object_iterator *iter TSRMLS_DC); + + /** + * Rewind the iterator back to the start + * @param iter + * @param tsrm_ls + */ + static void rewind(zend_object_iterator *iter TSRMLS_DC); + +public: + /** + * Constructor + * @param iterator The iterator that is implemented by the extension + */ + IteratorImpl(Iterator *iterator) : _iterator(iterator) + { + // initialize impl object + _impl.data = this; + _impl.index = 0; + _impl.funcs = functions(); + } + + /** + * Destructor + */ + virtual ~IteratorImpl() {} + + /** + * Internal method that returns the implementation object + * @return zend_object_iterator + */ + zend_object_iterator *implementation() + { + return &_impl; + } +}; + +/** + * End namespace + */ +} diff --git a/zend/member.h b/zend/member.h new file mode 100644 index 0000000..7aa01d8 --- /dev/null +++ b/zend/member.h @@ -0,0 +1,77 @@ +/** + * Member.h + * + * Base class for properties of a class + * + * @author Emiel Bruijntjes + * @copyright 2013, 2014 Copernica BV + */ + +/** + * Namespace + */ +namespace Php { + +/** + * Class definition + */ +class Member +{ +public: + /** + * Constructor + * @param name Name of the member + * @param flags Flag access to a class member (public, protected etc) + */ + Member(const char *name, int flags) : _name(name), _flags(flags) {} + + /** + * Destructor + */ + virtual ~Member() {} + + /** + * Initialize the member + * @param zend_class_entry + * @param tsrm_ls + */ + void initialize(struct _zend_class_entry *entry TSRMLS_DC) + { + if (_flags == Const) constant(entry TSRMLS_CC); + else declare(entry TSRMLS_CC); + } + +protected: + /** + * Internal method to declare the property as constant + * @param zend_class_entry + * @param tsrm_ls + */ + virtual void constant(struct _zend_class_entry *entry TSRMLS_DC) = 0; + + /** + * Internal method to declare the property + * @param zend_class_entry + * @param tsrm_ls + */ + virtual void declare(struct _zend_class_entry *entry TSRMLS_DC) = 0; + +protected: + /** + * The member name + * @var std::string + */ + std::string _name; + + /** + * The member flags + * @var int + */ + int _flags; +}; + +/** + * End of namespace + */ +} + diff --git a/zend/members.cpp b/zend/members.cpp new file mode 100644 index 0000000..1965807 --- /dev/null +++ b/zend/members.cpp @@ -0,0 +1,90 @@ +/** + * Members.cpp + * + * Implementation of the members class + * + * @author Emiel Bruijntjes + * @copyright 2013 Copernica BV + */ +#include "includes.h" + +/** + * Set up namespace + */ +namespace Php { + +///** +// * Destructor +// */ +//Members::~Members() +//{ +// // check if there are methods +// if (_methods) delete[] _methods; +//} +// +///** +// * Number of methods +// * @return integer +// */ +//int Members::methods() +//{ +// // result variable +// int result = 0; +// +// // loop through the functions +// for (auto it = begin(); it != end(); it++) +// { +// // check if this is a method +// if (it->isMethod()) result++; +// } +// +// // done +// return result; +//} +// +///** +// * Get access to the methods +// * @return Methods +// */ +//struct _zend_function_entry *Members::methods(const char *classname) +//{ +// // already set? +// if (_methods) return _methods; +// +// // the number of methods +// int count = methods(); +// +// // allocate memory for the functions +// _methods = new zend_function_entry[count + 1]; +// +// // keep iterator counter +// int i = 0; +// +// // loop through the functions +// for (auto it = begin(); it != end(); it++) +// { +// // skip if this is not a method +// if (!it->isMethod()) continue; +// +// // retrieve entry +// zend_function_entry *entry = &_methods[i++]; +// +// // let the function fill the entry +// it->initialize(classname, entry); +// } +// +// // last entry should be set to all zeros +// zend_function_entry *last = &_methods[i]; +// +// // all should be set to zero +// memset(last, 0, sizeof(zend_function_entry)); +// +// // done +// return _methods; +//} +// +/** + * End of namespace + */ +} + diff --git a/zend/method.h b/zend/method.h new file mode 100644 index 0000000..dd18a9a --- /dev/null +++ b/zend/method.h @@ -0,0 +1,139 @@ +/** + * Method.h + * + * Internal class that represents a native class method, that can be called + * from PHP scripts. + * + * @author Emiel Bruijntjes + * @copyright 2014 Copernica BV + */ + +/** + * Set up namespace + */ +namespace Php { + +/** + * Class definition + */ +class Method : public Callable +{ +public: + /** + * Constructor + * + * @param name Method name + * @param callback Native callback + * @param flags Access flags + * @param args Argument description + */ + Method(const char *name, const method_callback_0 &callback, int flags, const Arguments &args) : Callable(name, args), _type(0), _flags(flags) { _callback.m0 = callback; } + Method(const char *name, const method_callback_1 &callback, int flags, const Arguments &args) : Callable(name, args), _type(1), _flags(flags) { _callback.m1 = callback; } + Method(const char *name, const method_callback_2 &callback, int flags, const Arguments &args) : Callable(name, args), _type(2), _flags(flags) { _callback.m2 = callback; } + Method(const char *name, const method_callback_3 &callback, int flags, const Arguments &args) : Callable(name, args), _type(3), _flags(flags) { _callback.m3 = callback; } + Method(const char *name, const method_callback_4 &callback, int flags, const Arguments &args) : Callable(name, args), _type(4), _flags(flags) { _callback.m4 = callback; } + Method(const char *name, const method_callback_5 &callback, int flags, const Arguments &args) : Callable(name, args), _type(5), _flags(flags) { _callback.m5 = callback; } + Method(const char *name, const method_callback_6 &callback, int flags, const Arguments &args) : Callable(name, args), _type(6), _flags(flags) { _callback.m6 = callback; } + Method(const char *name, const method_callback_7 &callback, int flags, const Arguments &args) : Callable(name, args), _type(7), _flags(flags) { _callback.m7 = callback; } + Method(const char *name, const native_callback_0 &callback, int flags, const Arguments &args) : Callable(name, args), _type(8), _flags(flags) { _callback.m8 = callback; } + Method(const char *name, const native_callback_1 &callback, int flags, const Arguments &args) : Callable(name, args), _type(9), _flags(flags) { _callback.m9 = callback; } + Method(const char *name, const native_callback_2 &callback, int flags, const Arguments &args) : Callable(name, args), _type(10), _flags(flags) { _callback.m10 = callback; } + Method(const char *name, const native_callback_3 &callback, int flags, const Arguments &args) : Callable(name, args), _type(11), _flags(flags) { _callback.m11 = callback; } + Method(const char *name, int flags, const Arguments &args) : Callable(name, args), _type(9999), _flags(flags) { _callback.m0 = nullptr; } + + /** + * Copy and move constructors + * @param that + */ + Method(const Method &that) : Callable(that), _type(that._type), _flags(that._flags), _callback(that._callback) {} + Method(Method &&that) : Callable(std::move(that)), _type(that._type), _flags(that._flags), _callback(that._callback) {} + + /** + * Destructor + * @param type + * @param callback + */ + virtual ~Method() {} + + /** + * Internal method to fill a function entry + * @param zend_function_entry + * @param classname + */ + void initialize(struct _zend_function_entry *entry, const std::string &classname) + { + // fix the flags, if neither public, private and protected is set, we use public, + // (this solves php warnings if only "final" or only "abstract" is set + if ((_flags & (Public|Private|Protected)) == 0) _flags |= Public; + + // call base + Callable::initialize(entry, classname.c_str(), _flags); + } + + /** + * Invoke the method + * @param parameters + * @return Value + */ + virtual Value invoke(Parameters ¶meters) override + { + // the object to call a method on + Base *base = parameters.object(); + + // find out which method to call, and call it + switch (_type) { + case 0: (base->*_callback.m0)(); return Value(); + case 1: (base->*_callback.m1)(parameters); return Value(); + case 2: return (base->*_callback.m2)(); + case 3: return (base->*_callback.m3)(parameters); + case 4: (base->*_callback.m4)(); return Value(); + case 5: (base->*_callback.m5)(parameters); return Value(); + case 6: return (base->*_callback.m6)(); + case 7: return (base->*_callback.m7)(parameters); + case 8: _callback.m8(); return Value(); + case 9: _callback.m9(parameters); return Value(); + case 10: return _callback.m10(); + case 11: return _callback.m11(parameters); + default: return Value(); + } + } + + +private: + /** + * Callback type + * @var int + */ + int _type; + + /** + * Access flags (protected, public, abstract, final, private, etc) + * @var int + */ + int _flags; + + /** + * The actual callback + * @var void* + */ + union { + method_callback_0 m0; + method_callback_1 m1; + method_callback_2 m2; + method_callback_3 m3; + method_callback_4 m4; + method_callback_5 m5; + method_callback_6 m6; + method_callback_7 m7; + native_callback_0 m8; + native_callback_1 m9; + native_callback_2 m10; + native_callback_3 m11; + } _callback; +}; + +/** + * End of namespace + */ +} + diff --git a/zend/modifiers.cpp b/zend/modifiers.cpp new file mode 100644 index 0000000..3720730 --- /dev/null +++ b/zend/modifiers.cpp @@ -0,0 +1,39 @@ +/** + * Modifiers.cpp + * + * In this file an enumeration type is with the possible + * member modifiers + * + * @author Martijn Otto + * @author Emiel Bruijntjes + * + * @copyright 2014 Copernica BV + */ +#include "includes.h" + +/** + * Set up namespace + */ +namespace Php { + +/** + * The modifiers are constants + */ +const int Static = 0x01; +const int Abstract = 0x02; +const int Final = 0x04; +const int Public = 0x100; +const int Protected = 0x200; +const int Private = 0x400; +const int Const = 0; + +/** + * Modifiers that are supported for methods and properties + */ +const int MethodModifiers = Final | Public | Protected | Private; +const int PropertyModifiers = Final | Public | Protected | Private | Const | Static; + +/** + * End namespace + */ +} diff --git a/zend/namespace.cpp b/zend/namespace.cpp new file mode 100644 index 0000000..bea31a1 --- /dev/null +++ b/zend/namespace.cpp @@ -0,0 +1,134 @@ +/** + * Namespace.cpp + * + * Implementation of the namespace class + * + * @author Emiel Bruijntjes + * @copyright 2014 Copernica BV + */ +#include "includes.h" + +/** + * Open namespace + */ +namespace Php { + +/** + * Add a native function directly to the extension + * @param name Name of the function + * @param function The function to add + * @param arguments Optional argument specification + * @return Namespace Same object to allow chaining + */ +Namespace &Namespace::add(const char *name, const native_callback_0 &function, const Arguments &arguments) +{ + // add a function + _functions.push_back(std::make_shared(name, function, arguments)); + + // allow chaining + return *this; +} + +/** + * Add a native function directly to the extension + * @param name Name of the function + * @param function The function to add + * @param arguments Optional argument specification + * @return Namespace Same object to allow chaining + */ +Namespace &Namespace::add(const char *name, const native_callback_1 &function, const Arguments &arguments) +{ + // add a function + _functions.push_back(std::make_shared(name, function, arguments)); + + // allow chaining + return *this; +} + +/** + * Add a native function directly to the extension + * @param name Name of the function + * @param function The function to add + * @param arguments Optional argument specification + * @return Namespace Same object to allow chaining + */ +Namespace &Namespace::add(const char *name, const native_callback_2 &function, const Arguments &arguments) +{ + // add a function + _functions.push_back(std::make_shared(name, function, arguments)); + + // allow chaining + return *this; +} + +/** + * Add a native function directly to the extension + * @param name Name of the function + * @param function The function to add + * @param arguments Optional argument specification + * @return Namespace Same object to allow chaining + */ +Namespace &Namespace::add(const char *name, const native_callback_3 &function, const Arguments &arguments) +{ + // add a function + _functions.push_back(std::make_shared(name, function, arguments)); + + // allow chaining + return *this; +} + +/** + * Apply a callback to each registered function + * + * The callback will be called with the name of the namespace, and + * a reference to the registered function. + * + * @param callback + */ +void Namespace::apply(const std::function &callback) +{ + // loop through the functions, and apply the callback + for (auto &function : _functions) callback(_name, *function); + + // loop through the other namespaces + for (auto &ns : _namespaces) ns->apply([this, callback](const std::string &ns, Function &func) { + + // if this is the root namespace, we don't have to change the prefix + if (_name.size() == 0) return callback(ns, func); + + // construct a new prefix + // @todo this could be slightly inefficient + return callback(_name + "\\" + ns, func); + }); +} + +/** + * Apply a callback to each registered class + * + * The callback will be called with the name of the namespace, and + * a reference to the registered class. + * + * @param callback + */ +void Namespace::apply(const std::function &callback) +{ + // loop through the classes, and apply the callback + for (auto &c : _classes) callback(_name, *c); + + // loop through the other namespaces + for (auto &ns : _namespaces) ns->apply([this, callback](const std::string &ns, ClassBase &clss) { + + // if this is the root namespace, we don't have to change the prefix + if (_name.size() == 0) return callback(ns, clss); + + // construct a new prefix + // @todo this could be slightly inefficient + return callback(_name + "\\" + ns, clss); + }); +} + +/** + * End namespace + */ +} + diff --git a/zend/notimplemented.h b/zend/notimplemented.h new file mode 100644 index 0000000..ee99254 --- /dev/null +++ b/zend/notimplemented.h @@ -0,0 +1,51 @@ +/** + * NotImplemented.h + * + * Exception that is thrown and catched by the library internally to detect + * if a magic method was implemented or not. + * + * Classes have magic methods (like __unset, __isset, etcetera). These methods + * can be implemented by the extension writer, but they do not have to be. + * + * The default implementation of the methods _could_ be to pass on the method + * to the original Zend engine, but the problem is that the magic methods from + * the PHP-CPP library do not have the same signature as the functions in the + * Zend engine. Passing them on directly is thus not possible. + * + * For that reason, the default implementation throw an exception that is + * immediately caught by the PHP-CPP library, so that it knows that the user + * has not overridden the methods, and the default Zend engine magic method + * can be called instead + * + * @author Emiel Bruijntjes + * @copyright 2014 Copernica BV + */ + +/** + * Set up namespace + */ +namespace Php { + +/** + * Class definition + */ +class NotImplemented : public std::exception +{ +public: + /** + * Constructor + */ + NotImplemented() : std::exception() {} + + /** + * Destructor + */ + virtual ~NotImplemented() throw() {} + +}; + +/** + * End namespace + */ +} + diff --git a/zend/nullmember.h b/zend/nullmember.h new file mode 100644 index 0000000..e035897 --- /dev/null +++ b/zend/nullmember.h @@ -0,0 +1,59 @@ +/** + * NullMember.h + * + * Implementation for a property that is initially set to NULL + * + * @author Emiel Bruijntjes + * @copyright 2013 Copernica BV + */ + +/** + * Set up namespace + */ +namespace Php { + +/** + * Class definition + */ +class NullMember : public Member +{ +public: + /** + * Constructor + * @param name + * @param flags + */ + NullMember(const char *name, int flags) : Member(name, flags) {} + + /** + * Destructor + */ + virtual ~NullMember() {} + + /** + * Internal method to declare the property as constant + * @param zend_class_entry + * @param tsrm_ls + */ + virtual void constant(struct _zend_class_entry *entry TSRMLS_DC) override + { + zend_declare_class_constant_null(entry, _name.c_str(), _name.size() TSRMLS_CC); + } + + /** + * Virtual method to declare the property + * @param entry Class entry + * @param tsrm_ls + */ + virtual void declare(struct _zend_class_entry *entry TSRMLS_DC) override + { + // char* cast is necessary for php 5.3 + zend_declare_property_null(entry, (char *)_name.c_str(), _name.size(), _flags TSRMLS_CC); + } +}; + +/** + * End of namespace + */ +} + diff --git a/zend/numericmember.h b/zend/numericmember.h new file mode 100644 index 0000000..0a040d3 --- /dev/null +++ b/zend/numericmember.h @@ -0,0 +1,67 @@ +/** + * NumericMember.h + * + * Implementation for a property that is initially set to a numeric value + * + * @author Emiel Bruijntjes + * @copyright 2013, 2014 Copernica BV + */ + +/** + * Set up namespace + */ +namespace Php { + +/** + * Class definition + */ +class NumericMember : public Member +{ +private: + /** + * The value + * @var long + */ + long _value; + +public: + /** + * Constructor + * @param name + * @param value + * @param flags + */ + NumericMember(const char *name, long value, int flags) : Member(name, flags), _value(value) {} + + /** + * Destructor + */ + virtual ~NumericMember() {} + + /** + * Declare class constant + * @param entry Class entry + * @param tsrm_ls + */ + virtual void constant(struct _zend_class_entry *entry TSRMLS_DC) override + { + zend_declare_class_constant_long(entry, _name.c_str(), _name.size(), _value TSRMLS_CC); + } + + /** + * Virtual method to declare the property + * @param entry Class entry + * @param tsrm_ls + */ + virtual void declare(struct _zend_class_entry *entry TSRMLS_DC) override + { + // char* cast is necessary for php 5.3 + zend_declare_property_long(entry, (char *)_name.c_str(), _name.size(), _value, _flags TSRMLS_CC); + } +}; + +/** + * End of namespace + */ +} + diff --git a/zend/object.cpp b/zend/object.cpp new file mode 100644 index 0000000..d7fc158 --- /dev/null +++ b/zend/object.cpp @@ -0,0 +1,79 @@ +/** + * Object.cpp + * + * @author Emiel Bruijntjes + * @copyright 2014 Copernica BV + */ +#include "includes.h" + +/** + * Set up namespace + */ +namespace Php { + +/** + * Constructor to create a new instance of a builtin class + * + * @param name Name of the class to instantiate + * @param base Implementation of the class + */ +Object::Object(const char *name, Base *base) +{ + // does the object already have a handle? + if (base->implementation()) + { + // the object is already instantiated, we can assign it the this object + operator=(Value(base)); + } + else + { + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + // this is a brand new object that should be allocated, the C++ instance + // is already there (created by the extension) but it is not yet stored + // in PHP, find out the classname first + auto *entry = zend_fetch_class(name, strlen(name), 0 TSRMLS_CC); + if (!entry) throw Php::Exception(std::string("Unknown class name ") + name); + + // construct an implementation (this will also set the implementation + // member in the base object) + new ObjectImpl(entry, base TSRMLS_CC); + + // now we can store it + operator=(Value(base)); + } +} + +/** + * Internal method to instantiate an object + * @param name + */ +void Object::instantiate(const char *name) +{ + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + // convert the name into a class_entry + auto *entry = zend_fetch_class(name, strlen(name), 0 TSRMLS_CC); + if (!entry) throw Php::Exception(std::string("Unknown class name ") + name); + + // initiate the zval (which was already allocated in the base constructor) + object_init_ex(_val, entry); + + // @todo should we call methods like allocating hashtables, copying and + // initializing properties, et cetera????? In all example you always + // see such complicated and next-to-impossible-to-understand + // sequences of functions being called, but this object_init_ex + // also seems to work... + + // @todo is this a memory leak? the base class first initializes a stdClass, + // and then we overwrite it with a specific class + +} + +/** + * End namespace + */ +} + diff --git a/zend/objectimpl.h b/zend/objectimpl.h new file mode 100644 index 0000000..41bed53 --- /dev/null +++ b/zend/objectimpl.h @@ -0,0 +1,204 @@ +/** + * ObjectImpl.h + * + * Implementation class for Base objects that allow the objects to be stored + * in the Zend engine + * + * @author Emiel Bruijntjes + * @copyright 2014 Copernica BV + */ + +/** + * Set up namespace + */ +namespace Php { + +/** + * Class definition + */ +class ObjectImpl +{ +private: + /** + * Structure with a first element which is a mixed object, so that + * it can be casted to a zend_object + * @var MixedObject + */ + struct MixedObject + { + /** + * The actual object is the first member, so that casting + * the MixedObject to a zend_object will also result in a valid pointer + * @var zend_object + */ + zend_object php; + + /** + * Pointer to ourselves + * @var ObjectImpl + */ + ObjectImpl *self; + + + } _mixed; + + /** + * Pointer to the C++ implementation + * @var Base + */ + Base *_object; + + /** + * The object handle in the Zend engine + * @var int + */ + int _handle; + +public: + /** + * Constructor + * + * This will create a new object in the Zend engine. + * + * @param entry Zend class entry + * @param base C++ object that already exists + * @param tsrm_ls Optional threading data + */ + ObjectImpl(zend_class_entry *entry, Base *base TSRMLS_DC) + { + // copy properties to the mixed object + _mixed.php.ce = entry; + _mixed.self = this; + + // store the c++ object + _object = base; + + // initialize the object + zend_object_std_init(&_mixed.php, entry TSRMLS_CC); + +#if PHP_VERSION_ID < 50399 + + // tmp variable + zval *tmp; + + // initialize the properties, php 5.3 way + zend_hash_copy(_mixed.php.properties, &entry->default_properties, (copy_ctor_func_t) zval_property_ctor, &tmp, sizeof(zval*)); + +#else + + // version higher than 5.3 have an easier way to initialize + object_properties_init(&_mixed.php, entry); + +#endif + +#ifdef ZTS + + // when in thread safety mode, the destruct method and free method have + // an extra parameter holding thread information + using DestructType = void(zend_object*,unsigned int,void***); + using FreeType = void(zend_object*,void***); + +#else + + // not in thread mode: no special parameter for the tsrm_ls variable + using DestructType = void(zend_object*,unsigned int); + using FreeType = void(zend_object*); + +#endif + + // store the two destruct methods in temporary vars + 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... + _handle = zend_objects_store_put(&_mixed, (zend_objects_store_dtor_t)destructMethod, (zend_objects_free_object_storage_t)freeMethod, NULL TSRMLS_CC); + + // the object may remember that we are its implementation object + base->_impl = this; + } + + /** + * Destructor + */ + virtual ~ObjectImpl() + { + // deallocate the cpp object + delete _object; + } + + /** + * Destruct the object + * @param tsrm_ls + */ + void destruct(TSRMLS_D) + { + // pass on to the default destructor + zend_objects_free_object_storage(&_mixed.php TSRMLS_CC); + + // destruct the object + delete this; + } + + /** + * Find the object based on a zval + * @param val Zval object + * @param tsrm_ls Optional pointer to thread info + * @return ObjectImpl + */ + static ObjectImpl *find(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); + + // done + return object->self; + } + + /** + * Find the object based on a zend_object + * @param object Zend object pointer + * @return ObjectImpl + */ + static ObjectImpl *find(const zend_object *object) + { + // retrieve the old object, which we are going to copy + const MixedObject *mixed = (MixedObject *)object; + + // done + return mixed->self; + } + + /** + * Retrieve the base class of the original C++ object + * @return Base + */ + Base *object() + { + return _object; + } + + /** + * Pointer to the PHP object + * @return zend_object + */ + zend_object *php() + { + return &_mixed.php; + } + + /** + * Retrieve the handle object + * @return int + */ + int handle() const + { + return _handle; + } +}; + +/** + * End of namespace + */ +} + diff --git a/zend/origexception.h b/zend/origexception.h new file mode 100644 index 0000000..775e412 --- /dev/null +++ b/zend/origexception.h @@ -0,0 +1,145 @@ +/** + * OrigException.h + * + * Class that wraps around an exception that was thrown by PHP code, + * and that could - but not necessarily has to - be caught by C++ + * + * @author Emiel Bruijntjes + * @copyright 2013 Copernica BV + */ + +/** + * Set up namespace + */ +namespace Php { + +/** + * Class definition + */ +class OrigException : public Value, public Exception +{ +private: + /** + * Is this a an exception that was caught by extension C++ code. + * + * When the object is initially created, we assume that it will be caught + * by C++ code. If it later turns out that the PHP-CPP can catch this + * exception after the extension C++ code ran, the variable is set back + * to false. + * + * @var bool + */ + bool _handled = true; + +#ifdef ZTS + /** + * When we run in multi-thread mode, we store the thread handle + * @var void*** + */ + TSRMLS_D; +#endif + +public: + /** + * Constructor + * @param val + */ + OrigException(zval *val TSRMLS_DC) : + Value(val), Exception("OrigException") + { +#ifdef ZTS + // copy tsrm_ls + this->TSRMLS_C = TSRMLS_C; +#endif + } + + /** + * Copy constructor + * @param exception + */ + OrigException(const OrigException &exception) : + Value(exception), Exception("OrigException"), _handled(exception._handled) + { +#ifdef ZTS + // copy tsrm_ls + TSRMLS_C = exception.TSRMLS_C; +#endif + } + + /** + * Move constructor + * @param exception + */ + OrigException(OrigException &&exception) : + Value(std::move(exception)), Exception("OrigException"), _handled(exception._handled) + { + // set other exception to handled so that it wont do anything on destruction + exception._handled = true; + +#ifdef ZTS + // copy tsrm_ls + TSRMLS_C = exception.TSRMLS_C; +#endif + } + + /** + * Destructor + */ + virtual ~OrigException() throw() + { + // if the exception was not handled by C++ code, we're not going to do anything + // and the exception stays active + if (!_handled) return; + + // the exception was handled, so we should clean it up + zend_clear_exception(TSRMLS_C); + } + + /** + * This is _not_ a native exception, it was thrown by a PHP script + * @return bool + */ + virtual bool native() const override + { + return false; + } + + /** + * Reactivate the exception + */ + void reactivate() + { + // it was not handled by extension C++ code + _handled = false; + } +}; + +/** + * Global function to process an exception + * @param exception + * @param tsrm_ls + */ +inline void process(Exception &exception TSRMLS_DC) +{ + // is this a native exception? + if (exception.native()) + { + // the exception is native, call the zend throw method + zend_throw_exception(zend_exception_get_default(TSRMLS_C), (char *)exception.what(), 0 TSRMLS_CC); + } + else + { + // this is not a native exception, so it was originally thrown by a + // php script, and then not caught by the c++ of the extensiont, we are + // going to tell to the exception that it is still active + OrigException &orig = static_cast(exception); + + // reactive the exception + orig.reactivate(); + } +} + +/** + * End of namespace + */ +} diff --git a/zend/parametersimpl.h b/zend/parametersimpl.h new file mode 100644 index 0000000..fd14238 --- /dev/null +++ b/zend/parametersimpl.h @@ -0,0 +1,53 @@ +/** + * ParametersImpl.h + * + * Extended parameters class that can be instantiated + * + * @author Emiel Bruijntjes + * @copyright 2013 Copernica BV + */ + +/** + * Set up namespace + */ +namespace Php { + +/** + * Class definition + */ +class ParametersImpl : public Parameters +{ +public: + /** + * Constructor + * @param this_ptr Pointer to the object + * @param argc Number of arguments + * @param tsrm_ls + */ + ParametersImpl(zval *this_ptr, int argc TSRMLS_DC) : Parameters(this_ptr ? ObjectImpl::find(this_ptr TSRMLS_CC)->object() : nullptr) + { + // reserve plenty of space + reserve(argc); + + // loop through the arguments + for (int i=0; i + * @copyright 2014 Copernica BV + */ + +/** + * Set up namespace + */ +namespace Php { + +/** + * Class definition + */ +class Property +{ +private: + /** + * The getter + * @var getter_callback + */ + union { + getter_callback_0 g0; + getter_callback_1 g1; + } _getter; + + /** + * The setter + * @var setter_callback + */ + union { + setter_callback_0 s0; + setter_callback_1 s1; + } _setter; + + /** + * Type of getter + * @var char + */ + int _gtype = 0; + + /** + * Type of setter + * @var char + */ + int _stype = 100; + +public: + /** + * Constructor + * @param getter + * @param setter + */ + Property(const getter_callback_0 &getter) : _gtype(0) + { + _getter.g0 = getter; + } + + /** + * Constructor + * @param getter + * @param setter + */ + Property(const getter_callback_1 &getter) : _gtype(1) + { + _getter.g1 = getter; + } + + + /** + * Constructor + * @param getter + * @param setter + */ + Property(const getter_callback_0 &getter, const setter_callback_0 &setter) : _gtype(0), _stype(0) + { + _getter.g0 = getter; + _setter.s0 = setter; + } + + /** + * Constructor + * @param getter + * @param setter + */ + Property(const getter_callback_1 &getter, const setter_callback_0 &setter) : _gtype(1), _stype(0) + { + _getter.g1 = getter; + _setter.s0 = setter; + } + + /** + * Constructor + * @param getter + * @param setter + */ + Property(const getter_callback_0 &getter, const setter_callback_1 &setter) : _gtype(0), _stype(1) + { + _getter.g0 = getter; + _setter.s1 = setter; + } + + /** + * Constructor + * @param getter + * @param setter + */ + Property(const getter_callback_1 &getter, const setter_callback_1 &setter) : _gtype(1), _stype(1) + { + _getter.g1 = getter; + _setter.s1 = setter; + } + + /** + * Copy constructor + * @param that + */ + Property(const Property &that) : + _getter(that._getter), _setter(that._setter), _gtype(that._gtype), _stype(that._stype) {} + + /** + * Destructor + */ + virtual ~Property() {} + + /** + * Get the property + * @param base Object to call it on + * @return Value + */ + Value get(Base *base) + { + if (_gtype == 0) return (base->*_getter.g0)(); + else return (base->*_getter.g1)(); + } + + /** + * Set the property + * @param base Object to call it on + * @param value New value + * @return bool + */ + bool set(Base *base, const Value &value) + { + switch (_stype) { + case 0: (base->*_setter.s0)(value); return true; + case 1: (base->*_setter.s1)(value); return true; + default: return false; + } + } +}; + +/** + * End of namespace + */ +} + diff --git a/zend/streambuf.cpp b/zend/streambuf.cpp new file mode 100644 index 0000000..e258b4e --- /dev/null +++ b/zend/streambuf.cpp @@ -0,0 +1,93 @@ +/** + * StreamBuf.cpp + * + * Implementation file for the StreamBuf class + * + * @see http://www.mr-edd.co.uk/blog/beginners_guide_streambuf + * + * @author Emiel Bruijntjes + * @copyright 2014 Copernica BV + */ +#include "includes.h" + +/** + * Set up namespace + */ +namespace Php { + +/** + * Constructor + * @param error + */ +StreamBuf::StreamBuf(int error) : _error(error) +{ + // we reserve one byte, so that when overflow is called, we still have one + // byte extra in the buffer to put the overflowed byte int + setp(_buffer, _buffer+1024-1); +} + + +/** + * Method that is called when the internal buffer overflows + * @param c + * @return int + */ +int StreamBuf::overflow(int c) +{ + // for error buffers, overflow is simply discarded + if (_error) return c; + + // end-of-file has not output, we call EOF directly, and by using the + // comma operator we ensure that EOF is returned + if (c == EOF) return sync(), EOF; + + // because we lied the underlying buffer about the size of the buffer + // by one byte, there is no real overflow, and we can still add the byte + // to the end of the buffer + *pptr() = c; + + // increment buffer size + pbump(1); + + // and now we're going to syn the buffer + return sync() == -1 ? EOF : c; +} + +/** + * Called when the internal buffer should be synchronized + * @return int + */ +int StreamBuf::sync() +{ + // current buffer size + size_t size = pptr() - pbase(); + + // is this the error stream or the regular output stream? + if (_error) + { + // write to error (the zend_error() method is a varargs function, + // which means that we have to include a printf() like format as first + // parameter. We can not specify pbase() directly, because (1) it is + // not null terminated and (2) it could contain % signs and allow all + // sorts of buffer overflows. + zend_error(_error, "%.*s", (int)size, pbase()); + + } + else + { + // write to zend + zend_write(pbase(), size); + } + + // reset the buffer + pbump(-size); + + // done + return 0; +} + +/** + * End namespace + */ +} + \ No newline at end of file diff --git a/zend/streambuf.h b/zend/streambuf.h new file mode 100644 index 0000000..ed1506a --- /dev/null +++ b/zend/streambuf.h @@ -0,0 +1,80 @@ +/** + * StreamBuf.h + * + * PHP output stream buffer which is used by the Php::out object to + * have an output stream just like the regular std::ostream buffers, + * but that sends all output to PHP output + * + * @author Emiel Bruijntjes + * @copyright 2014 Copernica BV + */ + +/** + * Set up namespace + */ +namespace Php { + +/** + * Class definition + */ +class StreamBuf : public std::streambuf +{ +public: + /** + * Constructor + * @param error the error type, or 0 for regular output + */ + StreamBuf(int error); + + /** + * No copying or moving + * @param that + */ + StreamBuf(const StreamBuf &that) = delete; + StreamBuf(StreamBuf &&that) = delete; + + /** + * Destructor + */ + virtual ~StreamBuf() {} + + /** + * No copying or moving + * @param that + */ + StreamBuf &operator=(const StreamBuf &that) = delete; + StreamBuf &operator=(StreamBuf &&that) = delete; + +protected: + /** + * Method that is called when the internal buffer overflows + * @param c + * @return int + */ + virtual int overflow(int c = EOF) override; + + /** + * Called when the internal buffer should be synchronized + * @return int + */ + virtual int sync() override; + +private: + /** + * The error type, or 0 for regular output + * @var int + */ + int _error; + + /** + * The internal buffer + * @var char[] + */ + char _buffer[1024]; +}; + +/** + * End namespace + */ +} + diff --git a/zend/streams.cpp b/zend/streams.cpp new file mode 100644 index 0000000..ba6d916 --- /dev/null +++ b/zend/streams.cpp @@ -0,0 +1,40 @@ +/** + * Streams.cpp + * + * Implementation of the streams + * + * @author Emiel Bruijntjes + * @copyright 2014 Copernica BV + */ +#include "includes.h" + +/** + * Set up namespace + */ +namespace Php { + +/** + * Some static buffers for writing data + * @var StreamBuf + */ +static StreamBuf bufOut (0); +static StreamBuf bufError (E_ERROR); +static StreamBuf bufWarning (E_WARNING); +static StreamBuf bufNotice (E_NOTICE); +static StreamBuf bufDeprecated (E_DEPRECATED); + +/** + * Create the actual steams + * @var std::ostream + */ +std::ostream out (&bufOut); +std::ostream error (&bufError); +std::ostream warning (&bufWarning); +std::ostream notice (&bufNotice); +std::ostream deprecated (&bufDeprecated); + +/** + * End namespace + */ +} + diff --git a/zend/stringmember.h b/zend/stringmember.h new file mode 100644 index 0000000..e58cd3d --- /dev/null +++ b/zend/stringmember.h @@ -0,0 +1,83 @@ +/** + * StringMember.h + * + * Implementation for a property that is initially set to a strnig value + * + * @author Emiel Bruijntjes + * @copyright 2013 Copernica BV + */ + +/** + * Set up namespace + */ +namespace Php { + +/** + * Class definition + */ +class StringMember : public Member +{ +private: + /** + * The value + * @var string + */ + std::string _value; + +public: + /** + * Constructor + * @param name + * @param value + * @param size + * @param flags + */ + StringMember(const char *name, const char *value, size_t size, int flags) : Member(name, flags), _value(value, size) {} + + /** + * Constructor + * @param name + * @param value + * @param flags + */ + StringMember(const char *name, const char *value, int flags) : StringMember(name, value, strlen(value), flags) {} + + /** + * Constructor + * @param name + * @param value + * @param flags + */ + StringMember(const char *name, const std::string &value, int flags) : Member(name, flags), _value(value) {} + + /** + * Destructor + */ + virtual ~StringMember() {} + + /** + * Virtual method to declare class constant + * @param entry Class entry + * @param tsrm_ls + */ + virtual void constant(struct _zend_class_entry *entry TSRMLS_DC) override + { + zend_declare_class_constant_stringl(entry, _name.c_str(), _name.size(), _value.c_str(), _value.size() TSRMLS_CC); + } + + /** + * Virtual method to declare the property + * @param entry Class entry + */ + virtual void declare(struct _zend_class_entry *entry TSRMLS_DC) override + { + // cast to char* is necessary for php 5.3 + zend_declare_property_stringl(entry, (char *)_name.c_str(), _name.size(), (char *)_value.c_str(), _value.size(), _flags TSRMLS_CC); + } +}; + +/** + * End of namespace + */ +} + diff --git a/zend/super.cpp b/zend/super.cpp new file mode 100644 index 0000000..506d4a5 --- /dev/null +++ b/zend/super.cpp @@ -0,0 +1,71 @@ +/** + * Super.cpp + * + * @copyright 2014 Copernica BV + * @author Emiel Bruijntjes + */ +#include "includes.h" + +/** + * Set up namespace + */ +namespace Php { + +/** + * A number of super-globals are always accessible + */ +Super POST (TRACK_VARS_POST, "_POST"); +Super GET (TRACK_VARS_GET, "_GET"); +Super COOKIE (TRACK_VARS_COOKIE, "_COOKIE"); +Super SERVER (TRACK_VARS_SERVER, "_SERVER"); +Super ENV (TRACK_VARS_ENV, "_ENV"); +Super FILES (TRACK_VARS_FILES, "_FILES"); +Super REQUEST (TRACK_VARS_REQUEST, "_REQUEST"); + +/** + * Array access operator + * This can be used for accessing associative arrays + * @param key + * @return Value + */ +Value Super::operator[](const std::string &key) +{ + // we need the tsrm_ls pointer + TSRMLS_FETCH(); + + // call zend_is_auto_global to ensure that the just-in-time globals are loaded + if (_name) { zend_is_auto_global(_name, strlen(_name) TSRMLS_CC); _name = nullptr; } + + // create a value object that wraps around the actual zval + Value value(PG(http_globals)[_index]); + + // pass on the call + return value[key]; +} + +/** + * Array access operator + * This can be used for accessing associative arrays + * @param key + * @return Value + */ +Value Super::operator[](const char *key) +{ + // we need the tsrm_ls pointer + TSRMLS_FETCH(); + + // call zend_is_auto_global to ensure that the just-in-time globals are loaded + if (_name) { zend_is_auto_global(_name, strlen(_name) TSRMLS_CC); _name = nullptr; } + + // create a value object that wraps around the actual zval + Value value(PG(http_globals)[_index]); + + // pass on the call + return value[key]; +} + +/** + * End namespace + */ +} + diff --git a/zend/traverseiterator.h b/zend/traverseiterator.h new file mode 100644 index 0000000..16f1ce7 --- /dev/null +++ b/zend/traverseiterator.h @@ -0,0 +1,259 @@ +/** + * TraverseIterator.h + * + * When an object from PHP userspace implements its own iterator methods, + * and the Php::Value object is used to iterate over its properties, this + * TraversableIterator class is used to iterate over the properties + * + * @author Emiel Bruijntjes + * @copyright 2014 Copernica BV + */ + +/** + * Set up namespace + */ +namespace Php { + +/** + * Class definition + */ +class TraverseIterator : public ValueIteratorImpl +{ +public: + /** + * Constructor + * @param object + * @param begin + * @param tsrm_ls + */ + TraverseIterator(zval *object, bool begin TSRMLS_DC) : _object(object) + { + // leap out if this iterator starts at the end + if (!begin) return; + + // we need the class entry + auto *entry = zend_get_class_entry(object TSRMLS_CC); + + // create the iterator + _iter = entry->get_iterator(entry, object, false TSRMLS_CC); + + // rewind the iterator + _iter->funcs->rewind(_iter TSRMLS_CC); + + // read the first key/value pair + read(TSRMLS_C); + } + + /** + * Copy constructor + * @param that + * @param tsrm_ls + */ + TraverseIterator(const TraverseIterator &that TSRMLS_DC) : TraverseIterator(that._object, that._iter != nullptr TSRMLS_CC) + { + // @todo this is a broken implementation, the copy is at the start + // position, while we'd like to be at the same position + } + + /** + * Destructor + */ + virtual ~TraverseIterator() + { + // do nothing if iterator is already invalid + if (!_iter) return; + + // we need the tsrm pointer + TSRMLS_FETCH(); + + // call the iterator destructor + if (_iter) _iter->funcs->dtor(_iter TSRMLS_CC); + } + + /** + * Clone the object + * @param tsrm_ls + * @return ValueIteratorImpl* + */ + virtual ValueIteratorImpl *clone() override + { + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + // construct iterator + return new TraverseIterator(*this TSRMLS_CC); + } + + /** + * Increment position (pre-increment) + * @param tsrm_ls + * @return bool + */ + virtual bool increment() override + { + // do we still have an iterator? + if (!_iter) return false; + + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + // movw it forward + _iter->funcs->move_forward(_iter TSRMLS_CC); + + // and read current data + read(TSRMLS_C); + } + + /** + * Decrement position (pre-decrement) + * @return bool + */ + virtual bool decrement() override + { + // not possible with PHP iterators + throw Exception("Impossible to iterate backwards"); + } + + /** + * Compare with other iterator + * @param that + * @return bool + */ + virtual bool equals(const ValueIteratorImpl *that) const override + { + // of course if the objects are identical + if (this == that) return true; + + // cast to traverse-iterator + TraverseIterator *other = (TraverseIterator *)that; + + // if both objects are in an invalid state we consider them to be identical + if (!_iter && !other->_iter) return true; + + // although the iterators could be at the same pos, for simplicity + // we consider them different here + return false; + } + + /** + * Derefecence, this returns a std::pair with the current key and value + * @return std::pair + */ + virtual const std::pair ¤t() const + { + return _data; + } + +private: + /** + * The object that is iterated over + * @var _val + */ + zval *_object = nullptr; + + /** + * The iterator from Zend + * @var zend_object_iterator + */ + struct _zend_object_iterator *_iter = nullptr; + + /** + * Current data + * @var pair + */ + std::pair _data; + + + /** + * Read current data + * @param tsrm_ls + * @return bool + */ + bool read(TSRMLS_D) + { + // not possible when no iterator exists + if (!_iter) return false; + + // is the iterator at a valid position? + if (_iter->funcs->valid(_iter TSRMLS_CC) == FAILURE) return invalidate(TSRMLS_C); + +#if PHP_VERSION_ID >= 50500 + + // create a value object + Value val; + + // call the function to get the key + _iter->funcs->get_current_key(_iter, val._val TSRMLS_CC); + + // store the key + _data.first = val; + +#else + + // variable we need for fetching the key, and that will be assigned by + // the PHP engine (this is php 5.3 code) + char *str_key; unsigned int str_key_len; unsigned long int_key; + + // php 5.4 or php 5.3 code, fetch the current key + int type = _iter->funcs->get_current_key(_iter, &str_key, &str_key_len, &int_key TSRMLS_CC); + + // what sort of key do we have? + if (type == HASH_KEY_IS_LONG) + { + // we have an int key + _data.first = (int64_t)int_key; + } + else + { + // we have a string key that is already allocated + _data.first = str_key; + + // deallocate the data + efree(str_key); + } + +#endif + + // now we're going to fetch the value, for this we need a strange zval + // it is strange that this is a pointer-to-pointer, but that is how + // the Zend engine implements this. It is going to be filled with + // a pointer to a memory address that is guaranteed to hold a valid + // zval. + zval **zval; + + // get the current value + _iter->funcs->get_current_data(_iter, &zval TSRMLS_CC); + + // wrap the zval in a value object + _data.second = Value(*zval); + + // done + return true; + } + + /** + * Invalidate the object + * @param tsrm_ls + * @return bool + */ + bool invalidate(TSRMLS_D) + { + // skip if already invalid + if (!_iter) return false; + + // reset the iterator + _iter->funcs->dtor(_iter TSRMLS_CC); + + // set back to null + _iter = nullptr; + + // done + return false; + } +}; + +/** + * End namespace + */ +} + diff --git a/zend/value.cpp b/zend/value.cpp new file mode 100644 index 0000000..2ca1585 --- /dev/null +++ b/zend/value.cpp @@ -0,0 +1,1911 @@ +/** + * Value.cpp + * + * Implementation for the Value class, which wraps a PHP userspace + * value (a 'zval' in Zend's terminology) into a C++ object + * + * Reminder for the implementer: + * + * A 'zval' is an object that represents a _value_ in the PHP user space, + * and thus not a variable. A 'value' or 'zval' can be used by many + * different variables at the same time. The 'refcount' property of the + * zval holds the number of variables ($a, $b, $c, et cetera) that are + * all linked to the same value. With this system, PHP can implement copy + * on write behavior. + * + * Next to the refcount, the zval also holds a is_ref property, which is + * set to true if all variables linked to the value are references of each + * other. Thus is $a, $b and $c all point to the same variable, and is_ref + * is set to true, changing the value means that the $a, $b and $c value + * are all updated. If is_res was false, a change to $a would not mean a + * change to $b, and the zval should have been copied first. + * + * + * @author Emiel Bruijntjes + * @copyright 2013, 2014 Copernica BV + */ +#include "includes.h" + +/** + * Set up namespace + */ +namespace Php { + +/** + * Constructor (value = NULL) + */ +Value::Value() +{ + // create a null zval + MAKE_STD_ZVAL(_val); + ZVAL_NULL(_val); +} + +/** + * Constructor for null ptr +*/ +Value::Value(std::nullptr_t value) +{ + // create a null zval + MAKE_STD_ZVAL(_val); + ZVAL_NULL(_val); +} + +/** + * Constructor based on integer value + * @param value + */ +Value::Value(int16_t value) +{ + // create an integer zval + MAKE_STD_ZVAL(_val); + ZVAL_LONG(_val, value); +} + +/** + * Constructor based on integer value + * @param value + */ +Value::Value(int32_t value) +{ + // create an integer zval + MAKE_STD_ZVAL(_val); + ZVAL_LONG(_val, value); +} + +/** + * Constructor based on int64_t value + * @param value + */ +Value::Value(int64_t value) +{ + // create an integer zval + MAKE_STD_ZVAL(_val); + ZVAL_LONG(_val, value); +} + +/** + * Constructor based on boolean value + * @param value + */ +Value::Value(bool value) +{ + // create a boolean zval + MAKE_STD_ZVAL(_val); + ZVAL_BOOL(_val, value); +} + +/** + * Constructor based on single character + * @param value + */ +Value::Value(char value) +{ + // create a string zval + MAKE_STD_ZVAL(_val); + ZVAL_STRINGL(_val, &value, 1, 1); +} + +/** + * Constructor based on string value + * @param value + */ +Value::Value(const std::string &value) +{ + // create a string zval + MAKE_STD_ZVAL(_val); + ZVAL_STRINGL(_val, value.c_str(), value.size(), 1); +} + +/** + * Constructor based on a byte array + * @param value + * @param size + */ +Value::Value(const char *value, int size) +{ + // create a string zval + MAKE_STD_ZVAL(_val); + ZVAL_STRINGL(_val, value, size < 0 ? strlen(value) : size, 1); +} + +/** + * Constructor based on decimal value + * @param value + */ +Value::Value(double value) +{ + // create a double zval + MAKE_STD_ZVAL(_val); + ZVAL_DOUBLE(_val, value); +} + +/** + * Wrap object around zval + * @param zval Value to wrap + * @param ref Force this to be a reference + */ +Value::Value(struct _zval_struct *val, bool ref) +{ + // just copy the zval into this object + _val = val; + + // if the variable is not already a reference, and it has more than one + // variable pointing to it, we should seperate it so that any changes + // we're going to make will not change the other variable + if (ref && Z_REFCOUNT_P(_val) > 1) + { + // separate the zval + SEPARATE_ZVAL_IF_NOT_REF(&_val); + } + + // we see ourselves as reference too + Z_ADDREF_P(_val); + + // we're ready if we do not have to force it as a reference + if (!ref || Z_ISREF_P(_val)) return; + + // make this a reference + Z_SET_ISREF_P(_val); +} + +/** + * Wrap around an object + * @param object + */ +Value::Value(const Base *object) +{ + // there are two options: the object was constructed from user space, + // and is already linked to a handle, or it was constructed from C++ + // space, and no handle does yet exist, find the implementation object + auto *impl = object->implementation(); + + // do we have a handle? + if (!impl) throw Php::Exception("Assigning an unassigned object to a variable"); + + // make a regular zval, and set it to an object + MAKE_STD_ZVAL(_val); + Z_TYPE_P(_val) = IS_OBJECT; + Z_OBJ_HANDLE_P(_val) = impl->handle(); + + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + // we have to lookup the object in the object-table + zend_object_store_bucket *obj_bucket = &EG(objects_store).object_buckets[impl->handle()]; + + // this is copy-pasted from zend_objects.c - and it is necessary too! + if (!obj_bucket->bucket.obj.handlers) obj_bucket->bucket.obj.handlers = &std_object_handlers; + + // store the handlers in the zval too (cast is necessary for php 5.3) + Z_OBJ_HT_P(_val) = (zend_object_handlers*)obj_bucket->bucket.obj.handlers; + + // run the copy constructor + zval_copy_ctor(_val); +} + +/** + * Copy constructor + * @param value + */ +Value::Value(const Value &that) +{ + // is the other variable a reference? + if (Z_ISREF_P(that._val)) + { + // because this is supposed to be a COPY, we can not add ourselves + // to the variable but have to allocate a new variable + ALLOC_ZVAL(_val); + INIT_PZVAL_COPY(_val, that._val); + + // we have to call the copy constructor to copy the entire other zval + zval_copy_ctor(_val); + } + else + { + // simply use the same zval + _val = that._val; + } + + // that zval has one more reference + Z_ADDREF_P(_val); + + +// Below the old implementation - I thought really hard about it and I though +// it was a correct and very smart implementation. However, it does not work +// when you swap two variables. I changed it to the implementation above, but +// maybe that implementation introduces other bugs??? Let's keep the old +// implementation for a while in this file, but commented out +// +// // how many references does the other object have? +// if (Z_REFCOUNT_P(that._val) > 1 && !Z_ISREF_P(that._val)) +// { +// // there are already multiple variables linked to this value, and it +// // is not a reference. this implies that we can not turn this variable +// // into a reference, otherwise strange things could happen, we're going +// // to create a new zval +// ALLOC_ZVAL(_val); +// INIT_PZVAL_COPY(_val, that._val); +// zval_copy_ctor(_val); +// } +// else +// { +// // simply use the same zval +// _val = that._val; +// } +// +// // the other object only has one variable using it, or it is already +// // a variable by reference, we can safely add one more reference to it +// // and make it a variable by reference if it was not already a ref +// Z_ADDREF_P(_val); +// +// // make reference +// Z_SET_ISREF_P(_val); +} + +/** + * Move constructor + * @param value + */ +Value::Value(Value &&that) : _val(that._val) +{ + // clear the other object + that._val = nullptr; +} + +/** + * Destructor + */ +Value::~Value() +{ + // ignore if moved + if (!_val) return; + + // if there were two references or less, we're going to remove a reference + // and only one reference will remain, the object will then impossible be + // a reference + if (Z_REFCOUNT_P(_val) <= 2) Z_UNSET_ISREF_P(_val); + + // destruct the zval (this function will decrement the reference counter, + // and only destruct if there are no other references left) + zval_ptr_dtor(&_val); +} + +/** + * Detach the zval + * + * This will unlink the zval internal structure from the Value object, + * so that the destructor will not reduce the number of references and/or + * deallocate the zval structure. This is used for functions that have to + * return a zval pointer, that would otherwise be deallocated the moment + * the function returns. + * + * @return zval + */ +zval *Value::detach() +{ + // leap out if already detached + if (!_val) return nullptr; + + // copy return value + zval *result = _val; + + // decrement reference counter + Z_DELREF_P(_val); + + // reset internal object + _val = nullptr; + + // done + return result; +} + +/** + * Attach a different zval + * + * This will first detach the current zval, and link the Value object to + * a different zval. + * + * @param val + */ +void Value::attach(struct _zval_struct *val) +{ + // detach first + if (_val) detach(); + + // store the zval + _val = val; + + // add one more reference + Z_ADDREF_P(_val); +} + +/** + * Attach a different zval + * + * This will first detach the current zval, and link the Value object to + * a new zval + * + * @param hashtable + */ +void Value::attach(struct _hashtable *hashtable) +{ + // detach first + if (_val) detach(); + + // construct a new zval + MAKE_STD_ZVAL(_val); + + // store pointer to the hashtable, and mark the zval as an array + Z_ARRVAL_P(_val) = hashtable; + Z_TYPE_P(_val) = IS_ARRAY; + + // add a reference + Z_ADDREF_P(_val); +} + +/** + * Retrieve the refcount + * @return int + */ +int Value::refcount() +{ + return Z_REFCOUNT_P(_val); +} + +/** + * Move operator + * @param value + * @return Value + */ +Value &Value::operator=(Value &&value) +{ + // skip self assignment + if (this == &value) return *this; + + // is the object a reference? + if (Z_ISREF_P(_val)) + { + // @todo difference if the other object is a reference or not? + + // the current object is a reference, this means that we should + // keep the zval object, and copy the other value into it, get + // the current refcount + int refcount = Z_REFCOUNT_P(_val); + + // make the copy + *_val = *value._val; + + // restore reference and refcount setting + Z_SET_ISREF_TO_P(_val, true); + Z_SET_REFCOUNT_P(_val, refcount); + + // how many references did the old variable have? + if (Z_REFCOUNT_P(value._val) > 1) + { + // the other object already had multiple references, this + // implies that many other PHP variables are also referring + // to it, and we still need to store its contents, with one + // reference less + Z_DELREF_P(value._val); + + // and we need to run the copy constructor on the current + // value, because we're making a deep copy + zval_copy_ctor(_val); + } + else + { + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + // the last and only reference to the other object was + // removed, we no longer need it + FREE_ZVAL(value._val); + + // the other object is no longer valid + value._val = nullptr; + } + } + else + { + // destruct the zval (this function will decrement the reference counter, + // and only destruct if there are no other references left) + zval_ptr_dtor(&_val); + + // just copy the zval completely + _val = value._val; + + // the other object is no longer valid + value._val = nullptr; + } + + // done + return *this; +} + +/** + * Assignment operator + * @param value + * @return Value + */ +Value &Value::operator=(const Value &value) +{ + // skip self assignment + if (this == &value) return *this; + + // is the object a reference? + if (Z_ISREF_P(_val)) + { + // the current object is a reference, this means that we should + // keep the zval object, and copy the other value into it, get + // the current refcount + int refcount = Z_REFCOUNT_P(_val); + + // clean up the current zval (but keep the zval structure) + zval_dtor(_val); + + // make the copy + *_val = *value._val; + zval_copy_ctor(_val); + + // restore refcount and reference setting + Z_SET_ISREF_TO_P(_val, true); + Z_SET_REFCOUNT_P(_val, refcount); + } + else + { + // destruct the zval (this function will decrement the reference counter, + // and only destruct if there are no other references left) + zval_ptr_dtor(&_val); + + // just copy the zval, and the refcounter + _val = value._val; + + // and we have one more reference + Z_ADDREF_P(_val); + } + + // update the object + return *this; +} + + +/** + * Assignment operator + * @param value + * @return Value + */ +Value &Value::operator=(std::nullptr_t value) +{ + // if this is not a reference variable, we should detach it to implement copy on write + SEPARATE_ZVAL_IF_NOT_REF(&_val); + + // deallocate current zval (without cleaning the zval structure) + zval_dtor(_val); + + // change to null value + ZVAL_NULL(_val); + + // update the object + return *this; +} + +/** + * Assignment operator + * @param value + * @return Value + */ +Value &Value::operator=(int16_t value) +{ + // if this is not a reference variable, we should detach it to implement copy on write + SEPARATE_ZVAL_IF_NOT_REF(&_val); + + // deallocate current zval (without cleaning the zval structure) + zval_dtor(_val); + + // set new value + ZVAL_LONG(_val, value); + + // update the object + return *this; +} + +/** + * Assignment operator + * @param value + * @return Value + */ +Value &Value::operator=(int32_t value) +{ + // if this is not a reference variable, we should detach it to implement copy on write + SEPARATE_ZVAL_IF_NOT_REF(&_val); + + // deallocate current zval (without cleaning the zval structure) + zval_dtor(_val); + + // set new value + ZVAL_LONG(_val, value); + + // update the object + return *this; +} + +/** + * Assignment operator + * @param value + * @return Value + */ +Value &Value::operator=(int64_t value) +{ + // if this is not a reference variable, we should detach it to implement copy on write + SEPARATE_ZVAL_IF_NOT_REF(&_val); + + // deallocate current zval (without cleaning the zval structure) + zval_dtor(_val); + + // set new value + ZVAL_LONG(_val, value); + + // update the object + return *this; +} + +/** + * Assignment operator + * @param value + * @return Value + */ +Value &Value::operator=(bool value) +{ + // if this is not a reference variable, we should detach it to implement copy on write + SEPARATE_ZVAL_IF_NOT_REF(&_val); + + // deallocate current zval (without cleaning the zval structure) + zval_dtor(_val); + + // set new value + ZVAL_BOOL(_val, value); + + // update the object + return *this; +} + +/** + * Assignment operator + * @param value + * @return Value + */ +Value &Value::operator=(char value) +{ + // if this is not a reference variable, we should detach it to implement copy on write + SEPARATE_ZVAL_IF_NOT_REF(&_val); + + // deallocate current zval (without cleaning the zval structure) + zval_dtor(_val); + + // set new value + ZVAL_STRINGL(_val, &value, 1, 1); + + // update the object + return *this; +} + +/** + * Assignment operator + * @param value + * @return Value + */ +Value &Value::operator=(const std::string &value) +{ + // if this is not a reference variable, we should detach it to implement copy on write + SEPARATE_ZVAL_IF_NOT_REF(&_val); + + // deallocate current zval (without cleaning the zval structure) + zval_dtor(_val); + + // set new value + ZVAL_STRINGL(_val, value.c_str(), value.size(), 1); + + // update the object + return *this; +} + +/** + * Assignment operator + * @param value + * @return Value + */ +Value &Value::operator=(const char *value) +{ + // if this is not a reference variable, we should detach it to implement copy on write + SEPARATE_ZVAL_IF_NOT_REF(&_val); + + // deallocate current zval (without cleaning the zval structure) + zval_dtor(_val); + + // set new value + ZVAL_STRING(_val, value, 1); + + // update the object + return *this; +} + +/** + * Assignment operator + * @param value + * @return Value + */ +Value &Value::operator=(double value) +{ + // if this is not a reference variable, we should detach it to implement copy on write + SEPARATE_ZVAL_IF_NOT_REF(&_val); + + // deallocate current zval (without cleaning the zval structure) + zval_dtor(_val); + + // set new value + ZVAL_DOUBLE(_val, value); + + // update the object + return *this; +} + +/** + * Add a value to the object + * @param value + * @return Value + */ +Value &Value::operator+=(const Value &value) { return Arithmetic(this).assign(value); } +Value &Value::operator+=(int16_t value) { return Arithmetic(this).assign(value); } +Value &Value::operator+=(int32_t value) { return Arithmetic(this).assign(value); } +Value &Value::operator+=(int64_t value) { return Arithmetic(this).assign(value); } +Value &Value::operator+=(bool value) { return Arithmetic(this).assign(value); } +Value &Value::operator+=(char value) { return Arithmetic(this).assign(value); } +Value &Value::operator+=(const std::string &value) { return Arithmetic(this).assign(value); } +Value &Value::operator+=(const char *value) { return Arithmetic(this).assign(value); } +Value &Value::operator+=(double value) { return Arithmetic(this).assign(value); } + +/** + * Subtract a value from the object + * @param value + * @return Value + */ +Value &Value::operator-=(const Value &value) { return Arithmetic(this).assign(value); } +Value &Value::operator-=(int16_t value) { return Arithmetic(this).assign(value); } +Value &Value::operator-=(int32_t value) { return Arithmetic(this).assign(value); } +Value &Value::operator-=(int64_t value) { return Arithmetic(this).assign(value); } +Value &Value::operator-=(bool value) { return Arithmetic(this).assign(value); } +Value &Value::operator-=(char value) { return Arithmetic(this).assign(value); } +Value &Value::operator-=(const std::string &value) { return Arithmetic(this).assign(value); } +Value &Value::operator-=(const char *value) { return Arithmetic(this).assign(value); } +Value &Value::operator-=(double value) { return Arithmetic(this).assign(value); } + +/** + * Multiply the object with a certain value + * @param value + * @return Value + */ +Value &Value::operator*=(const Value &value) { return Arithmetic(this).assign(value); } +Value &Value::operator*=(int16_t value) { return Arithmetic(this).assign(value); } +Value &Value::operator*=(int32_t value) { return Arithmetic(this).assign(value); } +Value &Value::operator*=(int64_t value) { return Arithmetic(this).assign(value); } +Value &Value::operator*=(bool value) { return Arithmetic(this).assign(value); } +Value &Value::operator*=(char value) { return Arithmetic(this).assign(value); } +Value &Value::operator*=(const std::string &value) { return Arithmetic(this).assign(value); } +Value &Value::operator*=(const char *value) { return Arithmetic(this).assign(value); } +Value &Value::operator*=(double value) { return Arithmetic(this).assign(value); } + +/** + * Divide the object with a certain value + * @param value + * @return Value + */ +Value &Value::operator/=(const Value &value) { return Arithmetic(this).assign(value); } +Value &Value::operator/=(int16_t value) { return Arithmetic(this).assign(value); } +Value &Value::operator/=(int32_t value) { return Arithmetic(this).assign(value); } +Value &Value::operator/=(int64_t value) { return Arithmetic(this).assign(value); } +Value &Value::operator/=(bool value) { return Arithmetic(this).assign(value); } +Value &Value::operator/=(char value) { return Arithmetic(this).assign(value); } +Value &Value::operator/=(const std::string &value) { return Arithmetic(this).assign(value); } +Value &Value::operator/=(const char *value) { return Arithmetic(this).assign(value); } +Value &Value::operator/=(double value) { return Arithmetic(this).assign(value); } + +/** + * Divide the object with a certain value and get the rest + * Note that this does not use the Arithmetic object, because no conversion between floats is necessary + * @param value + * @return Value + */ +Value &Value::operator%=(const Value &value) { return operator=(numericValue() % value.numericValue()); } +Value &Value::operator%=(int16_t value) { return operator=(numericValue() % value); } +Value &Value::operator%=(int32_t value) { return operator=(numericValue() % value); } +Value &Value::operator%=(int64_t value) { return operator=(numericValue() % value); } +Value &Value::operator%=(bool value) { return operator=(numericValue() % value); } +Value &Value::operator%=(char value) { return operator=(numericValue() % value); } +Value &Value::operator%=(const std::string &value) { return operator=(numericValue() % atoi(value.c_str())); } +Value &Value::operator%=(const char *value) { return operator=(numericValue() % atoi(value)); } +Value &Value::operator%=(double value) { return operator=(numericValue() % (int)value); } + +/** + * Assignment operator + * @param value + * @return Value + */ +Value Value::operator+(const Value &value) { return Arithmetic(this).apply(value); } +Value Value::operator+(int16_t value) { return Arithmetic(this).apply(value); } +Value Value::operator+(int32_t value) { return Arithmetic(this).apply(value); } +Value Value::operator+(int64_t value) { return Arithmetic(this).apply(value); } +Value Value::operator+(bool value) { return Arithmetic(this).apply(value); } +Value Value::operator+(char value) { return Arithmetic(this).apply(value); } +Value Value::operator+(const std::string &value) { return Arithmetic(this).apply(value); } +Value Value::operator+(const char *value) { return Arithmetic(this).apply(value); } +Value Value::operator+(double value) { return Arithmetic(this).apply(value); } + +/** + * Subtraction operator + * @param value + * @return Value + */ +Value Value::operator-(const Value &value) { return Arithmetic(this).apply(value); } +Value Value::operator-(int16_t value) { return Arithmetic(this).apply(value); } +Value Value::operator-(int32_t value) { return Arithmetic(this).apply(value); } +Value Value::operator-(int64_t value) { return Arithmetic(this).apply(value); } +Value Value::operator-(bool value) { return Arithmetic(this).apply(value); } +Value Value::operator-(char value) { return Arithmetic(this).apply(value); } +Value Value::operator-(const std::string &value) { return Arithmetic(this).apply(value); } +Value Value::operator-(const char *value) { return Arithmetic(this).apply(value); } +Value Value::operator-(double value) { return Arithmetic(this).apply(value); } + +/** + * Multiplication operator + * @param value + * @return Value + */ +Value Value::operator*(const Value &value) { return Arithmetic(this).apply(value); } +Value Value::operator*(int16_t value) { return Arithmetic(this).apply(value); } +Value Value::operator*(int32_t value) { return Arithmetic(this).apply(value); } +Value Value::operator*(int64_t value) { return Arithmetic(this).apply(value); } +Value Value::operator*(bool value) { return Arithmetic(this).apply(value); } +Value Value::operator*(char value) { return Arithmetic(this).apply(value); } +Value Value::operator*(const std::string &value) { return Arithmetic(this).apply(value); } +Value Value::operator*(const char *value) { return Arithmetic(this).apply(value); } +Value Value::operator*(double value) { return Arithmetic(this).apply(value); } + +/** + * Division operator + * @param value + * @return Value + */ +Value Value::operator/(const Value &value) { return Arithmetic(this).apply(value); } +Value Value::operator/(int16_t value) { return Arithmetic(this).apply(value); } +Value Value::operator/(int32_t value) { return Arithmetic(this).apply(value); } +Value Value::operator/(int64_t value) { return Arithmetic(this).apply(value); } +Value Value::operator/(bool value) { return Arithmetic(this).apply(value); } +Value Value::operator/(char value) { return Arithmetic(this).apply(value); } +Value Value::operator/(const std::string &value) { return Arithmetic(this).apply(value); } +Value Value::operator/(const char *value) { return Arithmetic(this).apply(value); } +Value Value::operator/(double value) { return Arithmetic(this).apply(value); } + +/** + * Modulus operator + * @param value + * @return Value + */ +Value Value::operator%(const Value &value) { return Value(numericValue() % value.numericValue()); } +Value Value::operator%(int16_t value) { return Value(numericValue() % value); } +Value Value::operator%(int32_t value) { return Value(numericValue() % value); } +Value Value::operator%(int64_t value) { return Value(numericValue() % value); } +Value Value::operator%(bool value) { return Value(numericValue() % value); } +Value Value::operator%(char value) { return Value(numericValue() % value); } +Value Value::operator%(const std::string &value) { return Value(numericValue() % atoi(value.c_str())); } +Value Value::operator%(const char *value) { return Value(numericValue() % atoi(value)); } +Value Value::operator%(double value) { return Value(numericValue() % (int)value); } + +/** + * Call the function in PHP + * We have ten variants of this function, depending on the number of parameters + * This call operator is only useful when the variable represents a callable + * @param p0-p10 Parameters of the function to be called. + * @return Value + */ +Value Value::operator()() const +{ + // call with zero parameters + return exec(0, NULL); +} + +/** + * Call the function - if the variable holds a callable thing + * @param p0 The first parameter + * @return Value + */ +Value Value::operator()(Value p0) const +{ + // array of parameters + zval **params[1] = { &p0._val }; + + // call the function + return exec(1, params); +} + +/** + * Call the function - if the variable holds a callable thing + * @param p0 The first parameter + * @param p1 The second parameter + * @return Value + */ +Value Value::operator()(Value p0, Value p1) const +{ + // array of parameters + zval **params[2] = { &p0._val, &p1._val }; + + // call the function + return exec(2, params); +} + +/** + * Call the function - if the variable holds a callable thing + * @param p0 The first parameter + * @param p1 The second parameter + * @param p2 The third parameter + * @return Value + */ +Value Value::operator()(Value p0, Value p1, Value p2) const +{ + // array of parameters + zval **params[3] = { &p0._val, &p1._val, &p2._val }; + + // call the function + return exec(3, params); +} + +/** + * Call the function - if the variable holds a callable thing + * @param p0 The first parameter + * @param p1 The second parameter + * @param p2 The third parameter + * @param p3 The fourth parameter + * @return Value + */ +Value Value::operator()(Value p0, Value p1, Value p2, Value p3) const +{ + // array of parameters + zval **params[4] = { &p0._val, &p1._val, &p2._val, &p3._val }; + + // call the function + return exec(4, params); +} + +/** + * Call the function - if the variable holds a callable thing + * @param p0 The first parameter + * @param p1 The second parameter + * @param p2 The third parameter + * @param p3 The fourth parameter + * @param p4 The fifth parameter + * @return Value + */ +Value Value::operator()(Value p0, Value p1, Value p2, Value p3, Value p4) const +{ + // array of parameters + zval **params[5] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val }; + + // call the function + return exec(5, params); +} + +/** + * Call the function - if the variable holds a callable thing + * @param p0 The first parameter + * @param p1 The second parameter + * @param p2 The third parameter + * @param p3 The fourth parameter + * @param p4 The fifth parameter + * @param p5 The sixth parameter + * @return Value + */ +Value Value::operator()(Value p0, Value p1, Value p2, Value p3, Value p4, Value p5) const +{ + // array of parameters + zval **params[6] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val }; + + // call the function + return exec(6, params); +} + +/** + * Call the function - if the variable holds a callable thing + * @param p0 The first parameter + * @param p1 The second parameter + * @param p2 The third parameter + * @param p3 The fourth parameter + * @param p4 The fifth parameter + * @param p5 The sixth parameter + * @param p6 The seventh parameter + * @return Value + */ +Value Value::operator()(Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6) const +{ + // array of parameters + zval **params[7] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val }; + + // call the function + return exec(7, params); +} + +/** + * Call the function - if the variable holds a callable thing + * @param p0 The first parameter + * @param p1 The second parameter + * @param p2 The third parameter + * @param p3 The fourth parameter + * @param p4 The fifth parameter + * @param p5 The sixth parameter + * @param p6 The seventh parameter + * @param p7 The eighth parameter + * @return Value + */ +Value Value::operator()(Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7) const +{ + // array of parameters + zval **params[8] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val, &p7._val }; + + // call the function + return exec(8, params); +} + +/** + * Call the function - if the variable holds a callable thing + * @param p0 The first parameter + * @param p1 The second parameter + * @param p2 The third parameter + * @param p3 The fourth parameter + * @param p4 The fifth parameter + * @param p5 The sixth parameter + * @param p6 The seventh parameter + * @param p7 The eighth parameter + * @param p8 The ninth parameter + * @return Value + */ +Value Value::operator()(Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7, Value p8) const +{ + // array of parameters + zval **params[9] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val, &p7._val, &p8._val }; + + // call the function + return exec(9, params); +} + +/** + * Call the function - if the variable holds a callable thing + * @param p0 The first parameter + * @param p1 The second parameter + * @param p2 The third parameter + * @param p3 The fourth parameter + * @param p4 The fifth parameter + * @param p5 The sixth parameter + * @param p6 The seventh parameter + * @param p7 The eighth parameter + * @param p8 The ninth parameter + * @param p9 The tenth parameter + * @return Value + */ +Value Value::operator()(Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7, Value p8, Value p9) const +{ + // array of parameters + zval **params[10] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val, &p7._val, &p8._val, &p9._val }; + + // call the function + return exec(10, params); +} + +/** + * Call the method - if the variable holds an object with the given method + * @param name name of the method to call + * @return Value + */ +Value Value::call(const char *name) +{ + // call with zero parameters + return exec(name, 0, NULL); +} + +/** + * Call the method - if the variable holds an object with the given method + * @param name name of the method to call + * @param p0 The first parameter + * @return Value + */ +Value Value::call(const char *name, Value p0) +{ + // array of parameters + zval **params[] = { &p0._val }; + + // call with zero parameters + return exec(name, 1, params); +} + +/** + * Call the method - if the variable holds an object with the given method + * @param name name of the method to call + * @param p0 The first parameter + * @param p1 The second parameter + * @return Value + */ +Value Value::call(const char *name, Value p0, Value p1) +{ + // array of parameters + zval **params[] = { &p0._val, &p1._val }; + + // call with zero parameters + return exec(name, 2, params); +} + +/** + * Call the method - if the variable holds an object with the given method + * @param name name of the method to call + * @param p0 The first parameter + * @param p1 The second parameter + * @param p2 The third parameter + * @return Value + */ +Value Value::call(const char *name, Value p0, Value p1, Value p2) +{ + // array of parameters + zval **params[] = { &p0._val, &p1._val, &p2._val }; + + // call with zero parameters + return exec(name, 3, params); +} + +/** + * Call the method - if the variable holds an object with the given method + * @param name name of the method to call + * @param p0 The first parameter + * @param p1 The second parameter + * @param p2 The third parameter + * @param p3 The fourth parameter + * @return Value + */ +Value Value::call(const char *name, Value p0, Value p1, Value p2, Value p3) +{ + // array of parameters + zval **params[] = { &p0._val, &p1._val, &p2._val, &p3._val }; + + // call with zero parameters + return exec(name, 4, params); +} + +/** + * Call the method - if the variable holds an object with the given method + * @param name name of the method to call + * @param p0 The first parameter + * @param p1 The second parameter + * @param p2 The third parameter + * @param p3 The fourth parameter + * @param p4 The fifth parameter + * @return Value + */ +Value Value::call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4) +{ + // array of parameters + zval **params[] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val }; + + // call with zero parameters + return exec(name, 5, params); +} + +/** + * Call the method - if the variable holds an object with the given method + * @param name name of the method to call + * @param p0 The first parameter + * @param p1 The second parameter + * @param p2 The third parameter + * @param p3 The fourth parameter + * @param p4 The fifth parameter + * @param p5 The sixth parameter + * @return Value + */ +Value Value::call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5) +{ + // array of parameters + zval **params[] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val }; + + // call with zero parameters + return exec(name, 6, params); +} + +/** + * Call the method - if the variable holds an object with the given method + * @param name name of the method to call + * @param p0 The first parameter + * @param p1 The second parameter + * @param p2 The third parameter + * @param p3 The fourth parameter + * @param p4 The fifth parameter + * @param p5 The sixth parameter + * @param p6 The seventh parameter + * @return Value + */ +Value Value::call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6) +{ + // array of parameters + zval **params[] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val }; + + // call with zero parameters + return exec(name, 7, params); +} + +/** + * Call the method - if the variable holds an object with the given method + * @param name name of the method to call + * @param p0 The first parameter + * @param p1 The second parameter + * @param p2 The third parameter + * @param p3 The fourth parameter + * @param p4 The fifth parameter + * @param p5 The sixth parameter + * @param p6 The seventh parameter + * @param p7 The eighth parameter + * @return Value + */ +Value Value::call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7) +{ + // array of parameters + zval **params[] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val, &p7._val }; + + // call with zero parameters + return exec(name, 8, params); +} + +/** + * Call the method - if the variable holds an object with the given method + * @param name name of the method to call + * @param p0 The first parameter + * @param p1 The second parameter + * @param p2 The third parameter + * @param p3 The fourth parameter + * @param p4 The fifth parameter + * @param p5 The sixth parameter + * @param p6 The seventh parameter + * @param p7 The eighth parameter + * @param p8 The ninth parameter + * @return Value + */ +Value Value::call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7, Value p8) +{ + // array of parameters + zval **params[] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val, &p7._val, &p8._val }; + + // call with zero parameters + return exec(name, 9, params); +} + +/** + * Call the method - if the variable holds an object with the given method + * @param name name of the method to call + * @param p0 The first parameter + * @param p1 The second parameter + * @param p2 The third parameter + * @param p3 The fourth parameter + * @param p4 The fifth parameter + * @param p5 The sixth parameter + * @param p6 The seventh parameter + * @param p7 The eighth parameter + * @param p8 The ninth parameter + * @param p9 The tenth parameter + * @return Value + */ +Value Value::call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7, Value p8, Value p9) +{ + // array of parameters + zval **params[] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val, &p7._val, &p8._val, &p9._val }; + + // call with zero parameters + return exec(name, 10, params); +} + +/** + * Helper function that runs the actual call + * @param object The object to call it on + * @param method The function or method to call + * @param args Number of arguments + * @param params The parameters + * @return Value + */ +static Value do_exec(zval **object, zval *method, int argc, zval ***params) +{ + // the return zval + zval *retval = nullptr; + + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + // the current exception + zval *oldException = EG(exception); + + // call the function + if (call_user_function_ex(CG(function_table), object, method, &retval, argc, params, 1, NULL TSRMLS_CC) != SUCCESS) + { + // throw an exception, the function does not exist + throw Exception("Invalid call to "+Value(method).stringValue()); + + // unreachable, but let's return at least something to prevent compiler warnings + return nullptr; + } + else + { + // was an exception thrown inside the function? In that case we throw a C++ new exception + // to give the C++ code the chance to catch it + if (oldException != EG(exception) && EG(exception)) throw OrigException(EG(exception) TSRMLS_CC); + + // no (additional) exception was thrown + return retval ? Value(retval) : nullptr; + } +} + +/** + * Call function with a number of parameters + * @param argc Number of parameters + * @param argv The parameters + * @return Value + */ +Value Value::exec(int argc, zval ***params) const +{ + // call helper function + return do_exec(nullptr, _val, argc, params); +} + +/** + * Call method with a number of parameters + * @param name Name of method to call + * @param argc Number of parameters + * @param argv The parameters + * @return Value + */ +Value Value::exec(const char *name, int argc, struct _zval_struct ***params) +{ + // wrap the name in a Php::Value object to get a zval + Value method(name); + + // call helper function + return do_exec(&_val, method._val, argc, params); +} + +/** + * The type of object + * @return Type + */ +Type Value::type() const +{ + // return regular type + return (Type)Z_TYPE_P(_val); +} + +/** + * Change the internal type + * @param type + * @return Value + */ +Value &Value::setType(Type type) +{ + // skip if nothing changes + if (this->type() == type) return *this; + + // if this is not a reference variable, we should detach it to implement copy on write + SEPARATE_ZVAL_IF_NOT_REF(&_val); + + // run the conversion + switch (type) { + case Type::Null: convert_to_null(_val); break; + case Type::Numeric: convert_to_long(_val); break; + case Type::Float: convert_to_double(_val); break; + case Type::Bool: convert_to_boolean(_val); break; + case Type::Array: convert_to_array(_val); break; + case Type::Object: convert_to_object(_val); break; + case Type::String: convert_to_string(_val); break; + case Type::Resource: throw Php::Exception("Resource types can not be handled by the PHP-CPP library"); break; + case Type::Constant: throw Php::Exception("Constant types can not be assigned to a PHP-CPP library variable"); break; + case Type::ConstantArray: throw Php::Exception("Constant types can not be assigned to a PHP-CPP library variable"); break; + case Type::Callable: throw Php::Exception("Callable types can not be assigned to a PHP-CPP library variable"); break; + } + + // done + return *this; +} + +/** + * Check if the variable holds something that is callable + * @return bool + */ +bool Value::isCallable() const +{ + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + // we can not rely on the type, because strings can be callable as well + return zend_is_callable(_val, 0, NULL TSRMLS_CC); +} + +/** + * Make a clone of the type + * @return Value + */ +Value Value::clone() const +{ + // the zval that will hold the copy + zval *copy; + + // allocate memory + ALLOC_ZVAL(copy); + + // copy the data + INIT_PZVAL_COPY(copy, _val); + + // run the copy constructor to ensure that everything gets copied + zval_copy_ctor(copy); + + // done + return Value(copy); +} + +/** + * Clone the zval to a different type + * @param type + * @return Value + */ +Value Value::clone(Type type) const +{ + // regular clone if nothing changes + if (this->type() == type) return clone(); + + // make a clone + return clone().setType(type); +} + +/** + * Retrieve the value as integer + * @return long + */ +int64_t Value::numericValue() const +{ + // already a long? + if (isNumeric()) return Z_LVAL_P(_val); + + // make a clone + return clone(Type::Numeric).numericValue(); +} + +/** + * Retrieve the value as boolean + * @return bool + */ +bool Value::boolValue() const +{ + // already a bool? + if (isBool()) return Z_BVAL_P(_val); + + // make a clone + return clone(Type::Bool).boolValue(); +} + +/** + * Retrieve the value as string + * @return string + */ +std::string Value::stringValue() const +{ + // already a string? + if (isString()) return std::string(Z_STRVAL_P(_val), Z_STRLEN_P(_val)); + + // make a clone + return clone(Type::String).stringValue(); +} + +/** + * Access to the raw buffer + * @return char * + */ +char *Value::buffer() const +{ + // must be a string + if (!isString()) return nullptr; + + // already a string? + return Z_STRVAL_P(_val); +} + +/** + * Reserve enough space + * @param size + * @return char* + */ +char *Value::reserve(size_t size) +{ + // must be a string + setType(Type::String); + + // is the current buffer too small? + if (Z_STRLEN_P(_val) < (int)size) + { + // is there already a buffer? + if (!Z_STRVAL_P(_val)) Z_STRVAL_P(_val) = (char *)emalloc(size+1); + + // reallocate an existing buffer + else Z_STRVAL_P(_val) = (char *)erealloc(Z_STRVAL_P(_val), size+1); + + // last byte should be zero + Z_STRVAL_P(_val)[size] = 0; + } + + // store size + Z_STRLEN_P(_val) = size; + + // done + return Z_STRVAL_P(_val); +} + +/** + * Access to the raw buffer + * @return const char * + */ +const char *Value::rawValue() const +{ + // must be a string + if (isString()) return Z_STRVAL_P(_val); + + // make a clone and return that buffer + return clone(Type::String).rawValue(); +} + +/** + * Retrieve the value as decimal + * @return double + */ +double Value::floatValue() const +{ + // already a double + if (isFloat()) return Z_DVAL_P(_val); + + // make a clone + return clone(Type::Float).floatValue(); +} + +/** + * The number of members in case of an array or object + * @return int + */ +int Value::size() const +{ + // is it an array? + if (isArray()) + { + // get the number of elements + return zend_hash_num_elements(Z_ARRVAL_P(_val)); + } + + // or an object? + else if (isObject()) + { + // the count_elements member function should be defined + if (!Z_OBJ_HT_P(_val)->count_elements) return 0; + + // create a variable to hold the result + long result; + + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + // call the function + return Z_OBJ_HT_P(_val)->count_elements(_val, &result TSRMLS_CC) == SUCCESS ? result : 0; + } + + // not an array, return string size if this is a string + else if (isString()) + { + // get string size + return Z_STRLEN_P(_val); + } + + // in all other situations, we convert the variable to a string + else + { + // make a copy + Value copy(*this); + + // convert the copy to a string + copy.setType(Type::String); + + // return the string size + return copy.size(); + } +} + +/** + * Convert the object to a map with string index and Php::Value value + * @return std::map + */ +std::map Value::mapValue() const +{ + // result variable + std::map result; + + // iterate over the object + for (auto &iter : *this) result[iter.first.rawValue()] = iter.second; + + // done + return result; +} + +/** + * Internal helper method to retrieve an iterator + * @param begin Should the iterator start at the begin + * @return iterator + */ +ValueIterator Value::createIterator(bool begin) const +{ + // check type + if (isArray()) return ValueIterator(new HashIterator(Z_ARRVAL_P(_val), begin)); + + // get access to the hast table + if (isObject()) + { + // we need the TSRMLS_CC variable + TSRMLS_FETCH(); + + // is a special iterator method defined in the class entry? + auto *entry = zend_get_class_entry(_val TSRMLS_CC); + + // check if there is an iterator + if (entry->get_iterator) + { + // the object implements Traversable interface, we have to use a + // special iterator to user that interface too + return ValueIterator(new TraverseIterator(_val, begin TSRMLS_CC)); + } + else + { + // construct a regular iterator + return ValueIterator(new HashIterator(Z_OBJ_HT_P(_val)->get_properties(_val TSRMLS_CC), begin)); + } + } + + // invalid + return ValueIterator(new InvalidIterator()); +} + +/** + * Return an iterator for iterating over the values + * This is only meaningful for Value objects that hold an array or an object + * @return iterator + */ +ValueIterator Value::begin() const +{ + return createIterator(true); +} + +/** + * Return an iterator for iterating over the values + * This is only meaningful for Value objects that hold an array or an object + * @return iterator + */ +ValueIterator Value::end() const +{ + return createIterator(false); +} + +/** + * Does the array contain a certain index? + * @param index + * @return bool + */ +bool Value::contains(int index) const +{ + // must be an array + if (!isArray()) return false; + + // unused variable + zval **result; + + // check if this index is already in the array + return zend_hash_index_find(Z_ARRVAL_P(_val), index, (void**)&result) != FAILURE; +} + +/** + * Does the array contain a certain key + * @param key + * @param size + * @return boolean + */ +bool Value::contains(const char *key, int size) const +{ + // calculate size + if (size < 0) size = strlen(key); + + // deal with arrays + if (isArray()) + { + // unused variable + zval **result; + + // check if index is already in the array + return zend_hash_find(Z_ARRVAL_P(_val), key, size+1, (void **)&result) != FAILURE; + } + else if (isObject()) + { + // we need the tsrmls_cc variable + TSRMLS_FETCH(); + + // retrieve the class entry + auto *entry = zend_get_class_entry(_val TSRMLS_CC); + + // read the property (cast necessary for php 5.3) + zval *property = zend_read_property(entry, _val, (char *)key, size, 0 TSRMLS_CC); + + // check if valid + return property != nullptr; + } + else + { + // scalar variable + return false; + } +} + +/** + * Get access to a certain array member + * @param index + * @return Value + */ +Value Value::get(int index) const +{ + // must be an array + if (!isArray()) return Value(); + + // zval to retrieve + zval **result; + + // check if index is in the array + if (zend_hash_index_find(Z_ARRVAL_P(_val), index, (void **)&result) == FAILURE) return Value(); + + // wrap the value + return Value(*result); +} + +/** + * Get access to a certain assoc member + * @param key + * @param size + * @return Value + */ +Value Value::get(const char *key, int size) const +{ + // must be an array + if (!isArray() && !isObject()) return Value(); + + // calculate size + if (size < 0) size = strlen(key); + + // are we in an object or an array? + if (isArray()) + { + // the result value + zval **result; + + // check if this index is already in the array, otherwise we return NULL + if (zend_hash_find(Z_ARRVAL_P(_val), key, size + 1, (void **)&result) == FAILURE) return Value(); + + // wrap the value + return Value(*result); + } + else + { + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + // retrieve the class entry + auto *entry = zend_get_class_entry(_val TSRMLS_CC); + + // read the property (case necessary for php 5.3) + zval *property = zend_read_property(entry, _val, (char *)key, size, 1 TSRMLS_CC); + + // wrap in value + return Value(property); + } +} + +/** + * Set a certain property without performing any checks + * This method can be used when it is already known that the object is an array + * @param index + * @param value + * @return Value + */ +void Value::setRaw(int index, const Value &value) +{ + // if this is not a reference variable, we should detach it to implement copy on write + SEPARATE_ZVAL_IF_NOT_REF(&_val); + + // add the value (this will decrement refcount on any current variable) + add_index_zval(_val, index, value._val); + + // the variable has one more reference (the array entry) + Z_ADDREF_P(value._val); +} + +/** + * Set a certain property + * @param index + * @param value + * @return Value + */ +void Value::set(int index, const Value &value) +{ + // the current value + zval **current; + + // check if this index is already in the array, otherwise we return NULL + if (isArray() && zend_hash_index_find(Z_ARRVAL_P(_val), index, (void **)¤t) != FAILURE) + { + // skip if nothing is going to change + if (value._val == *current) return; + } + + // must be an array + setType(Type::Array); + + // set property + setRaw(index, value); +} + +/** + * Set a certain property without running any checks + * @param key + * @param size + * @param value + */ +void Value::setRaw(const char *key, int size, const Value &value) +{ + // is this an object? + if (isObject()) + { + // if this is not a reference variable, we should detach it to implement copy on write + SEPARATE_ZVAL_IF_NOT_REF(&_val); + + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + // retrieve the class entry + auto *entry = zend_get_class_entry(_val TSRMLS_CC); + + // update the property (cast necessary for php 5.3) + zend_update_property(entry, _val, (char *)key, size, value._val TSRMLS_CC); + } + else + { + // if this is not a reference variable, we should detach it to implement copy on write + SEPARATE_ZVAL_IF_NOT_REF(&_val); + + // add the value (this will reduce the refcount of the current value) + add_assoc_zval_ex(_val, key, size+1, value._val); + + // the variable has one more reference (the array entry) + Z_ADDREF_P(value._val); + } +} + +/** + * Set a certain property + * @param key + * @param size + * @param value + * @return Value + */ +void Value::set(const char *key, int size, const Value &value) +{ + // the current value + zval **current; + + // check if this index is already in the array, otherwise we return NULL + if (isArray() && zend_hash_find(Z_ARRVAL_P(_val), key, size + 1, (void **)¤t) != FAILURE) + { + // skip if nothing is going to change + if (value._val == *current) return; + } + + // this should be an object or an array + if (!isObject()) setType(Type::Array); + + // done + setRaw(key, size, value); +} + +/** + * Array access operator + * This can be used for accessing arrays + * @param index + * @return HashMember + */ +HashMember Value::operator[](int index) +{ + return HashMember(this, index); +} + +/** + * Array access operato + * This can be used for accessing associative arrays + * @param key + * @return HashMember + */ +HashMember Value::operator[](const std::string &key) +{ + return HashMember(this, key); +} + +/** + * Array access operator + * This can be used for accessing associative arrays + * @param key + * @return HashMember + */ +HashMember Value::operator[](const char *key) +{ + return HashMember(this, key); +} + +/** + * Retrieve the original implementation + * + * This only works for classes that were implemented using PHP-CPP, + * it returns nullptr for all other classes + * + * @return Base* + */ +Base *Value::implementation() const +{ + // must be an object + if (!isObject()) return nullptr; + + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + // retrieve the mixed object that contains the base + return ObjectImpl::find(_val TSRMLS_CC)->object(); +} + +/** + * Custom output stream operator + * @param stream + * @param value + * @return ostream + */ +std::ostream &operator<<(std::ostream &stream, const Value &value) +{ + return stream << value.stringValue(); +} + +/** + * End of namespace + */ +} + diff --git a/zend/valueiterator.cpp b/zend/valueiterator.cpp new file mode 100644 index 0000000..65c687c --- /dev/null +++ b/zend/valueiterator.cpp @@ -0,0 +1,100 @@ +/** + * ValueIterator.cpp + * + * Implementation of the value iterator + * + * @author Emiel Bruijntjes + * @copyright 2014 Copernica BV + */ +#include "includes.h" + +/** + * Set up namespace + */ +namespace Php { + +/** + * Copy constructor + * @param that + * @param tsrm_ls + */ +ValueIterator::ValueIterator(const ValueIterator &that) : _impl(that._impl->clone()) {} + +/** + * Destructor + */ +ValueIterator::~ValueIterator() +{ + // get rid of implementation + delete _impl; +} + +/** + * Increment position + * @return ValueIterator + */ +ValueIterator &ValueIterator::operator++() +{ + // increment implementation + _impl->increment(); + + // done + return *this; +} + +/** + * Decrement position + * @return ValueIterator + */ +ValueIterator &ValueIterator::operator--() +{ + // decrement implementation + _impl->decrement(); + + // done + return *this; +} + +/** + * Compare with other iterator + * @param that + * @return bool + */ +bool ValueIterator::operator==(const ValueIterator &that) const +{ + return _impl->equals(that._impl); +} + +/** + * Compare with other iterator + * @param that + * @return bool + */ +bool ValueIterator::operator!=(const ValueIterator &that) const +{ + return !_impl->equals(that._impl); +} + +/** + * Derefecence, this returns a std::pair with the current key and value + * @return std::pair + */ +const std::pair &ValueIterator::operator*() const +{ + return _impl->current(); +} + +/** + * Dereference, this returns a std::pair with the current key and value + * @return std::pair + */ +const std::pair *ValueIterator::operator->() const +{ + return &_impl->current(); +} + +/** + * End namespace + */ +} + diff --git a/zend/valueiteratorimpl.h b/zend/valueiteratorimpl.h new file mode 100644 index 0000000..82c888d --- /dev/null +++ b/zend/valueiteratorimpl.h @@ -0,0 +1,71 @@ +/** + * ValueIteratorImpl.h + * + * Interface that describes what an implementation of a value iterator should + * look like. This is an internal class that extension developers do not + * need. It is used internally inside the ValueIterator class. + * + * @author Emiel Bruijntjes + * @copyright 2014 Copernica BV + */ + +/** + * Set up namespace + */ +namespace Php { + +/** + * Class definition + */ +class ValueIteratorImpl +{ +public: + /** + * Constructor + */ + ValueIteratorImpl() {} + + /** + * Destructor + */ + virtual ~ValueIteratorImpl() {} + + /** + * Clone the object + * @param tsrm_ls + * @return ValueIteratorImpl* + */ + virtual ValueIteratorImpl *clone() = 0; + + /** + * Increment position (pre-increment) + * @param tsrm_ls + * @return bool + */ + virtual bool increment() = 0; + + /** + * Decrement position (pre-decrement) + * @return bool + */ + virtual bool decrement() = 0; + + /** + * Compare with other iterator + * @param that + * @return bool + */ + virtual bool equals(const ValueIteratorImpl *that) const = 0; + + /** + * Derefecence, this returns a std::pair with the current key and value + * @return std::pair + */ + virtual const std::pair ¤t() const = 0; +}; + +/** + * End namespace + */ +} + -- cgit v1.2.3