summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/call.h15
-rw-r--r--include/object.h20
-rw-r--r--include/value.h45
-rw-r--r--phpcpp.h2
-rw-r--r--zend/classimpl.cpp65
-rw-r--r--zend/classimpl.h6
-rw-r--r--zend/eval.cpp68
-rw-r--r--zend/exists.cpp77
-rw-r--r--zend/value.cpp132
9 files changed, 375 insertions, 55 deletions
diff --git a/include/call.h b/include/call.h
index 16de1fe..1a0c59f 100644
--- a/include/call.h
+++ b/include/call.h
@@ -13,6 +13,21 @@
namespace Php {
/**
+ * List of functions that are available for use in PHP
+ */
+extern bool class_exists(const char *classname, size_t size, bool autoload = true);
+inline bool class_exists(const char *classname, bool autoload = true) { return class_exists(classname, strlen(classname), autoload); }
+inline bool class_exists(const std::string &classname, bool autoload = true) { return class_exists(classname.c_str(), classname.size(), autoload); }
+extern Value eval(const std::string &phpCode);
+inline bool is_a(const Value &obj, const char *classname, size_t size, bool allow_string = false) { return obj.instanceOf(classname, size, allow_string); }
+inline bool is_a(const Value &obj, const char *classname, bool allow_string = false) { return is_a(obj, classname, strlen(classname), allow_string); }
+inline bool is_a(const Value &obj, const std::string &classname, bool allow_string = false) { return is_a(obj, classname.c_str(), classname.size(), allow_string); }
+inline bool is_subclass_of(const Value &obj, const char *classname, size_t size, bool allow_string = true) { return obj.derivedFrom(classname, size, allow_string); }
+inline bool is_subclass_of(const Value &obj, const char *classname, bool allow_string = true) { return is_subclass_of(obj, classname, strlen(classname), allow_string); }
+inline bool is_subclass_of(const Value &obj, const std::string &classname, bool allow_string = true) { return is_subclass_of(obj, classname.c_str(), classname.size(), allow_string); }
+
+
+/**
* Call a function in PHP
* @param name Name of the function to call
* @param params Variable number of parameters
diff --git a/include/object.h b/include/object.h
index 2e6628a..6350259 100644
--- a/include/object.h
+++ b/include/object.h
@@ -41,11 +41,21 @@ public:
*/
Object(const Value &value) : Value()
{
- // string types are instantiated
- if (value.isString()) instantiate(value);
-
- // otherwise copy the other object
- else operator=(value);
+ // when a string is passed in, we are going to make a new instance of the
+ // passed in string
+ if (value.isString())
+ {
+ // instantiate the object
+ instantiate(value);
+
+ // and call the __construct method
+ call("__construct");
+ }
+ else
+ {
+ // this simply copies the other object
+ operator=(value);
+ }
}
/**
diff --git a/include/value.h b/include/value.h
index f72304c..2fe940a 100644
--- a/include/value.h
+++ b/include/value.h
@@ -100,7 +100,7 @@ public:
* @param value
*/
template <typename T>
- Value(const std::map<std::string,T> &value)
+ Value(const std::map<std::string,T> &value) : Value(Type::Array)
{
// set all elements
for (auto &iter : value) setRaw(iter.first.c_str(), iter.first.size(), iter.second);
@@ -452,8 +452,6 @@ public:
template <typename T>
std::vector<T> vectorValue() const
{
-
-
// only works for arrays, other types return an empty vector
if (!isArray()) return std::vector<T>();
@@ -502,6 +500,9 @@ public:
// result variable
std::map<std::string,T> result;
+
+ // loop through the original map, and copy everything to the result
+ for (auto &iter : map) result[iter.first] = iter.second;
// done
return result;
@@ -964,6 +965,37 @@ public:
// try casting it
return dynamic_cast<T*>(base);
}
+
+ /**
+ * Check whether this object is an instance of a certain class
+ *
+ * If you set the parameter 'allowString' to true, and the Value object
+ * holds a string, the string will be treated as class name.
+ *
+ * @param classname The class of which this should be an instance
+ * @param size Length of the classname string
+ * @param allowString Is it allowed for 'this' to be a string
+ * @return bool
+ */
+ bool instanceOf(const char *classname, size_t size, bool allowString = false) const;
+ bool instanceOf(const char *classname, bool allowString = false) const { return instanceOf(classname, strlen(classname), allowString); }
+ bool instanceOf(const std::string &classname, bool allowString = false) const { return instanceOf(classname.c_str(), classname.size(), allowString); }
+
+ /**
+ * Check whether this object is derived from a certain class.
+ *
+ * If you set the parameter 'allowString' to true, and the Value object
+ * holds a string, the string will be treated as class name.
+ *
+ * @param classname The class of which this should be an instance
+ * @param size Length of the classname string
+ * @param allowString Is it allowed for 'this' to be a string
+ * @return bool
+ */
+ bool derivedFrom(const char *classname, size_t size, bool allowString = false) const;
+ bool derivedFrom(const char *classname, bool allowString = false) const { return derivedFrom(classname, strlen(classname), allowString); }
+ bool derivedFrom(const std::string &classname, bool allowString = false) const { return derivedFrom(classname.c_str(), classname.size(), allowString); }
+
private:
/**
@@ -1049,6 +1081,13 @@ protected:
iterator createIterator(bool begin) const;
/**
+ * Retrieve the class entry
+ * @param allowString Allow the 'this' object to be a string
+ * @return zend_class_entry
+ */
+ struct _zend_class_entry *classEntry(bool allowString = true) const;
+
+ /**
* The Globals and Member classes can access the zval directly
*/
friend class Globals;
diff --git a/phpcpp.h b/phpcpp.h
index 51ded73..23efe69 100644
--- a/phpcpp.h
+++ b/phpcpp.h
@@ -2,7 +2,7 @@
* phpcpp.h
*
* Library to build PHP extensions with CPP
- *
+ *
* @copyright 2013 CopernicA BV
* @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
*/
diff --git a/zend/classimpl.cpp b/zend/classimpl.cpp
index b2acc24..9c28a0a 100644
--- a/zend/classimpl.cpp
+++ b/zend/classimpl.cpp
@@ -20,11 +20,6 @@ ClassImpl::~ClassImpl()
{
// destruct the entries
if (_entries) delete[] _entries;
-
- // php 5.3 deallocates the doc_comment by iself
-#if PHP_VERSION_ID >= 50400
- if (_comment) free(_comment);
-#endif
}
/**
@@ -41,18 +36,21 @@ static ClassImpl *self(zend_class_entry *entry)
// we need the base class (in user space the class may have been overridden,
// but we are not interested in these user space classes)
while (entry->parent) entry = entry->parent;
-
+
#if PHP_VERSION_ID >= 50400
// retrieve the comment (it has a pointer hidden in it to the ClassBase object)
const char *comment = entry->info.user.doc_comment;
-#else
- // retrieve the comment php5.3 style (it has a pointer hidden in it to the ClassBase object)
- const char *comment = entry->doc_comment;
-#endif
-
+
// the first byte of the comment is an empty string (null character), but
// the next bytes contain a pointer to the ClassBase class
return *((ClassImpl **)(comment + 1));
+#else
+ // on php 5.3 we store the pointer to impl after the name in the entry
+ ClassImpl** impl = (ClassImpl**)(entry->name + 1 + entry->name_length);
+
+ // return the actual implementation
+ return *impl;
+#endif
}
/**
@@ -1411,34 +1409,37 @@ void ClassImpl::initialize(ClassBase *base, const std::string &prefix TSRMLS_DC)
// otherwise report an error
else std::cerr << "Derived class " << name() << " is initialized before base class " << interface->name() << ": interface is ignored" << std::endl;
}
-
+
+ // this pointer has to be copied to temporary pointer, as &this causes compiler error
+ ClassImpl *impl = this;
+
+#if PHP_VERSION_ID >= 50400
+
// allocate doc comment to contain an empty string + a hidden pointer
- if (!_comment)
- {
- // allocate now
- _comment = (char *)malloc(1 + sizeof(ClassBase *));
-
- // empty string on first position
- _comment[0] = '\0';
-
- // this pointer has to be copied to temporary pointer, as &this causes compiler error
- ClassImpl *impl = this;
-
- // copy the 'this' pointer to the doc-comment
- memcpy(_comment+1, &impl, sizeof(ClassImpl *));
- }
-
- // store pointer to the class in the unused doc_comment member
-#if PHP_VERSION_ID >= 50400
+ char *_comment = (char *)malloc(1 + sizeof(ClassImpl *));
+
+ // empty string on first position
+ _comment[0] = '\0';
+
+ // copy the 'this' pointer to the doc-comment
+ memcpy(_comment+1, &impl, sizeof(ClassImpl *));
+
+ // set our comment in the actual class entry
_entry->info.user.doc_comment = _comment;
+
#else
- // and store the wrapper inside the comment
- _entry->doc_comment = _comment;
+
+ // Reallocate some extra space in the name in the zend_class_entry so we can fit a pointer behind it
+ _entry->name = (char *) realloc(_entry->name, _entry->name_length + 1 + sizeof(ClassImpl *));
+
+ // Copy the pointer after it
+ memcpy(_entry->name + _entry->name_length + 1, &impl, sizeof(ClassImpl *));
+
#endif
// set access types flags for class
_entry->ce_flags = (int)_type;
-
+
// declare all member variables
for (auto &member : _members) member->initialize(_entry TSRMLS_CC);
}
diff --git a/zend/classimpl.h b/zend/classimpl.h
index bd631b8..26cf030 100644
--- a/zend/classimpl.h
+++ b/zend/classimpl.h
@@ -32,12 +32,6 @@ private:
std::string _name;
/**
- * The comment for reflexion, with a stored pointer to ourselves
- * @var char*
- */
- char *_comment = nullptr;
-
- /**
* The class type (this can be values like Php::Abstract and Php::Final)
* @var ClassType
*/
diff --git a/zend/eval.cpp b/zend/eval.cpp
new file mode 100644
index 0000000..4632fff
--- /dev/null
+++ b/zend/eval.cpp
@@ -0,0 +1,68 @@
+/**
+ * Eval.cpp
+ *
+ * This file holds the implementation for the Php::eval() function
+ *
+ * @author andot <https://github.com/andot>
+ */
+
+/**
+ * Dependencies
+ */
+#include "includes.h"
+
+/**
+ * Open PHP namespace
+ */
+namespace Php {
+
+/**
+ * Evaluate a PHP string
+ * @param phpCode The PHP code to evaluate
+ * @return Value The result of the evaluation
+ */
+Value eval(const std::string &phpCode)
+{
+ // we need the tsrm_ls variable
+ TSRMLS_FETCH();
+
+ // the current exception
+ zval* oldException = EG(exception);
+
+ // the return zval
+ zval* retval = nullptr;
+ if (zend_eval_stringl_ex((char *)phpCode.c_str(), (int32_t)phpCode.length(), retval, (char *)"", 1 TSRMLS_CC) != SUCCESS)
+ {
+ // Do we want to throw an exception here? The original author
+ // did, but there are some reasons not to:
+ //
+ // 1. the PHP eval() function also does not throw exceptions.
+ //
+ // 2. the zend_eval_string() function already triggers a
+ // 'PHP parse error' when an error occurs, which also has
+ // to be handled. If we also throw an exception here, the
+ // user will have to write two error checks: for the error
+ // and the exception.
+ //
+ // if we _do_ want to throw an exception, we will first have to
+ // prevent the original zend_error to occur, and then turn it
+ // into an exception. An exception would be nicer from a C++
+ // point of view, but because of the extra complexity, we do not
+ // this for now.
+ return nullptr;
+ }
+ else
+ {
+ // was an exception thrown inside the eval()'ed code? In that case we
+ // throw a C++ new exception to give the C++ code the chance to catch it
+ if (oldException != EG(exception) && EG(exception)) throw OrigException(EG(exception) TSRMLS_CC);
+
+ // no (additional) exception was thrown
+ return retval ? Value(retval) : nullptr;
+ }
+}
+
+/**
+ * End of namespace
+ */
+}
diff --git a/zend/exists.cpp b/zend/exists.cpp
new file mode 100644
index 0000000..1177e47
--- /dev/null
+++ b/zend/exists.cpp
@@ -0,0 +1,77 @@
+/**
+ * Exists.cpp
+ *
+ * This file holds the implementation of all *_exists() functions,
+ * like class_exists(), et cetera
+ *
+ * @author andot <https://github.com/andot>
+ */
+
+/**
+ * Dependencies
+ */
+#include "includes.h"
+
+/**
+ * On php 5.3 ZEND_ACC_TRAIT isn't defined, so we simply define it to 0
+ * so all operations with it are basically no-ops. Currently unconfirmed
+ * if this actually works correctly on php 5.3, but it at least compiles.
+ */
+#ifndef ZEND_ACC_TRAIT
+#define ZEND_ACC_TRAIT 0
+#endif
+
+/**
+ * Open the PHP namespace
+ */
+namespace Php {
+
+/**
+ * Check whether a class with a certain name exists
+ * @param classname
+ * @param len
+ * @param autoload
+ * @return bool
+ */
+bool class_exists(const char *classname, size_t len, bool autoload)
+{
+ // we need the tsrm_ls variable
+ TSRMLS_FETCH();
+
+ // we're going to load a class-entry
+ zend_class_entry **ce;
+
+ // should we autoload the class?
+ if (autoload)
+ {
+ // no auto-load
+ if (SUCCESS != zend_lookup_class(classname, len, &ce TSRMLS_CC)) return false;
+
+ // the found "class" could also be an interface or trait, which we do no want
+ return ((*ce)->ce_flags & (ZEND_ACC_INTERFACE | (ZEND_ACC_TRAIT - ZEND_ACC_EXPLICIT_ABSTRACT_CLASS))) == 0;
+ }
+ else
+ {
+ // starting slashes can be ignored
+ if (len > 0 && classname[0] == '\\') { classname++; len--; }
+
+ // all classes are in lowercase in the hash, so we make
+ // a temporary buffer for storing the lowercase class name
+ // (is this smart? memory allocation is expensive!)
+ std::unique_ptr<char[]> lc_name(new char[len + 1]);
+
+ // copy the name to lowercase, but ignore the starting slash (if there is one)
+ zend_str_tolower_copy(lc_name.get(), classname, len);
+
+ // see if there is a class with this name
+ if (SUCCESS != zend_hash_find(EG(class_table), lc_name.get(), len + 1, (void **) &ce)) return false;
+
+ // the found "class" could also be an interface or trait, which we do no want
+ return !(((*ce)->ce_flags & (ZEND_ACC_INTERFACE | ZEND_ACC_TRAIT)) > ZEND_ACC_EXPLICIT_ABSTRACT_CLASS);
+ }
+}
+
+/**
+ * End of namespace
+ */
+}
diff --git a/zend/value.cpp b/zend/value.cpp
index 352e90d..dfea847 100644
--- a/zend/value.cpp
+++ b/zend/value.cpp
@@ -124,10 +124,10 @@ Value::Value(const std::string &value)
*/
Value::Value(const char *value, int size)
{
- // allocate the zval
+ // allocate the zval
MAKE_STD_ZVAL(_val);
-
- // do we have a valid value?
+
+ // is there a value?
if (value)
{
// create a string zval
@@ -1409,6 +1409,116 @@ bool Value::isCallable() const
}
/**
+ * Retrieve the class entry
+ * @param allowString
+ * @return zend_class_entry
+ */
+zend_class_entry *Value::classEntry(bool allowString) const
+{
+ // we need the tsrm_ls variable
+ TSRMLS_FETCH();
+
+ // is this an object
+ if (isObject())
+ {
+ // should have a class entry
+ if (!HAS_CLASS_ENTRY(*_val)) return nullptr;
+
+ // class entry can be easily found
+ return Z_OBJCE_P(_val);
+ }
+ else
+ {
+ // the value is not an object, is this allowed?
+ if (!allowString || !isString()) return nullptr;
+
+ // temporary variable
+ zend_class_entry **ce;
+
+ // find the class entry
+ if (zend_lookup_class(Z_STRVAL_P(_val), Z_STRLEN_P(_val), &ce TSRMLS_CC) == FAILURE) return nullptr;
+
+ // found the entry
+ return *ce;
+ }
+}
+
+/**
+ * Check whether this object is an instance of a certain class
+ *
+ * If you set the parameter 'allowString' to true, and the Value object
+ * holds a string, the string will be treated as class name.
+ *
+ * @param classname The class of which this should be an instance
+ * @param size Length of the classname string
+ * @param allowString Is it allowed for 'this' to be a string
+ * @return bool
+ */
+bool Value::instanceOf(const char *classname, size_t size, bool allowString) const
+{
+ // we need the tsrm_ls variable
+ TSRMLS_FETCH();
+
+ // the class-entry of 'this'
+ zend_class_entry *this_ce = classEntry(allowString);
+ if (!this_ce) return false;
+
+ // class entry of the parameter
+ zend_class_entry **ce;
+
+ // now we can look up the actual class
+ // the signature of zend_lookup_class_ex is slightly different since 5.4
+ // TODO The signature of this changed once again as of 5.6!
+#if PHP_VERSION_ID >= 50400
+ if (zend_lookup_class_ex(classname, size, NULL, 0, &ce TSRMLS_CC) == FAILURE) return false;
+#else
+ if (zend_lookup_class_ex(classname, size, 0, &ce TSRMLS_CC) == FAILURE) return false;
+#endif
+
+ // check if this is a subclass
+ return instanceof_function(this_ce, *ce TSRMLS_CC);
+}
+
+/**
+ * Check whether this object is derived from a certain class
+ *
+ * If you set the parameter 'allowString' to true, and the Value object
+ * holds a string, the string will be treated as class name.
+ *
+ * @param classname The class of which this should be an instance
+ * @param size Length of the classname string
+ * @param allowString Is it allowed for 'this' to be a string
+ * @return bool
+ */
+bool Value::derivedFrom(const char *classname, size_t size, bool allowString) const
+{
+ // we need the tsrm_ls variable
+ TSRMLS_FETCH();
+
+ // the class-entry of 'this'
+ zend_class_entry *this_ce = classEntry(allowString);
+ if (!this_ce) return false;
+
+ // class entry of the parameter
+ zend_class_entry **ce;
+
+ // now we can look up the actual class
+ // the signature of zend_lookup_class_ex is slightly different since 5.4
+ // TODO The signature of this changed once again as of 5.6!
+#if PHP_VERSION_ID >= 50400
+ if (zend_lookup_class_ex(classname, size, NULL, 0, &ce TSRMLS_CC) == FAILURE) return false;
+#else
+ if (zend_lookup_class_ex(classname, size, 0, &ce TSRMLS_CC) == FAILURE) return false;
+#endif
+
+ // should not be identical, it must be a real derived object
+ if (this_ce == *ce) return false;
+
+ // check if this is a subclass
+ return instanceof_function(this_ce, *ce TSRMLS_CC);
+}
+
+/**
* Make a clone of the type
* @return Value
*/
@@ -1610,10 +1720,10 @@ std::map<std::string,Php::Value> Value::mapValue() const
{
// result variable
std::map<std::string,Php::Value> result;
-
+
// iterate over the object
- for (auto &iter : *this) result[iter.first.rawValue()] = iter.second;
-
+ for (auto &iter : *this) result[iter.first.stringValue()] = iter.second;
+
// done
return result;
}
@@ -1761,7 +1871,7 @@ Value Value::get(int index) const
*/
Value Value::get(const char *key, int size) const
{
- // must be an array
+ // must be an array or object
if (!isArray() && !isObject()) return Value();
// calculate size
@@ -1781,6 +1891,9 @@ Value Value::get(const char *key, int size) const
}
else
{
+ // key should not start with a null byte
+ if (size > 0 && key[0] == 0) return Value();
+
// we need the tsrm_ls variable
TSRMLS_FETCH();
@@ -1847,6 +1960,9 @@ void Value::set(int index, const Value &value)
*/
void Value::setRaw(const char *key, int size, const Value &value)
{
+ // does not work for empty keys
+ if (!key || (size > 0 && key[0] == 0)) return;
+
// is this an object?
if (isObject())
{
@@ -1858,7 +1974,7 @@ void Value::setRaw(const char *key, int size, const Value &value)
// retrieve the class entry
auto *entry = zend_get_class_entry(_val TSRMLS_CC);
-
+
// update the property (cast necessary for php 5.3)
zend_update_property(entry, _val, (char *)key, size, value._val TSRMLS_CC);
}