summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorEmiel Bruijntjes <emiel.bruijntjes@copernica.com>2014-03-11 14:14:12 +0100
committerEmiel Bruijntjes <emiel.bruijntjes@copernica.com>2014-03-11 14:14:12 +0100
commitfc5c9305507f8eb899070b0d6d72ddb27c3bfc0f (patch)
tree59228bf56f74a8be1179c3cc45c25efcfda2295d /src
parentcdf237fb05c396d258c2c82a441a2f9b4deff282 (diff)
implemented __call() function
Diffstat (limited to 'src')
-rw-r--r--src/base.cpp21
-rw-r--r--src/callable.cpp2
-rw-r--r--src/classbase.cpp92
3 files changed, 114 insertions, 1 deletions
diff --git a/src/base.cpp b/src/base.cpp
index b561899..85cb85c 100644
--- a/src/base.cpp
+++ b/src/base.cpp
@@ -144,6 +144,27 @@ void Base::__unset(const Php::Value &key)
}
/**
+ * Call a method
+ *
+ * This method is called when a method is called from the PHP script that
+ * was not explicitly defined. You can use this to catch variable method
+ * names, or to support all thinkable method names.
+ *
+ * @param method Name of the method that was called
+ * @param params The parameters that were passed to the function
+ * @return Value The return value
+ */
+Value Base::__call(const char *method, Parameters &params)
+{
+ // 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;
+}
+
+/**
* End namespace
*/
}
diff --git a/src/callable.cpp b/src/callable.cpp
index bf83e10..c3db05d 100644
--- a/src/callable.cpp
+++ b/src/callable.cpp
@@ -15,7 +15,7 @@ namespace Php {
/**
* Function that is called by the Zend engine every time that a function gets called
- * @param ht
+ * @param ht
* @param return_value
* @param return_value_ptr
* @param this_ptr
diff --git a/src/classbase.cpp b/src/classbase.cpp
index 91227a1..e6fb88f 100644
--- a/src/classbase.cpp
+++ b/src/classbase.cpp
@@ -52,6 +52,95 @@ static Base *cpp_object(const zval *val)
}
/**
+ * Handler function that runs the __call function
+ * @param ??
+ */
+static void call_method(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;
+
+ // retrieve the function name
+ const char *name = func->function_name;
+
+ // the function structure was allocated by ourselves in the get_method function,
+ // we no longer need it now
+ efree(func);
+
+ // the function could throw an exception
+ try
+ {
+ // wrap the return value
+ Value result(return_value, true);
+
+ // construct parameters
+ Parameters params(this_ptr, ZEND_NUM_ARGS());
+
+ // retrieve the base object
+ Base *base = params.object();
+
+ // call the actual __call method on the base object
+ result = base->__call(name, params);
+ }
+ catch (const NotImplemented &exception)
+ {
+ // because of the two-step nature, we are going to report the error ourselves
+ zend_error(E_ERROR, "Undefined method %s", name);
+ }
+ catch (Exception &exception)
+ {
+ // process the exception
+ exception.process();
+ }
+}
+
+/**
+ * Method that returns the function definition of the __call function
+ * @param object_ptr
+ * @param method_name
+ * @param method_len
+ * @return zend_function
+ */
+zend_function *ClassBase::getMethod(zval **object_ptr, char *method_name, int method_len)
+{
+ // 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, but the call_method function
+ // will later never be called again -- this is typical
+
+
+ // retrieve the class entry linked to this object
+ auto *entry = zend_get_class_entry(*object_ptr);
+
+ // we need the C++ class meta-information object
+ ClassBase *meta = cpp_class(entry);
+
+ // 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 *function = (zend_internal_function *)emalloc(sizeof(zend_internal_function));
+
+ // we're going to set all properties
+ function->type = ZEND_INTERNAL_FUNCTION;
+ function->module = nullptr;
+ function->handler = call_method;
+ function->arg_info = nullptr;
+ function->num_args = 0;
+ function->scope = meta->_entry;
+ function->fn_flags = ZEND_ACC_CALL_VIA_HANDLER;
+ function->function_name = method_name;
+
+ // 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;
+}
+
+/**
* Retrieve pointer to our own object handlers
* @return zend_object_handlers
*/
@@ -87,6 +176,9 @@ zend_object_handlers *ClassBase::objectHandlers()
handlers.has_property = &ClassBase::hasProperty;
handlers.unset_property = &ClassBase::unsetProperty;
+ // when a method is called (__call)
+ handlers.get_method = &ClassBase::getMethod;
+
// remember that object is now initialized
initialized = true;