summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEmiel Bruijntjes <emiel.bruijntjes@copernica.com>2014-03-09 12:51:02 +0100
committerEmiel Bruijntjes <emiel.bruijntjes@copernica.com>2014-03-09 12:51:02 +0100
commiteb4962468ace477cdd72fd1da48ae304407e40d2 (patch)
treeab437ea4c9acdaf523e4c3a1ba6ace8b88bf3025
parent116770c10d2f8219be6d90207b56853bf5a356e1 (diff)
added arrayaccess implementation
-rw-r--r--include/arrayaccess.h38
-rw-r--r--include/class.h2
-rw-r--r--include/classbase.h34
-rw-r--r--include/value.h26
-rw-r--r--src/classbase.cpp208
-rw-r--r--src/value.cpp58
6 files changed, 312 insertions, 54 deletions
diff --git a/include/arrayaccess.h b/include/arrayaccess.h
index 6ee3163..cf8c261 100644
--- a/include/arrayaccess.h
+++ b/include/arrayaccess.h
@@ -47,44 +47,6 @@ public:
* @param key
*/
virtual void offsetUnset(const Php::Value &key) = 0;
-
- /**
- * Alternative offsetExists as it is initially called
- * @param params
- * @return bool
- */
- virtual Php::Value offsetExists(Php::Parameters &params)
- {
- return offsetExists(params[0]);
- }
-
- /**
- * Alternative set member function as it is initially called
- * @param params
- */
- virtual void offsetSet(const Php::Parameters &params)
- {
- offsetSet(params[0], params[1]);
- }
-
- /**
- * Alternative retrieve member function that is initially called
- * @param params
- * @return value
- */
- virtual Php::Value offsetGet(Php::Parameters &params)
- {
- return offsetGet(params[0]);
- }
-
- /**
- * Alternative function to remove a member that is initally called
- * @param params
- */
- virtual void offsetUnset(Php::Parameters &params)
- {
- return offsetUnset(params[0]);
- }
};
/**
diff --git a/include/class.h b/include/class.h
index ab4a3e0..e9a7179 100644
--- a/include/class.h
+++ b/include/class.h
@@ -52,7 +52,7 @@ public:
// interfaces are not yet initialized by the zend engine, this only
// happens later when the all classes are registered (after the
// get_module() call)
- if (std::is_base_of<ArrayAccess, T>::value) interface(&zend_ce_arrayaccess);
+// if (std::is_base_of<ArrayAccess, T>::value) interface(&zend_ce_arrayaccess);
}
/**
diff --git a/include/classbase.h b/include/classbase.h
index f15e879..81454ba 100644
--- a/include/classbase.h
+++ b/include/classbase.h
@@ -243,6 +243,40 @@ private:
static int countElements(struct _zval_struct *object, long *count);
/**
+ * Function that is called when the object is used as an array in PHP
+ * @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 *readDimension(struct _zval_struct *object, struct _zval_struct *offset, int type);
+
+ /**
+ * Function that is called when the object is used as an array in PHP
+ * @param object The object on which it is called
+ * @param offset The name of the property
+ * @param value The new value
+ * @return zval
+ */
+ static void writeDimension(struct _zval_struct *object, struct _zval_struct *offset, struct _zval_struct *value);
+
+ /**
+ * Function that is called when the object is used as an array in PHP
+ * @param object The object on which it is called
+ * @param member The member to check
+ * @param check_empty ????
+ * @return bool
+ */
+ static int hasDimension(struct _zval_struct *object, struct _zval_struct *member, int check_empty);
+
+ /**
+ * Function that is called when the object is used as an array in PHP
+ * @param object The object on which it is called
+ * @param member The member to remove
+ */
+ static void unsetDimension(struct _zval_struct *object, struct _zval_struct *member);
+
+ /**
* Retrieve pointer to our own object handlers
* @return zend_object_handlers
*/
diff --git a/include/value.h b/include/value.h
index 72bd895..a1dd593 100644
--- a/include/value.h
+++ b/include/value.h
@@ -357,6 +357,12 @@ public:
bool isCallable() const;
/**
+ * Is the variable empty?
+ * @return bool
+ */
+ bool isEmpty() const;
+
+ /**
* Retrieve the value as number
* @return long
*/
@@ -802,6 +808,12 @@ private:
*/
Value exec(const char *name, int argc, struct _zval_struct ***params);
+ /**
+ * Refcount - the number of references to the value
+ * @return int
+ */
+ int refcount();
+
protected:
/**
* The wrapped zval
@@ -810,6 +822,19 @@ protected:
struct _zval_struct *_val;
/**
+ * Detach the zval
+ *
+ * This will unlink the zval internal structure from the Value object,
+ * so that the destructor will not reduce the number of references and/or
+ * deallocate the zval structure. This is used for functions that have to
+ * return a zval pointer, that would otherwise be deallocated the moment
+ * the function returns.
+ *
+ * @return zval
+ */
+ struct _zval_struct *detach();
+
+ /**
* Set a certain property without running any checks (you must already know
* for sure that this is an array, and that the index is not yet in use)
*
@@ -836,6 +861,7 @@ protected:
*/
friend class Globals;
friend class Member;
+ friend class ClassBase;
};
/**
diff --git a/src/classbase.cpp b/src/classbase.cpp
index c9576f9..fe08bd1 100644
--- a/src/classbase.cpp
+++ b/src/classbase.cpp
@@ -38,13 +38,17 @@ static ClassBase *cpp_class(zend_class_entry *entry)
}
/**
- * Retrieve our C++ implementation object
+ * Retrieve the CPP object
* @param val
- * @return ClassBase
+ * @return Base
*/
-static ClassBase *cpp_class(const zval *val)
+static Base *cpp_object(const zval *val)
{
- return cpp_class(zend_get_class_entry(val));
+ // retrieve the old object, which we are going to copy
+ MixedObject *object = (MixedObject *)zend_object_store_get_object(val);
+
+ // return the cpp object
+ return object->cpp;
}
/**
@@ -68,6 +72,10 @@ zend_object_handlers *ClassBase::objectHandlers()
// install custom clone function
handlers.clone_obj = &ClassBase::cloneObject;
handlers.count_elements = &ClassBase::countElements;
+ handlers.write_dimension = &ClassBase::writeDimension;
+ handlers.read_dimension = &ClassBase::readDimension;
+ handlers.has_dimension = &ClassBase::hasDimension;
+ handlers.unset_dimension = &ClassBase::unsetDimension;
// remember that object is now initialized
initialized = true;
@@ -129,20 +137,192 @@ zend_object_value ClassBase::cloneObject(zval *val TSRMLS_DC)
*/
int ClassBase::countElements(zval *object, long *count TSRMLS_DC)
{
- // we need the C++ class meta-information object
- ClassBase *meta = cpp_class(object);
-
// does it implement the countable interface?
- Countable *countable = dynamic_cast<Countable*>(meta);
-
+ Countable *countable = dynamic_cast<Countable*>(cpp_object(object));
+
// if it does not implement the Countable interface, we rely on the default implementation
- if (!countable) return std_object_handlers.count_elements(object, count);
+ if (countable)
+ {
+ // call the count function
+ *count = countable->count();
+
+ // done
+ return SUCCESS;
+ }
+ else
+ {
+ // Countable interface was not implemented, check if there is a default
+ if (!std_object_handlers.count_elements) return FAILURE;
+
+ // call default
+ return std_object_handlers.count_elements(object, count);
+ }
+}
+
+/**
+ * Function that is called when the object is used as an array in PHP
+ *
+ * This is the [] operator in PHP, and mapped to the offsetGet() method
+ * of the ArrayAccess interface
+ *
+ * @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::readDimension(zval *object, zval *offset, int type)
+{
+ // what to do with the type?
+ //
+ // the type parameter tells us whether the dimension 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.
- // call the count function
- *count = countable->count();
+
+ // does it implement the arrayaccess interface?
+ ArrayAccess *arrayaccess = dynamic_cast<ArrayAccess*>(cpp_object(object));
- // done
- return SUCCESS;
+ // if it does not implement the ArrayAccess interface, we rely on the default implementation
+ if (arrayaccess)
+ {
+ // 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();
+ }
+ else
+ {
+ // ArrayAccess not implemented, check if there is a default handler
+ if (!std_object_handlers.read_dimension) return nullptr;
+
+ // call default
+ return std_object_handlers.read_dimension(object, offset, type);
+ }
+}
+
+/**
+ * Function that is called when the object is used as an array in PHP
+ *
+ * This is the [] operator in PHP, and mapped to the offsetSet() method
+ * of the ArrayAccess interface
+ *
+ * @param object The object on which it is called
+ * @param offset The name of the property
+ * @param value The new value
+ * @return zval
+ */
+void ClassBase::writeDimension(zval *object, zval *offset, zval *value)
+{
+ // does it implement the arrayaccess interface?
+ ArrayAccess *arrayaccess = dynamic_cast<ArrayAccess*>(cpp_object(object));
+
+ // if it does not implement the ArrayAccess interface, we rely on the default implementation
+ if (arrayaccess)
+ {
+ // set the value
+ arrayaccess->offsetSet(offset, value);
+ }
+ else
+ {
+ // ArrayAccess not interface, check if there is a default handler
+ if (!std_object_handlers.write_dimension) return;
+
+ // call the default
+ std_object_handlers.write_dimension(object, offset, value);
+ }
+}
+
+/**
+ * Function that is called when the object is used as an array in PHP
+ *
+ * This is the [] operator in PHP, and mapped to the offsetExists() method
+ * of the ArrayAccess interface
+ *
+ * @param object The object on which it is called
+ * @param member The member to check
+ * @param check_empty Was this an isset() call, or an empty() call?
+ * @return bool
+ */
+int ClassBase::hasDimension(zval *object, zval *member, int check_empty)
+{
+ // does it implement the arrayaccess interface?
+ ArrayAccess *arrayaccess = dynamic_cast<ArrayAccess*>(cpp_object(object));
+
+ // if it does not implement the ArrayAccess interface, we rely on the default implementation
+ if (arrayaccess)
+ {
+ // check if the member exists
+ if (!arrayaccess->offsetExists(member)) return false;
+
+ // we know for certain that the offset exists, but should we check
+ // more, like whether the value is empty or not?
+ if (!check_empty) return true;
+
+ // it should not be empty
+ return !arrayaccess->offsetGet(member).isEmpty();
+ }
+ else
+ {
+ // ArrayAccess interface is not implemented, check if there is a default handler
+ if (!std_object_handlers.has_dimension) return 0;
+
+ // call default
+ return std_object_handlers.has_dimension(object, member, check_empty);
+ }
+}
+
+/**
+ * Function that is called when the object is used as an array in PHP
+ *
+ * This is the [] operator in PHP, and mapped to the offsetUnset() method
+ * of the ArrayAccess interface
+ *
+ * @param object The object on which it is called
+ * @param member The member to remove
+ */
+void ClassBase::unsetDimension(zval *object, zval *member)
+{
+ // does it implement the arrayaccess interface?
+ ArrayAccess *arrayaccess = dynamic_cast<ArrayAccess*>(cpp_object(object));
+
+ // if it does not implement the ArrayAccess interface, we rely on the default implementation
+ if (arrayaccess)
+ {
+ // remove the member
+ arrayaccess->offsetUnset(member);
+ }
+ else
+ {
+ // ArrayAccess is not implemented, is a default handler available?
+ if (!std_object_handlers.unset_dimension) return;
+
+ // call the default
+ std_object_handlers.unset_dimension(object, member);
+ }
}
/**
diff --git a/src/value.cpp b/src/value.cpp
index 7b1a931..2afb750 100644
--- a/src/value.cpp
+++ b/src/value.cpp
@@ -290,6 +290,41 @@ Value::~Value()
}
/**
+ * Detach the zval
+ *
+ * This will unlink the zval internal structure from the Value object,
+ * so that the destructor will not reduce the number of references and/or
+ * deallocate the zval structure. This is used for functions that have to
+ * return a zval pointer, that would otherwise be deallocated the moment
+ * the function returns.
+ *
+ * @return zval
+ */
+zval *Value::detach()
+{
+ // copy return value
+ zval *result = _val;
+
+ // decrement reference counter
+ Z_DELREF_P(_val);
+
+ // reset internal object
+ _val = nullptr;
+
+ // done
+ return result;
+}
+
+/**
+ * Retrieve the refcount
+ * @return int
+ */
+int Value::refcount()
+{
+ return Z_REFCOUNT_P(_val);
+}
+
+/**
* Move operator
* @param value
* @return Value
@@ -1255,7 +1290,28 @@ bool Value::isCallable() const
{
// we can not rely on the type, because strings can be callable as well
return zend_is_callable(_val, 0, NULL);
-}
+}
+
+/**
+ * Is the variable empty?
+ * @return bool
+ */
+bool Value::isEmpty() const
+{
+ switch (type()) {
+ case Type::Null: return true;
+ case Type::Numeric: return numericValue() == 0;
+ case Type::Float: return floatValue() == 0.0;
+ case Type::Bool: return boolValue() == false;
+ case Type::Array: return size() == 0;
+ case Type::Object: return false;
+ case Type::String: return size() == 0;
+ case Type::Callable: return false;
+ default: return false;
+ }
+
+ // for the time being we consider resources and consts to be not-empty
+}
/**
* Make a clone of the type