diff options
author | Emiel Bruijntjes <emiel.bruijntjes@copernica.com> | 2014-03-10 12:26:04 +0100 |
---|---|---|
committer | Emiel Bruijntjes <emiel.bruijntjes@copernica.com> | 2014-03-10 12:26:04 +0100 |
commit | 4872cc627642044f46ba8a8726902592a1fae05f (patch) | |
tree | a416aff6d3511b17923001a664ece1deca01b4d7 | |
parent | d2e10c764d1b8860dd798eda3055fc957ff556ad (diff) |
first setup for magic methods __get(), __set(), __isset() and __unset()
-rw-r--r-- | documentation/magic-methods-and-interfaces.html | 8 | ||||
-rw-r--r-- | include/base.h | 44 | ||||
-rw-r--r-- | include/classbase.h | 34 | ||||
-rw-r--r-- | src/base.cpp | 66 | ||||
-rw-r--r-- | src/classbase.cpp | 177 | ||||
-rw-r--r-- | src/includes.h | 1 | ||||
-rw-r--r-- | src/notimplemented.h | 51 |
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<std::string,std::string> &map) : + MapIterator(Map *object, const std::map<std::string,std::string> &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 + */ +} + |