diff options
-rw-r--r-- | documentation/magic-methods.html | 24 | ||||
-rw-r--r-- | include/class.h | 22 | ||||
-rw-r--r-- | include/classbase.h | 31 | ||||
-rw-r--r-- | src/classbase.cpp | 130 | ||||
-rw-r--r-- | src/includes.h | 1 | ||||
-rw-r--r-- | src/property.h | 82 |
6 files changed, 239 insertions, 51 deletions
diff --git a/documentation/magic-methods.html b/documentation/magic-methods.html index 2b40273..9294a3e 100644 --- a/documentation/magic-methods.html +++ b/documentation/magic-methods.html @@ -12,8 +12,8 @@ </p> <h2 id="compile-time-detection">Compile time detection</h2> <p> - Although you may think that the magic methods are virtual methods that - are overridden from the Php::Base class, they are not. The methods are + Although you may have expected that the magic methods are virtual methods in + the Php::Base class that can be overridden, they are not. The methods are detected by the C++ compiler at compile time - and are very normal methods that just happen to have a certain name. </p> @@ -46,18 +46,18 @@ The only exception to this rule is the __construct() method. This method does have to be explicitly registered. There are a number of reasons for this. For a start, the __construct() method does not have a fixed signature, and - by explicitly adding it to the extension, you can also exactly specify what - parameters it accepts, and whether the __construct() method should be + by explicitly adding it to the extension, you can also specify what + parameters it accepts, and whether the __construct() method should be public, private or protected (if you want to create classes that can not be instantiated from PHP). </p> <p> The other reason why you have to explicitly register the __construct() method, - is that, unlike other magic methods, the magic __construct method <i>must</i> + is that, unlike other magic methods, the __construct method <i>must</i> be visible from PHP. Inside constructors of derived classes, it often is necessary to make a call to parent::__construct(). By registering the __construct() method in the get_module() function you make the function - visible from PHP, which makes constructs like this possible. + visible from PHP. </p> <p> We have a special article about <a href="constructors-and-destructors"> @@ -72,12 +72,12 @@ the object gets destructed (and the C++ destructor runs). </p> <p> - The __clone() and __destruct() methods are regular magic methods. You do - not have to register them to make them active. The PHP-CPP library calls - them automatically if they are available. + The __clone() and __destruct() methods are regular magic methods and you + therefore do not have to register them to make them active. The PHP-CPP + library calls them automatically if they are available. </p> <p> - In normal circumstances you probably do not need these methods very often. + In normal circumstances you probably have not need for these methods. The C++ copy constructor and the C++ destructor can be used too. The only difference is that the magic methods are called on objects that are in a fully initialized state, while the C++ copy constructor and C++ destructor @@ -85,8 +85,8 @@ <i>being destructed</i>. </p> <p> - The article about <a href="constructors-and-destructors">mentioned above</a> - as more details and examples. + The article about mentioned above about <a href="constructors-and-destructors"> + constructors and destructors</a> has more details and examples. </p> <h2 id="pseudo-properties">Pseudo properties</h2> <p> diff --git a/include/class.h b/include/class.h index 008ff8a..d867699 100644 --- a/include/class.h +++ b/include/class.h @@ -156,7 +156,27 @@ public: void property(const char *name, const std::string &value, int flags = Public) { ClassBase::property(name, value, flags); } void property(const char *name, bool value, int flags = Public) { ClassBase::property(name, value, flags); } void property(const char *name, double value, int flags = Public) { ClassBase::property(name, value, flags); } - + + /** + * Properties as methods + * + * This is a smarter way for adding properties to a class. You can define + * a property and a method that gets called every time the property is + * set or unset. + * + * If you do not set a setter method, your property will be read-only. + * + * @param name Name of the property + * @param getter The getter method + * @param setter The setter method + */ + void property(const char *name, Value (T::*getter)() ) { ClassBase::property(name, static_cast<getter_callback>(getter), nullptr); } + void property(const char *name, Value (T::*getter)() const ) { ClassBase::property(name, static_cast<getter_callback>(getter), nullptr); } + void property(const char *name, Value (T::*getter)() , void (T::*setter)(const Value &value) ) { ClassBase::property(name, static_cast<getter_callback>(getter), static_cast<setter_callback>(setter)); } + void property(const char *name, Value (T::*getter)() const, void (T::*setter)(const Value &value) ) { ClassBase::property(name, static_cast<getter_callback>(getter), static_cast<setter_callback>(setter)); } + void property(const char *name, Value (T::*getter)() , void (T::*setter)(const Value &value) const) { ClassBase::property(name, static_cast<getter_callback>(getter), static_cast<setter_callback>(setter)); } + void property(const char *name, Value (T::*getter)() const, void (T::*setter)(const Value &value) const) { ClassBase::property(name, static_cast<getter_callback>(getter), static_cast<setter_callback>(setter)); } + private: /** * Construct a new instance of the object diff --git a/include/classbase.h b/include/classbase.h index 1979f94..3ae8568 100644 --- a/include/classbase.h +++ b/include/classbase.h @@ -52,10 +52,17 @@ typedef Value (Base::*method_callback_6)() const; typedef Value (Base::*method_callback_7)(Parameters &) const; /** + * Signatures for getters and setters + */ +typedef Value (Base::*getter_callback)(); +typedef void (Base::*setter_callback)(const Php::Value &value); + +/** * Forward declarations */ class Method; class Member; +class Property; /** * Class definition @@ -87,6 +94,7 @@ public: _type(that._type), _methods(that._methods), _members(that._members), + _properties(that._properties), _entry(nullptr) {} /** @@ -98,6 +106,7 @@ public: _type(that._type), _methods(std::move(that._methods)), _members(std::move(that._members)), + _properties(std::move(that._properties)), _entry(that._entry) { // other entry are invalid now (not that it is used..., class objects are @@ -269,6 +278,14 @@ protected: void property(const char *name, const char *value, int flags = Php::Public); void property(const char *name, double value, int flags = Php::Public); + /** + * Set property with callbacks + * @param name Name of the property + * @param getter Getter method + * @param setter Setter method + */ + void property(const char *name, const getter_callback &getter, const setter_callback &setter); + private: /** * Retrieve an array of zend_function_entry objects that hold the @@ -281,6 +298,14 @@ private: const struct _zend_function_entry *entries(); /** + * Helper method to turn a property into a zval + * @param value + * @param type + * @return Value + */ + static struct _zval_struct *toZval(Value &&value, int type); + + /** * 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 @@ -504,6 +529,12 @@ private: std::list<std::shared_ptr<Member>> _members; /** + * Map of dynamically accessible properties + * @var std::map + */ + std::map<std::string,std::shared_ptr<Property>> _properties; + + /** * Base object has access to the members * This is needed by the Base::store() method */ diff --git a/src/classbase.cpp b/src/classbase.cpp index 2fae658..1773890 100644 --- a/src/classbase.cpp +++ b/src/classbase.cpp @@ -646,22 +646,7 @@ zval *ClassBase::readDimension(zval *object, zval *offset, int type) try { // ArrayAccess is implemented, call function - Value value = arrayaccess->offsetGet(offset); - - // 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(); + return toZval(arrayaccess->offsetGet(offset), type); } catch (Exception &exception) { @@ -814,6 +799,30 @@ void ClassBase::unsetDimension(zval *object, zval *member) } /** + * Helper method to turn a property into a zval + * @param value + * @param type + * @return Value + */ +zval *ClassBase::toZval(Value &&value, int type) +{ + // 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(); +} + +/** * Function that is called when a property is read * @param object * @param name @@ -857,23 +866,23 @@ zval *ClassBase::readProperty(zval *object, zval *name, int type, const struct _ // the exception we know if the object was implemented by the user or not try { - // retrieve value - 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 - // read operation we can do this right away - if (type == 0) return value.detach(); + // convert name to a Value object + Value key(name); - // 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(); + // is it a property with a callback? + auto iter = meta->_properties.find(key); - // we're dealing with an editable zval, return a reference variable - return Value(value.detach(), true).detach(); + // was it found? + if (iter == meta->_properties.end()) + { + // retrieve value from the __get method + return toZval(meta->callGet(base, key), type); + } + else + { + // get the value + return toZval(iter->second->get(base), type); + } } catch (const NotImplemented &exception) { @@ -929,8 +938,26 @@ void ClassBase::writeProperty(zval *object, zval *name, zval *value, const struc // we know for sure that the user has not overridden the __set method try { - // call the default - meta->callSet(base, name, value); + // wrap the name + Value key(name); + + // check if the property has a callback + auto iter = meta->_properties.find(key); + + // is it set? + if (iter == meta->_properties.end()) + { + // use the __set method + meta->callSet(base, key, value); + } + else + { + // check if it could be set + if (iter->second->set(base, value)) return; + + // read-only property + zend_error(E_ERROR, "Unable to write to read-only property %s", (const char *)key); + } } catch (const NotImplemented &exception) { @@ -989,15 +1016,21 @@ int ClassBase::hasProperty(zval *object, zval *name, int has_set_exists, const s // we need the C++ class meta-information object ClassBase *meta = cpp_class(entry); + + // convert the name to a Value object + Value key(name); + + // check if this is a callback property + if (meta->_properties.find(key) != meta->_properties.end()) return true; // call the C++ object - if (!meta->callIsset(base, name)) return false; + if (!meta->callIsset(base, key)) 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 = meta->callGet(base, name); + Value value = meta->callGet(base, key); // should we check on NULL? switch (has_set_exists) { @@ -1052,9 +1085,18 @@ void ClassBase::unsetProperty(zval *object, zval *member, const struct _zend_lit // we need the C++ class meta-information object ClassBase *meta = cpp_class(entry); - - // call the __unset method - meta->callUnset(cpp_object(object), member); + + // property name + Value name(member); + + // is this a callback property? + auto iter = meta->_properties.find(name); + + // if the property does not exist, we forward to the __unset + if (iter == meta->_properties.end()) meta->callUnset(cpp_object(object), member); + + // callback properties cannot be unset + zend_error(E_ERROR, "Property %s can not be unset", (const char *)name); } catch (const NotImplemented &exception) { @@ -1641,6 +1683,18 @@ void ClassBase::property(const char *name, double value, int flags) } /** + * Set property with callbacks + * @param name Name of the property + * @param getter Getter method + * @param setter Setter method + */ +void ClassBase::property(const char *name, const getter_callback &getter, const setter_callback &setter) +{ + // add property + _properties[name] = std::make_shared<Property>(getter,setter); +} + +/** * End namespace */ } diff --git a/src/includes.h b/src/includes.h index b2cb353..721b29e 100644 --- a/src/includes.h +++ b/src/includes.h @@ -90,6 +90,7 @@ #include "arithmetic.h" #include "origexception.h" #include "notimplemented.h" +#include "property.h" #ifndef ZVAL_COPY_VALUE #define ZVAL_COPY_VALUE(z, v) \ diff --git a/src/property.h b/src/property.h new file mode 100644 index 0000000..589b58c --- /dev/null +++ b/src/property.h @@ -0,0 +1,82 @@ +/** + * Property.h + * + * Internal class for properties that are defined with a getter and setter method + * + * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com> + * @copyright 2014 Copernica BV + */ + +/** + * Set up namespace + */ +namespace Php { + +/** + * Class definition + */ +class Property +{ +private: + /** + * The getter + * @var getter_callback + */ + getter_callback _getter = nullptr; + + /** + * The setter + * @var setter_callback + */ + setter_callback _setter = nullptr; + +public: + /** + * Constructor + * @param getter + * @param setter + */ + Property(const getter_callback &getter, const setter_callback &setter) : + _getter(getter), _setter(setter) {} + + /** + * Copy constructor + * @param that + */ + Property(const Property &that) : + _getter(that._getter), _setter(that._setter) {} + + /** + * Destructor + */ + virtual ~Property(); + + /** + * Get the property + * @param base Object to call it on + * @return Value + */ + Value get(Base *base) + { + return (base->*_getter)(); + } + + /** + * Set the property + * @param base Object to call it on + * @param value New value + * @return bool + */ + bool set(Base *base, const Value &value) + { + if (!_setter) return false; + (base->*_setter)(value); + return false; + } +}; + +/** + * End of namespace + */ +} + |