summaryrefslogtreecommitdiff
path: root/zend
diff options
context:
space:
mode:
authorvalmat <ufabiz@gmail.com>2014-04-09 11:00:05 +0600
committervalmat <ufabiz@gmail.com>2014-04-09 11:00:05 +0600
commit6c7c846edd5b74450b76532da33c25e6cc6a10a4 (patch)
tree51b0e0be5c43ddba6ca9351026fc94bf8ae7bc07 /zend
parent08ed8866a5bba0b23a8d5587116a968512df2568 (diff)
parent33760c3efba4207eac826ff080b5f9b9672fc60e (diff)
Merge branch 'master' into ini-master
Conflicts: include/namespace.h zend/extensionimpl.cpp
Diffstat (limited to 'zend')
-rw-r--r--zend/arithmetic.h286
-rw-r--r--zend/base.cpp287
-rw-r--r--zend/boolmember.h67
-rw-r--r--zend/callable.cpp138
-rw-r--r--zend/callable.h203
-rw-r--r--zend/classbase.cpp130
-rw-r--r--zend/classimpl.cpp1429
-rw-r--r--zend/classimpl.h445
-rw-r--r--zend/extension.cpp103
-rw-r--r--zend/extensionimpl.cpp321
-rw-r--r--zend/extensionimpl.h120
-rw-r--r--zend/floatmember.h67
-rw-r--r--zend/function.h102
-rw-r--r--zend/global.cpp45
-rw-r--r--zend/globals.cpp92
-rw-r--r--zend/hashiterator.h250
-rw-r--r--zend/hashmember.cpp40
-rw-r--r--zend/includes.h128
-rw-r--r--zend/init.h54
-rw-r--r--zend/invaliditerator.h82
-rw-r--r--zend/iteratorimpl.cpp183
-rw-r--r--zend/iteratorimpl.h190
-rw-r--r--zend/member.h77
-rw-r--r--zend/members.cpp90
-rw-r--r--zend/method.h139
-rw-r--r--zend/namespace.cpp134
-rw-r--r--zend/notimplemented.h51
-rw-r--r--zend/nullmember.h59
-rw-r--r--zend/numericmember.h67
-rw-r--r--zend/object.cpp79
-rw-r--r--zend/objectimpl.h207
-rw-r--r--zend/origexception.h145
-rw-r--r--zend/parametersimpl.h53
-rw-r--r--zend/property.h160
-rw-r--r--zend/streambuf.cpp55
-rw-r--r--zend/streams.cpp40
-rw-r--r--zend/stringmember.h83
-rw-r--r--zend/super.cpp71
-rw-r--r--zend/traverseiterator.h259
-rw-r--r--zend/value.cpp1911
-rw-r--r--zend/valueiterator.cpp100
-rw-r--r--zend/valueiteratorimpl.h71
42 files changed, 8613 insertions, 0 deletions
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 <emiel.bruijntjes@copernica.com>
+ * @copyright 2013 Copernica BV
+ */
+
+/**
+ * Set up namespace
+ */
+namespace Php {
+
+/**
+ * Class definition
+ */
+template < template<typename T> 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<double>()(_value->floatValue(), value));
+
+ // apply to natural numbers
+ return Value(F<int64_t>()(_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<double>()(_value->floatValue(), value));
+
+ // apply to natural numbers
+ return Value(F<int64_t>()(_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<double>()(_value->floatValue(), value));
+
+ // apply to natural numbers
+ return Value(F<int64_t>()(_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<double>()(_value->floatValue(), value?1:0));
+
+ // apply to natural numbers
+ return Value(F<int64_t>()(_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<double>()(_value->floatValue(), v));
+
+ // apply to natural numbers
+ return Value(F<int64_t>()(_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<double>()(_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<double>()(_value->floatValue(), value));
+
+ // do a numeric operation
+ return _value->operator=(F<int64_t>()(_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<double>()(_value->floatValue(), value));
+
+ // do a numeric operation
+ return _value->operator=(F<int64_t>()(_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<double>()(_value->floatValue(), value));
+
+ // do a numeric operation
+ return _value->operator=(F<int64_t>()(_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<double>()(_value->floatValue(), value?1:0));
+
+ // do a numeric operation
+ return _value->operator=(F<int64_t>()(_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<double>()(_value->floatValue(), v));
+
+ // do a numeric operation
+ return _value->operator=(F<int64_t>()(_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<double>()(_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 &params) 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 &params) 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 <emiel.bruijntjes@copernica.com>
+ * @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..96f5f90
--- /dev/null
+++ b/zend/callable.cpp
@@ -0,0 +1,138 @@
+/**
+ * Function.cpp
+ *
+ * Implementation for the function class
+ *
+ * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
+ * @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
+ */
+void Callable::invoke(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<Callable>(name);
+
+ // construct parameters
+ ParametersImpl params(this_ptr, ZEND_NUM_ARGS() TSRMLS_CC);
+
+ // the function could throw an exception
+ try
+ {
+ // get the result
+ Value result(callable->invoke(params));
+
+ // detach the zval (we don't want it to be destructed)
+ zval *val = result.detach();
+
+ // @todo php 5.6 has a RETVAL_ZVAL_FAST macro that can be used instead (and is faster)
+
+ // return a full copy of the zval, and do not destruct it
+ RETVAL_ZVAL(val, 1, 0);
+ }
+ 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 = &Callable::invoke;
+ 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..67863a9
--- /dev/null
+++ b/zend/callable.h
@@ -0,0 +1,203 @@
+/**
+ * Callable.h
+ *
+ * Object represents a callable function or method that is defined with the CPP
+ * API.
+ *
+ * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
+ * @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 &params) = 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<Callable> _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 = arg.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();
+ }
+
+ /**
+ * 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(INTERNAL_FUNCTION_PARAMETERS);
+
+};
+
+/**
+ * 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 <emiel.bruijntjes@copernica.com>
+ * @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<ClassImpl>(classname, ClassType::Abstract);
+ else if (flags & Final) _impl = std::make_shared<ClassImpl>(classname, ClassType::Final);
+ else _impl = std::make_shared<ClassImpl>(classname, ClassType::Regular);
+}
+
+/**
+ * Protected constructor
+ * @param classname Class name
+ * @param type Class type
+ */
+ClassBase::ClassBase(const char *classname, ClassType type)
+{
+ // construct implementation
+ _impl = std::make_shared<ClassImpl>(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 <emiel.bruijntjes@copernica.com>
+ * @copyright 2014 Copernica BV
+ */
+#include "includes.h"
+
+/**
+ * Set up namespace
+ */
+namespace Php {
+
+/**
+ * Destructor
+ */
+ClassImpl::~ClassImpl()
+{
+ // destruct the entries
+ if (_entries) delete[] _entries;
+
+ // php 5.3 deallocates the doc_comment by iself
+#if PHP_VERSION_ID >= 50400
+ if (_comment) free(_comment);
+#endif
+}
+
+/**
+ * @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<Countable*>(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<ArrayAccess*>(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<ArrayAccess*>(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<ArrayAccess*>(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<ArrayAccess*>(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<Traversable*>(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<Serializable*>(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<Serializable*>(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 <emiel.bruijntjes@copernica.com>
+ * @copyright 2014 Copernica BV
+ */
+
+/**
+ * Set up namespace
+ */
+namespace Php {
+
+/**
+ * Class definition
+ */
+class ClassImpl
+{
+private:
+ /**
+ * Pointer to the actual Php::Class<X> that is created in the extension
+ * @var ClassBase
+ */
+ ClassBase *_base = nullptr;
+
+ /**
+ * Name of the class
+ * @var string
+ */
+ std::string _name;
+
+ /**
+ * The comment for reflexion, with a stored pointer to ourselves
+ * @var char*
+ */
+ char *_comment = nullptr;
+
+ /**
+ * The class type (this can be values like Php::Abstract and Php::Final)
+ * @var ClassType
+ */
+ ClassType _type = ClassType::Regular;
+
+ /**
+ * The class entry
+ * @var zend_class_entry
+ */
+ zend_class_entry *_entry = nullptr;
+
+ /**
+ * Pointer to the entries
+ * @var zend_function_entry[]
+ */
+ zend_function_entry *_entries = nullptr;
+
+ /**
+ * All class methods
+ * @var std::list
+ */
+ std::list<std::shared_ptr<Method>> _methods;
+
+ /**
+ * All class members (class properties)
+ * @var std::list
+ */
+ std::list<std::shared_ptr<Member>> _members;
+
+ /**
+ * Map of dynamically accessible properties
+ * @var std::map
+ */
+ std::map<std::string,std::shared_ptr<Property>> _properties;
+
+ /**
+ * Interfaces that are implemented
+ * @var std::list
+ */
+ std::list<std::shared_ptr<ClassImpl>> _interfaces;
+
+ /**
+ * The parent/base class
+ * @var std::shared_ptr
+ */
+ std::shared_ptr<ClassImpl> _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<Method>(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<Method>(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<Method>(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<Method>(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<Method>(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<Method>(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<Method>(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<Method>(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<Method>(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<Method>(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<Method>(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<Method>(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<Method>(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<NullMember> (name, flags & PropertyModifiers)); }
+ void property(const char *name, int16_t value, int flags = Php::Public) { _members.push_back(std::make_shared<NumericMember>(name, value, flags & PropertyModifiers)); }
+ void property(const char *name, int32_t value, int flags = Php::Public) { _members.push_back(std::make_shared<NumericMember>(name, value, flags & PropertyModifiers)); }
+ void property(const char *name, int64_t value, int flags = Php::Public) { _members.push_back(std::make_shared<NumericMember>(name, value, flags & PropertyModifiers)); }
+ void property(const char *name, bool value, int flags = Php::Public) { _members.push_back(std::make_shared<BoolMember> (name, value, flags & PropertyModifiers)); }
+ void property(const char *name, char value, int flags = Php::Public) { _members.push_back(std::make_shared<StringMember> (name, &value, 1, flags & PropertyModifiers)); }
+ void property(const char *name, const std::string &value, int flags = Php::Public) { _members.push_back(std::make_shared<StringMember> (name, value, flags & PropertyModifiers)); }
+ void property(const char *name, const char *value, int flags = Php::Public) { _members.push_back(std::make_shared<StringMember> (name, value, strlen(value), flags & PropertyModifiers)); }
+ void property(const char *name, double value, int flags = Php::Public) { _members.push_back(std::make_shared<FloatMember> (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<Property>(getter); }
+ void property(const char *name, const getter_callback_1 &getter) { _properties[name] = std::make_shared<Property>(getter); }
+ void property(const char *name, const getter_callback_0 &getter, const setter_callback_0 &setter) { _properties[name] = std::make_shared<Property>(getter,setter); }
+ void property(const char *name, const getter_callback_1 &getter, const setter_callback_0 &setter) { _properties[name] = std::make_shared<Property>(getter,setter); }
+ void property(const char *name, const getter_callback_0 &getter, const setter_callback_1 &setter) { _properties[name] = std::make_shared<Property>(getter,setter); }
+ void property(const char *name, const getter_callback_1 &getter, const setter_callback_1 &setter) { _properties[name] = std::make_shared<Property>(getter,setter); }
+
+ /**
+ * Add an interface that is implemented
+ * @param interface The interface that is implemented
+ */
+ void implements(const std::shared_ptr<ClassImpl> &interface) { _interfaces.push_back(interface); }
+
+ /**
+ * Set the base class
+ * @param base The base class
+ */
+ void extends(const std::shared_ptr<ClassImpl> &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 <emiel.bruijntjes@copernica.com>
+ * @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..3534cdb
--- /dev/null
+++ b/zend/extensionimpl.cpp
@@ -0,0 +1,321 @@
+/**
+ * Extension.cpp
+ *
+ * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
+ * @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<std::string,ExtensionImpl*> name2extension;
+static std::map<int,ExtensionImpl*> 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::processStartup(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);
+
+ // array contains ini settings
+ static zend_ini_entry *ini_entries = new zend_ini_entry[ extension->_ini_entries.size()+1 ];
+
+ // Filling ini_entries
+ unsigned int Ind = 0;
+ for (auto &ini : extension->_ini_entries) ini->fill(&ini_entries[Ind++], module_number);
+
+ // add last empty ini entry (Zend, for some reason, it requires)
+ zend_ini_entry empty_entry { 0, 0, nullptr, 0, nullptr, nullptr, nullptr, nullptr, nullptr, 0, nullptr, 0, 0, 0, nullptr };
+ ini_entries[Ind] = empty_entry;
+
+ // register
+ REGISTER_INI_ENTRIES();
+
+ // 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::processShutdown(int type, int module_number TSRMLS_DC)
+{
+ // get the extension
+ auto *extension = find(module_number TSRMLS_CC);
+
+
+ UNREGISTER_INI_ENTRIES();
+ // free memory from array ini entries
+ static zend_ini_entry *ini_entries;
+ delete [] ini_entries;
+
+ // 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::processRequest(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::processIdle(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) : ExtensionBase(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, will be filled by Zend engine
+ _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::processStartup; // startup function for the whole extension
+ _entry.module_shutdown_func = &ExtensionImpl::processShutdown; // shutdown function for the whole extension
+ _entry.request_startup_func = &ExtensionImpl::processRequest; // startup function per request
+ _entry.request_shutdown_func = &ExtensionImpl::processIdle; // 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..e58ce66
--- /dev/null
+++ b/zend/extensionimpl.h
@@ -0,0 +1,120 @@
+/**
+ * ExtensionImpl.h
+ *
+ * Extension implementation for the Zend engine.
+ *
+ * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
+ * @copyright 2013, 2014 Copernica BV
+ */
+
+/**
+ * Set up namespace
+ */
+namespace Php {
+
+/**
+ * Class definition
+ */
+class ExtensionImpl : public ExtensionBase
+{
+protected:
+ /**
+ * 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;
+
+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();
+
+ /**
+ * 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 processStartup(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 processShutdown(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 processRequest(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 processIdle(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 <emiel.bruijntjes@copernica.com>
+ * @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 <emiel.bruijntjes@copernica.com>
+ * @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 &params) 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<Callable>(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 <emiel.bruijntjes@copernica.com>
+ * @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 <emiel.bruijntjes@copernica.com>
+ * @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<Value,Value> &current() 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<Value,Value> _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<Value,Value>(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<Value,Value>(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 <emiel.bruijntjes@copernica.com>
+ * @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<int> &value)
+{
+ return stream << value.value();
+}
+
+/**
+ * Custom output stream operator
+ * @param stream
+ * @param value
+ * @return ostream
+ */
+std::ostream &operator<<(std::ostream &stream, const HashMember<std::string> &value)
+{
+ return stream << value.value();
+}
+
+/**
+ * End of namespace
+ */
+}
+
diff --git a/zend/includes.h b/zend/includes.h
new file mode 100644
index 0000000..e7dece9
--- /dev/null
+++ b/zend/includes.h
@@ -0,0 +1,128 @@
+/**
+ * Includes.h
+ *
+ * Startup include file to compile the phpcpp library
+ *
+ * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
+ * @copyright 2013 Copernica BV
+ */
+
+/**
+ * Include standard C and C++ libraries
+ */
+#include <stdlib.h>
+#include <string>
+#include <initializer_list>
+#include <vector>
+#include <map>
+#include <memory>
+#include <list>
+#include <exception>
+#include <type_traits>
+
+// for debug
+#include <iostream>
+
+//#define ZTS
+//#define THREAD_T pthread_t
+//#define MUTEX_T pthread_mutex_t *
+
+/**
+ * PHP includes
+ */
+#pragma GCC system_header
+#include <php.h>
+#include <zend_exceptions.h>
+#include <zend_interfaces.h>
+#include <zend_ini.h>
+
+/**
+ * Macro to convert results to success status
+ */
+#define BOOL2SUCCESS(b) ((b) ? SUCCESS : FAILURE)
+
+/**
+ * Include other files from this library
+ */
+#include "../include/ini.h"
+#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"
+
+/**
+ * Common header files for internal use only
+ */
+#include "../common/extensionbase.h"
+#include "../common/streambuf.h"
+
+/**
+ * Specific zend implementation 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 "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 <emiel.bruijntjes@copernica.com>
+ * @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 <emiel.bruijntjes@copernica.com>
+ * @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<Value,Value> &current() const override
+ {
+ // this method is never called, when it is, we create a static instance
+ static std::pair<Value,Value> 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 <emiel.bruijntjes@copernica.com>
+ * @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 <emiel.bruijntjes@copernica.com>
+ * @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> _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 <emiel.bruijntjes@copernica.com>
+ * @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 <emiel.bruijntjes@copernica.com>
+ * @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 <emiel.bruijntjes@copernica.com>
+ * @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 &parameters) 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/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 <emiel.bruijntjes@copernica.com>
+ * @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<Function>(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<Function>(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<Function>(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<Function>(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<void(const std::string &ns, Function &func)> &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<void(const std::string &ns, ClassBase &clss)> &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 <emiel.bruijntjes@copernica.com>
+ * @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 <emiel.bruijntjes@copernica.com>
+ * @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 <emiel.bruijntjes@copernica.com>
+ * @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 <emiel.bruijntjes@copernica.com>
+ * @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..f66a6ab
--- /dev/null
+++ b/zend/objectimpl.h
@@ -0,0 +1,207 @@
+/**
+ * ObjectImpl.h
+ *
+ * Implementation class for Base objects that allow the objects to be stored
+ * in the Zend engine
+ *
+ * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
+ * @copyright 2014 Copernica BV
+ */
+
+/**
+ * Set up namespace
+ */
+namespace Php {
+
+/**
+ * Class definition
+ */
+class ObjectImpl
+{
+private:
+ /**
+ * Structure with a first element which is a zend_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)
+ {
+ // allocate a mixed object (for some reason this does not have to deallocated)
+ _mixed = (MixedObject *)emalloc(sizeof(MixedObject));
+
+ // 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(php(), (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
+ if (_object) delete _object;
+ }
+
+ /**
+ * Destruct the object
+ * @param tsrm_ls
+ */
+ void destruct(TSRMLS_D)
+ {
+ // pass on to the default destructor
+ zend_objects_free_object_storage(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 <emiel.bruijntjes@copernica.com>
+ * @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<OrigException&>(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 <emiel.bruijntjes@copernica.com>
+ * @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<argc; i++)
+ {
+ // get the argument
+ zval **arg = (zval **) (zend_vm_stack_top(TSRMLS_C) - 1 - (argc-i));
+
+ // append value
+ push_back(Value(*arg));
+ }
+ }
+
+ /**
+ * Destructor
+ */
+ virtual ~ParametersImpl() {}
+};
+
+/**
+ * End of namespace
+ */
+}
+
diff --git a/zend/property.h b/zend/property.h
new file mode 100644
index 0000000..f0fd46f
--- /dev/null
+++ b/zend/property.h
@@ -0,0 +1,160 @@
+/**
+ * Property.h
+ *
+ * Internal class for properties that are defined with a getter and setter method
+ *
+ * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
+ * @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..86e5f03
--- /dev/null
+++ b/zend/streambuf.cpp
@@ -0,0 +1,55 @@
+/**
+ * StreamBuf.cpp
+ *
+ * Implementation file for the StreamBuf class
+ *
+ * @see http://www.mr-edd.co.uk/blog/beginners_guide_streambuf
+ *
+ * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
+ * @copyright 2014 Copernica BV
+ */
+#include "includes.h"
+
+/**
+ * Set up namespace
+ */
+namespace Php {
+
+/**
+ * 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/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 <emiel.bruijntjes@copernica.com>
+ * @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 <emiel.bruijntjes@copernica.com>
+ * @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 <emiel.bruijntjes@copernica.com>
+ */
+#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 <emiel.bruijntjes@copernica.com>
+ * @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<Value,Value> &current() 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<Value,Value> _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 <emiel.bruijntjes@copernica.com>
+ * @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<std::plus>(this).assign(value); }
+Value &Value::operator+=(int16_t value) { return Arithmetic<std::plus>(this).assign(value); }
+Value &Value::operator+=(int32_t value) { return Arithmetic<std::plus>(this).assign(value); }
+Value &Value::operator+=(int64_t value) { return Arithmetic<std::plus>(this).assign(value); }
+Value &Value::operator+=(bool value) { return Arithmetic<std::plus>(this).assign(value); }
+Value &Value::operator+=(char value) { return Arithmetic<std::plus>(this).assign(value); }
+Value &Value::operator+=(const std::string &value) { return Arithmetic<std::plus>(this).assign(value); }
+Value &Value::operator+=(const char *value) { return Arithmetic<std::plus>(this).assign(value); }
+Value &Value::operator+=(double value) { return Arithmetic<std::plus>(this).assign(value); }
+
+/**
+ * Subtract a value from the object
+ * @param value
+ * @return Value
+ */
+Value &Value::operator-=(const Value &value) { return Arithmetic<std::minus>(this).assign(value); }
+Value &Value::operator-=(int16_t value) { return Arithmetic<std::minus>(this).assign(value); }
+Value &Value::operator-=(int32_t value) { return Arithmetic<std::minus>(this).assign(value); }
+Value &Value::operator-=(int64_t value) { return Arithmetic<std::minus>(this).assign(value); }
+Value &Value::operator-=(bool value) { return Arithmetic<std::minus>(this).assign(value); }
+Value &Value::operator-=(char value) { return Arithmetic<std::minus>(this).assign(value); }
+Value &Value::operator-=(const std::string &value) { return Arithmetic<std::minus>(this).assign(value); }
+Value &Value::operator-=(const char *value) { return Arithmetic<std::minus>(this).assign(value); }
+Value &Value::operator-=(double value) { return Arithmetic<std::minus>(this).assign(value); }
+
+/**
+ * Multiply the object with a certain value
+ * @param value
+ * @return Value
+ */
+Value &Value::operator*=(const Value &value) { return Arithmetic<std::multiplies>(this).assign(value); }
+Value &Value::operator*=(int16_t value) { return Arithmetic<std::multiplies>(this).assign(value); }
+Value &Value::operator*=(int32_t value) { return Arithmetic<std::multiplies>(this).assign(value); }
+Value &Value::operator*=(int64_t value) { return Arithmetic<std::multiplies>(this).assign(value); }
+Value &Value::operator*=(bool value) { return Arithmetic<std::multiplies>(this).assign(value); }
+Value &Value::operator*=(char value) { return Arithmetic<std::multiplies>(this).assign(value); }
+Value &Value::operator*=(const std::string &value) { return Arithmetic<std::multiplies>(this).assign(value); }
+Value &Value::operator*=(const char *value) { return Arithmetic<std::multiplies>(this).assign(value); }
+Value &Value::operator*=(double value) { return Arithmetic<std::multiplies>(this).assign(value); }
+
+/**
+ * Divide the object with a certain value
+ * @param value
+ * @return Value
+ */
+Value &Value::operator/=(const Value &value) { return Arithmetic<std::divides>(this).assign(value); }
+Value &Value::operator/=(int16_t value) { return Arithmetic<std::divides>(this).assign(value); }
+Value &Value::operator/=(int32_t value) { return Arithmetic<std::divides>(this).assign(value); }
+Value &Value::operator/=(int64_t value) { return Arithmetic<std::divides>(this).assign(value); }
+Value &Value::operator/=(bool value) { return Arithmetic<std::divides>(this).assign(value); }
+Value &Value::operator/=(char value) { return Arithmetic<std::divides>(this).assign(value); }
+Value &Value::operator/=(const std::string &value) { return Arithmetic<std::divides>(this).assign(value); }
+Value &Value::operator/=(const char *value) { return Arithmetic<std::divides>(this).assign(value); }
+Value &Value::operator/=(double value) { return Arithmetic<std::divides>(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<std::plus>(this).apply(value); }
+Value Value::operator+(int16_t value) { return Arithmetic<std::plus>(this).apply(value); }
+Value Value::operator+(int32_t value) { return Arithmetic<std::plus>(this).apply(value); }
+Value Value::operator+(int64_t value) { return Arithmetic<std::plus>(this).apply(value); }
+Value Value::operator+(bool value) { return Arithmetic<std::plus>(this).apply(value); }
+Value Value::operator+(char value) { return Arithmetic<std::plus>(this).apply(value); }
+Value Value::operator+(const std::string &value) { return Arithmetic<std::plus>(this).apply(value); }
+Value Value::operator+(const char *value) { return Arithmetic<std::plus>(this).apply(value); }
+Value Value::operator+(double value) { return Arithmetic<std::plus>(this).apply(value); }
+
+/**
+ * Subtraction operator
+ * @param value
+ * @return Value
+ */
+Value Value::operator-(const Value &value) { return Arithmetic<std::minus>(this).apply(value); }
+Value Value::operator-(int16_t value) { return Arithmetic<std::minus>(this).apply(value); }
+Value Value::operator-(int32_t value) { return Arithmetic<std::minus>(this).apply(value); }
+Value Value::operator-(int64_t value) { return Arithmetic<std::minus>(this).apply(value); }
+Value Value::operator-(bool value) { return Arithmetic<std::minus>(this).apply(value); }
+Value Value::operator-(char value) { return Arithmetic<std::minus>(this).apply(value); }
+Value Value::operator-(const std::string &value) { return Arithmetic<std::minus>(this).apply(value); }
+Value Value::operator-(const char *value) { return Arithmetic<std::minus>(this).apply(value); }
+Value Value::operator-(double value) { return Arithmetic<std::minus>(this).apply(value); }
+
+/**
+ * Multiplication operator
+ * @param value
+ * @return Value
+ */
+Value Value::operator*(const Value &value) { return Arithmetic<std::multiplies>(this).apply(value); }
+Value Value::operator*(int16_t value) { return Arithmetic<std::multiplies>(this).apply(value); }
+Value Value::operator*(int32_t value) { return Arithmetic<std::multiplies>(this).apply(value); }
+Value Value::operator*(int64_t value) { return Arithmetic<std::multiplies>(this).apply(value); }
+Value Value::operator*(bool value) { return Arithmetic<std::multiplies>(this).apply(value); }
+Value Value::operator*(char value) { return Arithmetic<std::multiplies>(this).apply(value); }
+Value Value::operator*(const std::string &value) { return Arithmetic<std::multiplies>(this).apply(value); }
+Value Value::operator*(const char *value) { return Arithmetic<std::multiplies>(this).apply(value); }
+Value Value::operator*(double value) { return Arithmetic<std::multiplies>(this).apply(value); }
+
+/**
+ * Division operator
+ * @param value
+ * @return Value
+ */
+Value Value::operator/(const Value &value) { return Arithmetic<std::divides>(this).apply(value); }
+Value Value::operator/(int16_t value) { return Arithmetic<std::divides>(this).apply(value); }
+Value Value::operator/(int32_t value) { return Arithmetic<std::divides>(this).apply(value); }
+Value Value::operator/(int64_t value) { return Arithmetic<std::divides>(this).apply(value); }
+Value Value::operator/(bool value) { return Arithmetic<std::divides>(this).apply(value); }
+Value Value::operator/(char value) { return Arithmetic<std::divides>(this).apply(value); }
+Value Value::operator/(const std::string &value) { return Arithmetic<std::divides>(this).apply(value); }
+Value Value::operator/(const char *value) { return Arithmetic<std::divides>(this).apply(value); }
+Value Value::operator/(double value) { return Arithmetic<std::divides>(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<std::string,Php::Value> Value::mapValue() const
+{
+ // result variable
+ std::map<std::string,Php::Value> 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 **)&current) != 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 **)&current) != 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<int> Value::operator[](int index)
+{
+ return HashMember<int>(this, index);
+}
+
+/**
+ * Array access operato
+ * This can be used for accessing associative arrays
+ * @param key
+ * @return HashMember
+ */
+HashMember<std::string> Value::operator[](const std::string &key)
+{
+ return HashMember<std::string>(this, key);
+}
+
+/**
+ * Array access operator
+ * This can be used for accessing associative arrays
+ * @param key
+ * @return HashMember
+ */
+HashMember<std::string> Value::operator[](const char *key)
+{
+ return HashMember<std::string>(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 <emiel.bruijntjes@copernica.com>
+ * @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<Value,Value> &ValueIterator::operator*() const
+{
+ return _impl->current();
+}
+
+/**
+ * Dereference, this returns a std::pair with the current key and value
+ * @return std::pair
+ */
+const std::pair<Value,Value> *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 <emiel.bruijntjes@copernica.com>
+ * @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<Value,Value> &current() const = 0;
+};
+
+/**
+ * End namespace
+ */
+}
+