diff options
author | valmat <ufabiz@gmail.com> | 2014-04-09 11:00:05 +0600 |
---|---|---|
committer | valmat <ufabiz@gmail.com> | 2014-04-09 11:00:05 +0600 |
commit | 6c7c846edd5b74450b76532da33c25e6cc6a10a4 (patch) | |
tree | 51b0e0be5c43ddba6ca9351026fc94bf8ae7bc07 /zend | |
parent | 08ed8866a5bba0b23a8d5587116a968512df2568 (diff) | |
parent | 33760c3efba4207eac826ff080b5f9b9672fc60e (diff) |
Merge branch 'master' into ini-master
Conflicts:
include/namespace.h
zend/extensionimpl.cpp
Diffstat (limited to 'zend')
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 ¶ms) const +{ + // throw an exception that will be caught in the ClassBase class, + // so that the default implementation of the function can be called + throw NotImplemented(); + + // unreachable code + return nullptr; +} + +/** + * Call the class as if it was a function + * + * This method is called when a an object is used with () operators: + * $object(). You can override this method to make objects callable. + * + * @param params The parameters that were passed to the function + * @return Value The return value + */ +Value Base::__invoke(Parameters ¶ms) const +{ + // throw an exception that will be caught in the ClassBase class, + // so that the default implementation of the function can be called + throw NotImplemented(); + + // unreachable code + return nullptr; +} + +/** + * Cast the object to a string + * + * This method is called when an object is casted to a string, or when + * it is used in a string context + * + * @return Value The object as a string + */ +Value Base::__toString() const +{ + // throw an exception that will be caught in the ClassBase class, + // so that the default implementation of the function can be called + throw NotImplemented(); + + // unreachable code + return nullptr; +} + +/** + * Cast the object to an integer + * + * This method is called when an object is casted to an integer, or when + * it is used in an integer context + * + * @return int Integer value + */ +Value Base::__toInteger() const +{ + // throw an exception that will be caught in the ClassBase class, + // so that the default implementation of the function can be called + throw NotImplemented(); + + // unreachable code + return 0; +} + +/** + * Cast the object to a float + * + * This method is called when an object is casted to a float, or when it + * is used in a float context + * + * @return double Floating point value + */ +Value Base::__toFloat() const +{ + // throw an exception that will be caught in the ClassBase class, + // so that the default implementation of the function can be called + throw NotImplemented(); + + // unreachable code + return 0.0; +} + +/** + * Cast the object to a boolean + * + * This method is called when an object is casted to a bool, or when it + * is used in a boolean context + * + * @return bool + */ +Value Base::__toBool() const +{ + // throw an exception that will be caught in the ClassBase class, + // so that the default implementation of the function can be called + throw NotImplemented(); + + // unreachable code + return false; +} + +/** + * Compare the object with a different object + * + * Check how a different object compares to this object + * + * @param that Object to compare with + * @return int + */ +int Base::__compare(const Base &that) const +{ + // throw an exception that will be caught in the ClassBase class, + // so that the default implementation of the function can be called + throw NotImplemented(); + + // unreachable code + return 1; +} + +/** + * End namespace + */ +} + diff --git a/zend/boolmember.h b/zend/boolmember.h new file mode 100644 index 0000000..5b5d43d --- /dev/null +++ b/zend/boolmember.h @@ -0,0 +1,67 @@ +/** + * BoolMember.h + * + * Implementation for a property that is initially set to a boolean value + * + * @author Emiel Bruijntjes <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 ¶ms) = 0; + + /** + * Fill a function entry + * @param entry Entry to be filled + * @param ns Active namespace + * @param classname Optional class name + * @param flags Access flags + */ + void initialize(zend_function_entry *entry, const char *classname = nullptr, int flags = 0) const; + + /** + * Fill function info + * @param info Info object to be filled + * @param ns Active namespace + * @param classname Optional class name + */ + void initialize(zend_arg_info *info, const char *classname = nullptr) const; + + +protected: + /** + * Hidden pointer to the name and the function + * @var HiddenPointer + */ + HiddenPointer<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 ¶ms) override + { + switch (_type) { + case 0: _function.f0(); return Value(); + case 1: _function.f1(params); return Value(); + case 2: return _function.f2(); + case 3: return _function.f3(params); + default: return Value(); + } + } + + /** + * Fill a function entry + * @param prefix Active namespace prefix + * @param entry Entry to be filled + */ + void initialize(const std::string &prefix, zend_function_entry *entry) + { + // if there is a namespace prefix, we should adjust the name + if (prefix.size()) _ptr = HiddenPointer<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> ¤t() const override + { + return _current; + } + +private: + /** + * The hash table over which is being iterated + * @var HashTable + */ + HashTable *_table = nullptr; + + /** + * The position in the hash table + * @var HashPosition + */ + Bucket *_position = nullptr; + + /** + * The current key and value + * @var std::pair + */ + std::pair<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> ¤t() 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 ¶meters) override + { + // the object to call a method on + Base *base = parameters.object(); + + // find out which method to call, and call it + switch (_type) { + case 0: (base->*_callback.m0)(); return Value(); + case 1: (base->*_callback.m1)(parameters); return Value(); + case 2: return (base->*_callback.m2)(); + case 3: return (base->*_callback.m3)(parameters); + case 4: (base->*_callback.m4)(); return Value(); + case 5: (base->*_callback.m5)(parameters); return Value(); + case 6: return (base->*_callback.m6)(); + case 7: return (base->*_callback.m7)(parameters); + case 8: _callback.m8(); return Value(); + case 9: _callback.m9(parameters); return Value(); + case 10: return _callback.m10(); + case 11: return _callback.m11(parameters); + default: return Value(); + } + } + + +private: + /** + * Callback type + * @var int + */ + int _type; + + /** + * Access flags (protected, public, abstract, final, private, etc) + * @var int + */ + int _flags; + + /** + * The actual callback + * @var void* + */ + union { + method_callback_0 m0; + method_callback_1 m1; + method_callback_2 m2; + method_callback_3 m3; + method_callback_4 m4; + method_callback_5 m5; + method_callback_6 m6; + method_callback_7 m7; + native_callback_0 m8; + native_callback_1 m9; + native_callback_2 m10; + native_callback_3 m11; + } _callback; +}; + +/** + * End of namespace + */ +} + diff --git a/zend/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> ¤t() const + { + return _data; + } + +private: + /** + * The object that is iterated over + * @var _val + */ + zval *_object = nullptr; + + /** + * The iterator from Zend + * @var zend_object_iterator + */ + struct _zend_object_iterator *_iter = nullptr; + + /** + * Current data + * @var pair + */ + std::pair<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 **)¤t) != FAILURE) + { + // skip if nothing is going to change + if (value._val == *current) return; + } + + // must be an array + setType(Type::Array); + + // set property + setRaw(index, value); +} + +/** + * Set a certain property without running any checks + * @param key + * @param size + * @param value + */ +void Value::setRaw(const char *key, int size, const Value &value) +{ + // is this an object? + if (isObject()) + { + // if this is not a reference variable, we should detach it to implement copy on write + SEPARATE_ZVAL_IF_NOT_REF(&_val); + + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + // retrieve the class entry + auto *entry = zend_get_class_entry(_val TSRMLS_CC); + + // update the property (cast necessary for php 5.3) + zend_update_property(entry, _val, (char *)key, size, value._val TSRMLS_CC); + } + else + { + // if this is not a reference variable, we should detach it to implement copy on write + SEPARATE_ZVAL_IF_NOT_REF(&_val); + + // add the value (this will reduce the refcount of the current value) + add_assoc_zval_ex(_val, key, size+1, value._val); + + // the variable has one more reference (the array entry) + Z_ADDREF_P(value._val); + } +} + +/** + * Set a certain property + * @param key + * @param size + * @param value + * @return Value + */ +void Value::set(const char *key, int size, const Value &value) +{ + // the current value + zval **current; + + // check if this index is already in the array, otherwise we return NULL + if (isArray() && zend_hash_find(Z_ARRVAL_P(_val), key, size + 1, (void **)¤t) != FAILURE) + { + // skip if nothing is going to change + if (value._val == *current) return; + } + + // this should be an object or an array + if (!isObject()) setType(Type::Array); + + // done + setRaw(key, size, value); +} + +/** + * Array access operator + * This can be used for accessing arrays + * @param index + * @return HashMember + */ +HashMember<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> ¤t() const = 0; +}; + +/** + * End namespace + */ +} + |