diff options
author | Emiel Bruijntjes <emiel.bruijntjes@copernica.com> | 2014-03-13 15:48:29 +0100 |
---|---|---|
committer | Emiel Bruijntjes <emiel.bruijntjes@copernica.com> | 2014-03-13 15:48:29 +0100 |
commit | 1a8c587f3a67db2e5c38cc525b29800e86f27936 (patch) | |
tree | 38b0a17b046d2cfdd76a5e03bfb97fddfbcaceed | |
parent | e45c7c392add8fdcd0d8fe06bbb790cea2f865f0 (diff) |
magic methods no longer are virtual, so that more signatures are acceptable. added support for __callStatic()
-rw-r--r-- | include/base.h | 24 | ||||
-rw-r--r-- | include/callstatic.h | 64 | ||||
-rw-r--r-- | include/class.h | 228 | ||||
-rw-r--r-- | include/classbase.h | 121 | ||||
-rw-r--r-- | include/interface.h | 53 | ||||
-rw-r--r-- | src/base.cpp | 24 | ||||
-rw-r--r-- | src/classbase.cpp | 182 |
7 files changed, 543 insertions, 153 deletions
diff --git a/include/base.h b/include/base.h index d0e7e71..6cf75df 100644 --- a/include/base.h +++ b/include/base.h @@ -121,7 +121,7 @@ public: * @param key * @return bool */ - virtual bool __isset(const Php::Value &key); + bool __isset(const Php::Value &key) const; /** * Overridable method that is called to set a new property @@ -132,7 +132,7 @@ public: * @param key * @param value */ - virtual void __set(const Php::Value &key, const Php::Value &value); + void __set(const Php::Value &key, const Php::Value &value) const; /** * Retrieve a property @@ -143,7 +143,7 @@ public: * @param key * @return value */ - virtual Php::Value __get(const Php::Value &key); + Php::Value __get(const Php::Value &key) const; /** * Remove a member @@ -153,7 +153,7 @@ public: * * @param key */ - virtual void __unset(const Php::Value &key); + void __unset(const Php::Value &key) const; /** * Call a method @@ -166,7 +166,7 @@ public: * @param params The parameters that were passed to the function * @return Value The return value */ - virtual Value __call(const char *method, Parameters ¶ms); + Php::Value __call(const char *method, Php::Parameters ¶ms) const; /** * Call the class as if it was a function @@ -177,7 +177,7 @@ public: * @param params The parameters that were passed to the function * @return Value The return value */ - virtual Value __invoke(Parameters ¶ms); + Php::Value __invoke(Php::Parameters ¶ms) const; /** * Cast the object to a string @@ -187,7 +187,7 @@ public: * * @return Value The object as a string */ - virtual Value __toString(); + Php::Value __toString() const; /** * Cast the object to an integer @@ -197,7 +197,7 @@ public: * * @return int Integer value */ - virtual long __toInteger(); + Php::Value __toInteger() const; /** * Cast the object to a float @@ -207,7 +207,7 @@ public: * * @return double Floating point value */ - virtual double __toFloat(); + Php::Value __toFloat() const; /** * Cast the object to a boolean @@ -217,17 +217,17 @@ public: * * @return bool */ - virtual bool __toBool(); + Value __toBool() const; /** - * Comparison operator + * Compare the object with a different object * * Check how a different object compares to this object * * @param that Object to compare with * @return int */ - bool operator<(const Base &that) const; + int __compare(const Base &base) const; private: diff --git a/include/callstatic.h b/include/callstatic.h new file mode 100644 index 0000000..3979ef3 --- /dev/null +++ b/include/callstatic.h @@ -0,0 +1,64 @@ +/** + * CallStatic.h + * + * Class that performs a lot of C++11 magic to find out if the __callStatic() + * method was implemented by the user, and if it was, calls it + * + */ + +namespace Php { + +/** + * SFINAE test to check if the __callStatic method is defined + * + * This type trait checks if the __callStatic method is defined in class T + * + * @see http://stackoverflow.com/questions/257288/is-it-possible-to-write-a-c-template-to-check-for-a-functions-existence + */ +template <typename T> +class HasCallStatic +{ + typedef char one; + typedef long two; + + template <typename C> static one test( decltype(&C::__callStatic) ) ; + template <typename C> static two test(...); + +public: + static const bool value = sizeof(test<T>(0)) == sizeof(char); +}; + +/** + * Function that only exists if the class T has a __callStatic method + * @param name Name of the function + * @param params Parameters passed to the function + * @return Value + */ +template<typename T> +typename std::enable_if<HasCallStatic<T>::value, Value >::type +callStatic(const char *name, Parameters ¶ms) +{ + // call the __callStatic() function + return T::__callStatic(name, params); +} + +/** + * Function that only exists if the class T does not have a __callStatic method + * @param name Name of the function + * @param params Parameters passed to the function + * @return Value + */ +template<typename T> +typename std::enable_if<!HasCallStatic<T>::value >::type +callStatic(const char *name, Parameters ¶ms) +{ + std::cout << "has NO call static" << std::endl; + + return nullptr; +} + +/** + * End namespace + */ +} + diff --git a/include/class.h b/include/class.h index bb05fdd..6fd938c 100644 --- a/include/class.h +++ b/include/class.h @@ -25,6 +25,23 @@ extern struct _zend_class_entry *zend_ce_arrayaccess; //extern struct _zend_class_entry *zend_ce_serializable; /** + * SFINAE test to check if the __callStatic method is defined + * @see http://stackoverflow.com/questions/257288/is-it-possible-to-write-a-c-template-to-check-for-a-functions-existence + */ +template <typename T> +class HasCallStatic +{ + typedef char one; + typedef long two; + + template <typename C> static one test( decltype(&C::__callStatic) ) ; + template <typename C> static two test(...); + +public: + static const bool value = sizeof(test<T>(0)) == sizeof(char); +}; + +/** * Set up namespace */ namespace Php { @@ -217,8 +234,217 @@ private: // check if the templated class overrides from the Serializable class return std::is_base_of<Serializable,T>::value; } + + /** + * Call a method + * @param base Object to call on + * @param name Name of the method + * @param params + * @return Value + */ + virtual Value call(Base *base, const char *name, Parameters ¶ms) const override + { + // cast to the user object + T *object = (T *)base; + + // call the method on the base object + return base->__call(name, params); + } + + /** + * SFINAE test to check if the __callStatic method is defined + * + * This type trait checks if the __callStatic method is defined in class T + * + * @see http://stackoverflow.com/questions/257288/is-it-possible-to-write-a-c-template-to-check-for-a-functions-existence + */ + template <typename X> + class HasCallStatic + { + typedef char one; + typedef long two; + + template <typename C> static one test( decltype(&C::__callStatic) ) ; + template <typename C> static two test(...); + + public: + static const bool value = sizeof(test<X>(0)) == sizeof(char); + }; + + /** + * Function that only exists if the class T has a __callStatic method + * @param name Name of the function + * @param params Parameters passed to the function + * @return Value + */ + template<typename X> + typename std::enable_if<HasCallStatic<X>::value, Value >::type + static maybeCallStatic(const char *name, Parameters ¶ms) + { + // call the __callStatic() function + return X::__callStatic(name, params); + } + + /** + * Function that only exists if the class T does not have a __callStatic method + * @param name Name of the function + * @param params Parameters passed to the function + * @return Value + */ + template<typename X> + typename std::enable_if<!HasCallStatic<X>::value, Value >::type + static maybeCallStatic(const char *name, Parameters ¶ms) + { + // this is not implemented + notImplemented(); + + // unreachable + return nullptr; + } + + /** + * Call a the __callStatic() function + * @param name Name of the function + * @param params Parameters passed to the function + * @return Value + */ + virtual Value callStatic(const char *name, Parameters ¶ms) const override + { + return maybeCallStatic<T>(name, params); + } + + /** + * Call the __invoke() method + * @param base Object to call it on + * @param params Parameters to pass + * @return Value + */ + virtual Value invoke(Base *object, Parameters ¶ms) const override + { + // cast to actual object + T *obj = (T *)object; + + // pass on + return obj->__invoke(params); + } + + /** + * Cast to string function + * @param base + * @return Value + */ + virtual Value toString(Base *base) const override + { + // cast to actual object + T *obj = (T *)base; + + // pass on + return obj->__toString().setType(Type::String); + } + + /** + * Cast to integer function + * @param base + * @return Value + */ + virtual Value toInteger(Base *base) const override + { + // cast to actual object + T *obj = (T *)base; + + // pass on + return obj->__toInteger().setType(Type::Numeric); + } + + /** + * Cast to float function + * @param base + * @return Value + */ + virtual Value toFloat(Base *base) const override + { + // cast to actual object + T *obj = (T *)base; + + // pass on + return obj->__toFloat().setType(Type::Float); + } + + /** + * Cast to bool function + * @param base + * @return Value + */ + virtual Value toBool(Base *base) const override + { + // cast to actual object + T *obj = (T *)base; + + // pass on + return obj->__toBool().setType(Type::Bool); + } + + /** + * Function to retrieve a property + * @param base + * @param name + * @param value + * @return Value + */ + virtual Value get(Base *base, const Value &name) const override + { + // cast to actual object + T *obj = (T *)base; + + // pass on + return obj->__get(name); + } /** + * Function to set/overwrite a property + * @param base + * @param name + * @param value + */ + virtual void set(Base *base, const Value &name, const Value &value) const override + { + // cast to actual object + T *obj = (T *)base; + + // pass on + obj->__set(name, value); + } + + /** + * Function to remove a property + * @param base + * @param name + */ + virtual void unset(Base *base, const Value &name) const override + { + // cast to actual object + T *obj = (T *)base; + + // pass on + obj->__unset(name); + } + + /** + * Function to check if a property is set + * @param base + * @param name + * @return bool + */ + virtual bool callIsset(Base *base, const Value &name) const override + { + // cast to actual object + T *obj = (T *)base; + + // pass on + return obj->__isset(name); + } + + /** * Compare two objects * @param object1 * @param object2 @@ -237,7 +463,7 @@ private: // they must be identical return 0; } - + /** * Namespaces have access to the private base class */ diff --git a/include/classbase.h b/include/classbase.h index b4312dd..d19a1b2 100644 --- a/include/classbase.h +++ b/include/classbase.h @@ -107,17 +107,32 @@ public: virtual ~ClassBase(); /** - * Construct a new instance of the object - * @return Base + * 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 ns Namespace name */ - virtual Base* construct() const = 0; - + void initialize(const std::string &ns); + +protected: /** - * Create a clone of an object - * @param orig + * Construct a new instance of the object, or to clone the object * @return Base */ - virtual Base *clone(Base *orig) const = 0; + virtual Base* construct() const { return nullptr; } + virtual Base *clone(Base *orig) const { return nullptr; } + + /** + * Methods to check if a certain interface is overridden + * @return bool + */ + virtual bool traversable() const { return false; } + virtual bool serializable() const { return false; } /** * Compare two objects @@ -125,36 +140,51 @@ public: * @param object2 * @return int */ - virtual int compare(Base *object1, Base *object2) const = 0; - + virtual int callCompare(Base *object1, Base *object2) const { return 1; } + /** - * Is this a traversable class? - * @return bool + * Call the __call(), __invoke() or __callStatic() method + * @param base Object to call on + * @param name Name of the method + * @param params Parameters to pass to the method + * @return Value */ - virtual bool traversable() const = 0; - + virtual Value callCall(Base *base, const char *name, Parameters ¶ms) const { return nullptr; } + virtual Value callInvoke(Base *base, Parameters ¶ms) const { return nullptr; } + virtual Value callCallStatic(const char *name, Parameters ¶ms) const { return nullptr; } + /** - * Is this a serializable class? - * @return bool + * Casting functions + * @param base + * @return Value */ - virtual bool serializable() const = 0; + virtual Value callToString(Base *base) const { return Value(Type::String); } + virtual Value callToInteger(Base *base) const { return Value(Type::Numeric); } + virtual Value callToFloat(Base *base) const { return Value(Type::Float); } + virtual Value callToBool(Base *base) const { return Value(Type::Bool); } /** - * 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 ns Namespace name + * Function to get and set properties + * @param base + * @param name + * @param value + * @return Value */ - void initialize(const std::string &ns); + virtual Value callGet(Base *base, const Value &name) const { return nullptr; } + virtual void callSet(Base *base, const Value &name, const Value &value) const {} + virtual void callUnset(Base *base, const Value &name) const {} + virtual bool callIsset(Base *base, const Value &name) const { return false; } + protected: /** + * Function that can be called by a derived method when a certain function + * is not implemented + */ + static void notImplemented(); + + /** * Add a method to the class * * The method will be accessible as one of the class methods in your PHP @@ -268,19 +298,24 @@ private: const struct _zend_function_entry *entries(); /** - * Static member functions to clone objects based on this class + * 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 * @return zend_object_value Object info */ + static struct _zend_object_value createObject(struct _zend_class_entry *entry); static struct _zend_object_value cloneObject(struct _zval_struct *val); /** - * 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 - * @return zend_object_value The newly created object + * 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? */ - static struct _zend_object_value createObject(struct _zend_class_entry *entry); + static void callMethod(int ht, struct _zval_struct *return_value, struct _zval_struct **return_value_ptr, struct _zval_struct *this_ptr, int return_value_used); + static void callInvoke(int ht, struct _zval_struct *return_value, struct _zval_struct **return_value_ptr, struct _zval_struct *this_ptr, int return_value_used); /** * Function that is used to count the number of elements in the object @@ -382,29 +417,25 @@ private: static void unsetProperty(struct _zval_struct *object, struct _zval_struct *member); /** - * Method that is called when a undefined method is invoked + * Method that returns information about the function signature of a undefined method + * @param object_ptr * @param method - * @param ht - * @param return_value - * @param return_value_ptr - * @param this_ptr - * @param return_value_used - * @param tsrm_ls - * @return integer + * @param method_len + * @param key + * @return zend_function */ - static int callMethod(const char *method, int ht, struct _zval_struct *return_value, struct _zval_struct **return_value_ptr, struct _zval_struct *this_ptr, int return_value_used); - static int callMethod(char *method, int ht, struct _zval_struct *return_value, struct _zval_struct **return_value_ptr, struct _zval_struct *this_ptr, int return_value_used); + static union _zend_function *getMethod(struct _zval_struct **object_ptr, char *method, int method_len, const struct _zend_literal *key); + static union _zend_function *getMethod(struct _zval_struct **object_ptr, char *method, int method_len); /** - * Method that returns information about the function signature of a undefined method + * Method that returns information about the function signature of an undefined static method * @param object_ptr * @param method * @param method_len * @param key * @return zend_function */ - static union _zend_function *getMethod(struct _zval_struct **object_ptr, char *method, int method_len, const struct _zend_literal *key); - static union _zend_function *getMethod(struct _zval_struct **object_ptr, char *method, int method_len); + static union _zend_function *getStaticMethod(struct _zend_class_entry *entry, char* method, int method_len); /** * Method that returns information about the __invoke() method diff --git a/include/interface.h b/include/interface.h index f7ef387..b0469f4 100644 --- a/include/interface.h +++ b/include/interface.h @@ -40,59 +40,6 @@ public: private: /** - * Construct a new instance of the object - * @return Base - */ - virtual Base* construct() const override - { - // this does not occur for interfaces - return nullptr; - } - - /** - * Construct a clone of the object - * @param orig - * @return Base - */ - virtual Base* clone(Base *orig) const override - { - // this does not occur for interfaces - return nullptr; - } - - /** - * Is this a traversable interface? - * @return bool - */ - virtual bool traversable() const override - { - // interfaces are never traversed - return false; - } - - /** - * Compare two objects - * @param object1 - * @param object2 - * @return int - */ - virtual int compare(Base *object1, Base *object2) const override - { - // this is never called for interfaces - return 0; - } - - /** - * Is this a serializable class? - * @return bool - */ - virtual bool serializable() const override - { - // not called for interfaces - return false; - } - - /** * Namespaces have access to the private base class */ friend class Namespace; diff --git a/src/base.cpp b/src/base.cpp index 516663d..2c46147 100644 --- a/src/base.cpp +++ b/src/base.cpp @@ -86,7 +86,7 @@ MixedObject *Base::store(zend_class_entry *entry) * @param key * @return bool */ -bool Base::__isset(const Php::Value &key) +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 @@ -102,7 +102,7 @@ bool Base::__isset(const Php::Value &key) * @param key * @param value */ -void Base::__set(const Php::Value &key, const Php::Value &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 @@ -118,7 +118,7 @@ void Base::__set(const Php::Value &key, const Php::Value &value) * @param key * @return value */ -Php::Value Base::__get(const Php::Value &key) +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 @@ -136,7 +136,7 @@ Php::Value Base::__get(const Php::Value &key) * * @param key */ -void Base::__unset(const Php::Value &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 @@ -154,7 +154,7 @@ void Base::__unset(const Php::Value &key) * @param params The parameters that were passed to the function * @return Value The return value */ -Value Base::__call(const char *method, Parameters ¶ms) +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 @@ -173,7 +173,7 @@ Value Base::__call(const char *method, Parameters ¶ms) * @param params The parameters that were passed to the function * @return Value The return value */ -Value Base::__invoke(Parameters ¶ms) +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 @@ -191,7 +191,7 @@ Value Base::__invoke(Parameters ¶ms) * * @return Value The object as a string */ -Value Base::__toString() +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 @@ -209,7 +209,7 @@ Value Base::__toString() * * @return int Integer value */ -long Base::__toInteger() +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 @@ -227,7 +227,7 @@ long Base::__toInteger() * * @return double Floating point value */ -double Base::__toFloat() +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 @@ -245,7 +245,7 @@ double Base::__toFloat() * * @return bool */ -bool Base::__toBool() +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 @@ -256,14 +256,14 @@ bool Base::__toBool() } /** - * Comparison operator + * Compare the object with a different object * * Check how a different object compares to this object * * @param that Object to compare with * @return int */ -bool Base::operator<(const Base &that) const +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 diff --git a/src/classbase.cpp b/src/classbase.cpp index ac3a0f1..348aaf3 100644 --- a/src/classbase.cpp +++ b/src/classbase.cpp @@ -52,22 +52,39 @@ static Base *cpp_object(const zval *val) } /** + * Extended zend_internal_function structure that we use to store an + * instance of the ClassBase object. We need this for static method calls + */ +struct CallData +{ + // the internal function is the first member, so + // that it is possible to cast an instance of this + // struct to a zend_internal_function + zend_internal_function func; + + // and a pointer to the ClassBase object + ClassBase *self; +}; + +/** * Handler function that runs the __call function * @param ... All normal parameters for function calls */ -static void call_method(INTERNAL_FUNCTION_PARAMETERS) +void ClassBase::callMethod(INTERNAL_FUNCTION_PARAMETERS) { // retrieve the originally called (and by us allocated) function object // (this was copied from the zend engine source code, code looks way too // ugly to be made by me) - zend_internal_function *func = (zend_internal_function *)EG(current_execute_data)->function_state.function; + CallData *data = (CallData *)EG(current_execute_data)->function_state.function; + zend_internal_function *func = &data->func; // retrieve the function name const char *name = func->function_name; + ClassBase *meta = data->self; - // the function structure was allocated by ourselves in the get_method function, - // we no longer need it now - efree(func); + // 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 @@ -80,9 +97,10 @@ static void call_method(INTERNAL_FUNCTION_PARAMETERS) // retrieve the base object Base *base = params.object(); - - // call the actual __call method on the base object - result = base->__call(name, params); + + // 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) { @@ -100,15 +118,19 @@ static void call_method(INTERNAL_FUNCTION_PARAMETERS) * Handler function that runs the __invoke function * @param ... All normal parameters for function calls */ -static void call_invoke(INTERNAL_FUNCTION_PARAMETERS) +void ClassBase::callInvoke(INTERNAL_FUNCTION_PARAMETERS) { // retrieve the originally called (and by us allocated) function object // (this was copied from the zend engine source code, code looks way too // ugly to be made by me) - zend_internal_function *func = (zend_internal_function *)EG(current_execute_data)->function_state.function; + CallData *data = (CallData *)EG(current_execute_data)->function_state.function; - // we no longer need it (question: why have we allocated this on the heap???) - efree(func); + // get self reference + ClassBase *meta = data->self; + + // the data structure was allocated by ourselves in the getMethod or + // getStaticMethod functions, we no longer need it now + efree(data); // the function could throw an exception try @@ -123,7 +145,7 @@ static void call_invoke(INTERNAL_FUNCTION_PARAMETERS) Base *base = params.object(); // call the actual __invoke method on the base object - result = base->__invoke(params); + result = meta->callInvoke(base, params); } catch (const NotImplemented &exception) { @@ -178,21 +200,68 @@ zend_function *ClassBase::getMethod(zval **object_ptr, char *method_name, int me // 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 *function = (zend_internal_function *)emalloc(sizeof(zend_internal_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 = call_method; + function->handler = &ClassBase::callMethod; function->arg_info = nullptr; function->num_args = 0; function->scope = entry; function->fn_flags = ZEND_ACC_CALL_VIA_HANDLER; function->function_name = method_name; + // store pointer to ourselves + data->self = cpp_class(entry); + // done (cast to zend_function* is allowed, because a zend_function is a union // that has one member being a zend_internal_function) - return (zend_function *)function; + return (zend_function *)data; +} + +/** + * Method that is called right before a static method call is attempted + * @param entry + * @param method + * @param method_len + * @return zend_function + */ +zend_function *ClassBase::getStaticMethod(zend_class_entry *entry, char* method, int method_len) +{ + // 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); +#else + auto *defaultFunction = zend_std_get_static_method(entry, method, method_len, nullptr); +#endif + + // did the default implementation do anything? + if (defaultFunction) return defaultFunction; + + // just like we did in getMethod() (see comment there) we are going to dynamically + // allocate data holding information about the function + auto *data = (CallData *)emalloc(sizeof(CallData)); + auto *function = &data->func; + + // we're going to set all properties + function->type = ZEND_INTERNAL_FUNCTION; + function->module = nullptr; + function->handler = ClassBase::callMethod; + function->arg_info = nullptr; + function->num_args = 0; + function->scope = nullptr; + function->fn_flags = ZEND_ACC_CALL_VIA_HANDLER; + function->function_name = method; + + // store pointer to ourselves + data->self = cpp_class(entry); + + // done (cast to zend_function* is allowed, because a zend_function is a union + // that has one member being a zend_internal_function) + return (zend_function *)data; } /** @@ -217,22 +286,26 @@ int ClassBase::getClosure(zval *object, zend_class_entry **entry_ptr, zend_funct // just like we did for getMethod(), we're going to dynamically allocate memory // with all information about the function - auto *function = (zend_internal_function *)emalloc(sizeof(zend_internal_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 = call_invoke; + function->handler = &ClassBase::callInvoke; function->arg_info = nullptr; function->num_args = 0; function->scope = entry; function->fn_flags = ZEND_ACC_CALL_VIA_HANDLER; function->function_name = nullptr; + // store pointer to ourselves + data->self = cpp_class(entry); + // assign this dynamically allocated variable to the func parameter // the case is ok, because zend_internal_function is a member of the // zend_function union - *func = (zend_function *)function; + *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) @@ -320,7 +393,7 @@ int ClassBase::compare(zval *object1, zval *object2) Base *base2 = cpp_object(object2); // run the compare method - return meta->compare(base1, base2); + return meta->callCompare(base1, base2); } catch (const NotImplemented &exception) { @@ -353,6 +426,12 @@ int ClassBase::cast(zval *object, zval *retval, int type) // get the base object Base *base = cpp_object(object); + // retrieve the class entry linked to this object + auto *entry = zend_get_class_entry(object); + + // we need the C++ class meta-information object + ClassBase *meta = cpp_class(entry); + // retval it not yet initialized --- and again feelings of disbelief, // frustration, wonder and anger come up when you see that there are not two // functions in the Zend engine that have a comparable API @@ -370,11 +449,11 @@ int ClassBase::cast(zval *object, zval *retval, int type) // check type switch ((Type)type) { - case Type::Numeric: result = Value(base->__toInteger()).detach(); break; - case Type::Float: result = Value(base->__toFloat()).detach(); break; - case Type::Bool: result = Value(base->__toBool()).detach(); break; - case Type::String: result = base->__toString().setType(Type::String).detach(); break; - default: throw NotImplemented(); break; + case Type::Numeric: result = meta->callToInteger(base).detach(); break; + case Type::Float: result = meta->callToFloat(base).detach(); break; + case Type::Bool: result = meta->callToBool(base).detach(); break; + case Type::String: result = meta->callToString(base).detach(); break; + default: throw NotImplemented(); break; } // @todo do we turn into endless conversion if the __toString object returns 'this' ?? @@ -733,12 +812,21 @@ zval *ClassBase::readProperty(zval *object, zval *name, int type, const struct _ // from PHP. If someone wants to get a reference to such an internal variable, // that is in most cases simply impossible. + // retrieve the object and class + Base *base = cpp_object(object); + + // retrieve the class entry linked to this object + auto *entry = zend_get_class_entry(object); + + // we need the C++ class meta-information object + ClassBase *meta = cpp_class(entry); + // the default implementation throws an exception, so by catching // the exception we know if the object was implemented by the user or not try { // retrieve value - Value value = cpp_object(object)->__get(name); + Value value = meta->callGet(base, name); // 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 @@ -796,12 +884,21 @@ void ClassBase::writeProperty(zval *object, zval *name, zval *value) void ClassBase::writeProperty(zval *object, zval *name, zval *value, const struct _zend_literal *key) #endif { + // retrieve the object and class + Base *base = cpp_object(object); + + // retrieve the class entry linked to this object + auto *entry = zend_get_class_entry(object); + + // we need the C++ class meta-information object + ClassBase *meta = cpp_class(entry); + // the default implementation throws an exception, if we catch that // we know for sure that the user has not overridden the __set method try { // call the default - cpp_object(object)->__set(name, value); + meta->callSet(base, name, value); } catch (const NotImplemented &exception) { @@ -855,14 +952,20 @@ int ClassBase::hasProperty(zval *object, zval *name, int has_set_exists, const s // get the cpp object Base *base = cpp_object(object); + // retrieve the class entry linked to this object + auto *entry = zend_get_class_entry(object); + + // we need the C++ class meta-information object + ClassBase *meta = cpp_class(entry); + // call the C++ object - if (!base->__isset(name)) return false; + if (!meta->callIsset(base, name)) 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 = base->__get(name); + Value value = meta->callGet(base, name); // should we check on NULL? switch (has_set_exists) { @@ -912,8 +1015,14 @@ void ClassBase::unsetProperty(zval *object, zval *member, const struct _zend_lit // 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); + + // we need the C++ class meta-information object + ClassBase *meta = cpp_class(entry); + // call the __unset method - cpp_object(object)->__unset(member); + meta->callUnset(cpp_object(object), member); } catch (const NotImplemented &exception) { @@ -1130,6 +1239,9 @@ void ClassBase::initialize(const std::string &prefix) // we need a special constructor entry.create_object = &ClassBase::createObject; + // register function that is called for static method calls + entry.get_static_method = &ClassBase::getStaticMethod; + // for traversable classes we install a special method to get the iterator if (traversable()) entry.get_iterator = &ClassBase::getIterator; @@ -1183,6 +1295,16 @@ void ClassBase::initialize(const std::string &prefix) } /** + * 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 |