summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEmiel Bruijntjes <emiel.bruijntjes@copernica.com>2014-03-10 12:26:04 +0100
committerEmiel Bruijntjes <emiel.bruijntjes@copernica.com>2014-03-10 12:26:04 +0100
commit4872cc627642044f46ba8a8726902592a1fae05f (patch)
treea416aff6d3511b17923001a664ece1deca01b4d7
parentd2e10c764d1b8860dd798eda3055fc957ff556ad (diff)
first setup for magic methods __get(), __set(), __isset() and __unset()
-rw-r--r--documentation/magic-methods-and-interfaces.html8
-rw-r--r--include/base.h44
-rw-r--r--include/classbase.h34
-rw-r--r--src/base.cpp66
-rw-r--r--src/classbase.cpp177
-rw-r--r--src/includes.h1
-rw-r--r--src/notimplemented.h51
7 files changed, 377 insertions, 4 deletions
diff --git a/documentation/magic-methods-and-interfaces.html b/documentation/magic-methods-and-interfaces.html
index 244ebcd..f485805 100644
--- a/documentation/magic-methods-and-interfaces.html
+++ b/documentation/magic-methods-and-interfaces.html
@@ -390,7 +390,7 @@ public:
* @param object The object that is being iterated over
* @param map The internal C++ map that is being iterated over
*/
- MapIterator(Php::Base *object, const std::map&lt;std::string,std::string&gt; &map) :
+ MapIterator(Map *object, const std::map&lt;std::string,std::string&gt; &map) :
Php::Iterator(object), _map(map), _iter(map.begin()) {}
/**
@@ -563,11 +563,11 @@ extern "C" {
properties.
</p>
<p>
- For this to work, we has to add the Php::Traversable class as base class
+ For this to work, we had to add the Php::Traversable class as base class
to the Map class, and implement the getIterator() method. This method
returns a new MapIterator class, which is allocated on the heap. Don't
worry about memory management: the PHP-CPP library will destruct your
- operator the moment the foreach loop is finished.
+ iterator the moment the foreach loop is finished.
</p>
<p>
The MapIterator class is derived from the Php::Iterator class, and
@@ -581,5 +581,5 @@ extern "C" {
<p>
Our MapIterator implementation internally is just a small wrapper around
a C++ iterator class. It is of course up to you to create more complex
- implementation when needed.
+ iterators when needed.
</p>
diff --git a/include/base.h b/include/base.h
index b027d5a..d944e3c 100644
--- a/include/base.h
+++ b/include/base.h
@@ -111,6 +111,50 @@ public:
{
return Value(this)[name];
}
+
+ /**
+ * 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
+ */
+ virtual bool __isset(const Php::Value &key);
+
+ /**
+ * 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
+ */
+ virtual void __set(const Php::Value &key, const Php::Value &value);
+
+ /**
+ * Retrieve a property
+ *
+ * The default implementation does nothing, and the script will fall back
+ * to accessing the regular object properties
+ *
+ * @param key
+ * @return value
+ */
+ virtual Php::Value __get(const Php::Value &key);
+
+ /**
+ * Remove a member
+ *
+ * The default implementation does nothing, and the script will fall back
+ * to accessing the regular object properties
+ *
+ * @param key
+ */
+ virtual void __unset(const Php::Value &key);
+
private:
/**
diff --git a/include/classbase.h b/include/classbase.h
index 1be2538..492bb15 100644
--- a/include/classbase.h
+++ b/include/classbase.h
@@ -298,6 +298,40 @@ private:
static struct _zend_object_iterator *getIterator(struct _zend_class_entry *entry, struct _zval_struct *object, int by_ref);
/**
+ * 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???
+ * @return zval
+ */
+ static struct _zval_struct *readProperty(struct _zval_struct *object, struct _zval_struct *name, int type);
+
+ /**
+ * 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
+ * @return zval
+ */
+ static void writeProperty(struct _zval_struct *object, struct _zval_struct *name, struct _zval_struct *value);
+
+ /**
+ * 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
+ * @return bool
+ */
+ static int hasProperty(struct _zval_struct *object, struct _zval_struct *name, int has_set_exists);
+
+ /**
+ * 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
+ */
+ static void unsetProperty(struct _zval_struct *object, struct _zval_struct *member);
+
+ /**
* Name of the class
* @var string
*/
diff --git a/src/base.cpp b/src/base.cpp
index 305821f..b561899 100644
--- a/src/base.cpp
+++ b/src/base.cpp
@@ -78,6 +78,72 @@ MixedObject *Base::store(zend_class_entry *entry)
}
/**
+ * 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)
+{
+ // 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)
+{
+ // 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)
+{
+ // 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)
+{
+ // throw an exception that will be caught in the ClassBase class,
+ // so that the default implementation of the function can be called
+ throw NotImplemented();
+}
+
+/**
* End namespace
*/
}
diff --git a/src/classbase.cpp b/src/classbase.cpp
index 2ea813f..8a2861b 100644
--- a/src/classbase.cpp
+++ b/src/classbase.cpp
@@ -71,12 +71,22 @@ zend_object_handlers *ClassBase::objectHandlers()
// install custom clone function
handlers.clone_obj = &ClassBase::cloneObject;
+
+ // functions for the Countable interface
handlers.count_elements = &ClassBase::countElements;
+
+ // functions for the ArrayAccess interface
handlers.write_dimension = &ClassBase::writeDimension;
handlers.read_dimension = &ClassBase::readDimension;
handlers.has_dimension = &ClassBase::hasDimension;
handlers.unset_dimension = &ClassBase::unsetDimension;
+ // functions for the magic properties handlers (__get, __set, __isset and __unset)
+ handlers.write_property = &ClassBase::writeProperty;
+ handlers.read_property = &ClassBase::readProperty;
+ handlers.has_property = &ClassBase::hasProperty;
+ handlers.unset_property = &ClassBase::unsetProperty;
+
// remember that object is now initialized
initialized = true;
@@ -326,6 +336,173 @@ void ClassBase::unsetDimension(zval *object, zval *member)
}
/**
+ * 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???
+ * @return zval
+ */
+zval *ClassBase::readProperty(zval *object, zval *name, int type)
+{
+ // 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.
+
+ // 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);
+
+ // 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();
+ }
+ catch (const NotImplemented &exception)
+ {
+ // __get() function was not overridden by the user
+ if (!std_object_handlers.read_property) return nullptr;
+
+ // call default
+ return std_object_handlers.read_property(object, name, type);
+ }
+}
+
+/**
+ * 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
+ * @return zval
+ */
+void ClassBase::writeProperty(zval *object, zval *name, zval *value)
+{
+ // 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);
+ }
+ 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
+ std_object_handlers.write_property(object, name, value);
+ }
+}
+
+/**
+ * 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
+ * @return bool
+ */
+int ClassBase::hasProperty(zval *object, zval *name, int has_set_exists)
+{
+ // 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 = cpp_object(object);
+
+ // call the C++ object
+ if (!base->__isset(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);
+
+ // 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
+ return std_object_handlers.has_property(object, name, has_set_exists);
+ }
+}
+
+/**
+ * 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
+ */
+void ClassBase::unsetProperty(zval *object, zval *member)
+{
+ // the default implementation throws an exception, if we catch that
+ // we know for sure that the user has not overridden the __unset method
+ try
+ {
+ // call the __unset method
+ cpp_object(object)->__unset(member);
+ }
+ catch (const NotImplemented &exception)
+ {
+ // __unset was not implemented, do we have a default?
+ if (!std_object_handlers.unset_property) return;
+
+ // call the default
+ std_object_handlers.unset_property(object, member);
+ }
+}
+
+/**
* 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
diff --git a/src/includes.h b/src/includes.h
index 24b4837..021c943 100644
--- a/src/includes.h
+++ b/src/includes.h
@@ -88,6 +88,7 @@
#include "floatmember.h"
#include "arithmetic.h"
#include "origexception.h"
+#include "notimplemented.h"
#ifndef ZVAL_COPY_VALUE
#define ZVAL_COPY_VALUE(z, v) \
diff --git a/src/notimplemented.h b/src/notimplemented.h
new file mode 100644
index 0000000..d876178
--- /dev/null
+++ b/src/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() {}
+
+};
+
+/**
+ * End namespace
+ */
+}
+