summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--documentation/magic-methods.html24
-rw-r--r--include/class.h22
-rw-r--r--include/classbase.h31
-rw-r--r--src/classbase.cpp130
-rw-r--r--src/includes.h1
-rw-r--r--src/property.h82
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
+ */
+}
+