summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--documentation/magic-methods.html58
-rw-r--r--include/base.h10
-rw-r--r--include/classbase.h19
-rw-r--r--src/base.cpp19
-rw-r--r--src/classbase.cpp124
5 files changed, 209 insertions, 21 deletions
diff --git a/documentation/magic-methods.html b/documentation/magic-methods.html
index 0f2067f..a12809a 100644
--- a/documentation/magic-methods.html
+++ b/documentation/magic-methods.html
@@ -192,15 +192,24 @@ unset($user->email);
?>
</code></pre>
</p>
-<h2>Magic method __call()</h2>
+<h2>Magic methods __call() and __invoke()</h2>
<p>
C++ methods need to be registered explicitly in your extension get_module()
- startup function to make them accessible. However, when you override the __call()
- method, you can accept all possible method calls. It does not matter what
- the name of the method is, when something that looks like a method is called
- from PHP user space, it will trigger a call to your __call() method.
+ startup function to be accessible from PHP user space. However, when you override the __call()
+ method, you can accept all calls - even calls to methods that do not exist.
+ When someone makes a call from user space to something that looks like a method,
+ it will be passed to this __call() method. In a script you can thus use
+ $object->something(), $object->whatever() or $object->anything() - it does not
+ matter what the name of the method is, all these calls are passed on to the
+ __call() method in the C++ class.
</p>
<p>
+ Next to the magic __call() function, the PHP-CPP library also supports the
+ __invoke() method. This is a method that gets called when an object instance
+ is used <i>as if</i> it was a function. This can be compared with overloading
+ the operator () in a C++ class. By implementing the __invoke() method, scripts
+ from PHP user space can create a method, and then use it as a function.
+<p>
<pre class="language-c++"><code>
#include &lt;phpcpp.h&gt;
@@ -217,12 +226,22 @@ public:
virtual ~MyClass() {}
/**
- * Override the __call() method to accept all method calls
+ * Regular method
+ * @param params Parameters that were passed to the method
+ * @return Value The return value
+ */
+ Php::Value regular(Php::Parameters &amp;params)
+ {
+ return "this is a regular method";
+ }
+
+ /**
+ * Overriden __call() method to accept all method calls
* @param name Name of the method that is called
* @param params Parameters that were passed to the method
* @return Value The return value
*/
- Php::Value __call(const char *name, Php::Parameters &amp;params)
+ virtual Php::Value __call(const char *name, Php::Parameters &amp;params) override
{
// the return value
std::string retval = name;
@@ -237,6 +256,28 @@ public:
// done
return retval;
}
+
+ /**
+ * Overridden __invoke() method so that objects can be called directly
+ * @param params Parameters that were passed to the method
+ * @return Value The return value
+ */
+ virtual Php::Value __invoke(Php::Parameters &amp;params) override
+ {
+ // the return value
+ std::string retval = "invoke";
+
+ // loop through the parameters
+ for (auto &amp;param : params)
+ {
+ // append parameter string value to return value
+ retval += " " + param.stringValue();
+ }
+
+ // done
+ return retval;
+ }
+
};
/**
@@ -258,6 +299,9 @@ extern "C" {
// which methods are accessible
Php::Class&lt;MyClass&gt; myClass("MyClass");
+ // register the regular method
+ myClass.method("regular", &amp;MyClass::regular);
+
// add the class to the extension
myExtension.add(std::move(myClass));
diff --git a/include/base.h b/include/base.h
index 7e33d8d..ed6936d 100644
--- a/include/base.h
+++ b/include/base.h
@@ -168,6 +168,16 @@ public:
*/
virtual Value __call(const char *method, Parameters &params);
+ /**
+ * 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
+ */
+ virtual Value __invoke(Parameters &params);
private:
/**
diff --git a/include/classbase.h b/include/classbase.h
index f0fe37b..6dba316 100644
--- a/include/classbase.h
+++ b/include/classbase.h
@@ -352,11 +352,28 @@ private:
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);
-
+ /**
+ * Method that returns information about the function signature of a undefined 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);
/**
+ * Method that returns information about the __invoke() method
+ * @param object
+ * @param entry
+ * @param func
+ * @param object_ptr
+ * @return int
+ */
+ static int getClosure(struct _zval_struct *object, struct _zend_class_entry **entry, union _zend_function **func, struct _zval_struct **object_ptr);
+
+ /**
* Name of the class
* @var string
*/
diff --git a/src/base.cpp b/src/base.cpp
index 85cb85c..a5dc7b3 100644
--- a/src/base.cpp
+++ b/src/base.cpp
@@ -165,6 +165,25 @@ Value Base::__call(const char *method, Parameters &params)
}
/**
+ * Call the class as if it was a function
+ *
+ * This method is called when a an object is used with () operators:
+ * $object(). You can override this method to make objects callable.
+ *
+ * @param params The parameters that were passed to the function
+ * @return Value The return value
+ */
+Value Base::__invoke(Parameters &params)
+{
+ // 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/classbase.cpp b/src/classbase.cpp
index 2c8efef..00c679a 100644
--- a/src/classbase.cpp
+++ b/src/classbase.cpp
@@ -53,7 +53,7 @@ static Base *cpp_object(const zval *val)
/**
* Handler function that runs the __call function
- * @param ??
+ * @param ... All normal parameters for function calls
*/
static void call_method(INTERNAL_FUNCTION_PARAMETERS)
{
@@ -97,6 +97,48 @@ 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)
+{
+ // 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;
+
+ // we no longer need it (question: why have we allocated this on the heap???)
+ 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 __invoke method on the base object
+ result = base->__invoke(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
+ exception.process();
+ }
+}
+
+/**
* Method that returns the function definition of the __call function
* @param object_ptr
* @param method_name
@@ -116,12 +158,20 @@ zend_function *ClassBase::getMethod(zval **object_ptr, char *method_name, int me
// 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);
+#else
+ auto *defaultFunction = std_object_handlers.get_method(object_ptr, method_name, method_len, key);
+#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);
- // 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,
@@ -131,14 +181,14 @@ zend_function *ClassBase::getMethod(zval **object_ptr, char *method_name, int me
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;
+ function->type = ZEND_INTERNAL_FUNCTION;
+ function->module = nullptr;
+ function->handler = call_method;
+ function->arg_info = nullptr;
+ function->num_args = 0;
+ function->scope = 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)
@@ -146,6 +196,53 @@ zend_function *ClassBase::getMethod(zval **object_ptr, char *method_name, int me
}
/**
+ * Method that returns the closure -- this is the __invoke handler!
+ * @param object
+ * @param entry_ptr
+ * @param func
+ * @param object_ptr
+ * @return int
+ */
+int ClassBase::getClosure(zval *object, zend_class_entry **entry_ptr, zend_function **func, zval **object_ptr)
+{
+ // 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);
+
+ // 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));
+
+ // we're going to set all properties
+ function->type = ZEND_INTERNAL_FUNCTION;
+ function->module = nullptr;
+ function->handler = call_invoke;
+ function->arg_info = nullptr;
+ function->num_args = 0;
+ function->scope = entry;
+ function->fn_flags = ZEND_ACC_CALL_VIA_HANDLER;
+ function->function_name = nullptr;
+
+ // 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;
+
+ // 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
*/
@@ -181,8 +278,9 @@ zend_object_handlers *ClassBase::objectHandlers()
handlers.has_property = &ClassBase::hasProperty;
handlers.unset_property = &ClassBase::unsetProperty;
- // when a method is called (__call)
+ // when a method is called (__call and __invoke)
handlers.get_method = &ClassBase::getMethod;
+ handlers.get_closure = &ClassBase::getClosure;
// remember that object is now initialized
initialized = true;