summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEmiel Bruijntjes <emiel.bruijntjes@copernica.com>2014-08-26 13:08:39 +0200
committerEmiel Bruijntjes <emiel.bruijntjes@copernica.com>2014-08-26 13:08:39 +0200
commitf526c4c7a7ada1ab534ca0976c7b66718b9a5660 (patch)
tree68b4db309ee00d3b7ebc1743b4825057b3db7f28
parenta286c34777e48d59410e7511f52f4a6f08000ba3 (diff)
parent97bc6757346d394a4b7d5898983be298e0b0ea98 (diff)
fixed conflict
-rw-r--r--documentation/extension-callbacks.html36
-rw-r--r--documentation/ten-reasons-for-using-php-cpp.html2
-rw-r--r--documentation/variables.html6
-rw-r--r--include/array.h10
-rw-r--r--include/call.h15
-rw-r--r--include/class.h30
-rw-r--r--include/classbase.h4
-rw-r--r--include/exception.h10
-rw-r--r--include/extension.h16
-rw-r--r--include/fatalerror.h66
-rw-r--r--include/namespace.h37
-rw-r--r--include/object.h28
-rw-r--r--include/value.h58
-rw-r--r--phpcpp.h3
-rw-r--r--tests/cpp/h/variables.h1
-rw-r--r--tests/cpp/include/variables/028-029-compare.h194
-rw-r--r--tests/cpp/main.cpp3
-rw-r--r--tests/php/phpt/variables/028-compare1.phpt53
-rw-r--r--tests/php/phpt/variables/029-compare2.phpt74
-rw-r--r--zend/classimpl.cpp164
-rw-r--r--zend/classimpl.h25
-rw-r--r--zend/eval.cpp68
-rw-r--r--zend/exists.cpp77
-rw-r--r--zend/extension.cpp12
-rw-r--r--zend/extensionimpl.cpp12
-rw-r--r--zend/extensionimpl.h23
-rw-r--r--zend/fatalerror.cpp31
-rw-r--r--zend/hashiterator.h16
-rw-r--r--zend/includes.h1
-rw-r--r--zend/namespace.cpp12
-rw-r--r--zend/object.cpp23
-rw-r--r--zend/objectimpl.h2
-rw-r--r--zend/origexception.h6
-rw-r--r--zend/streambuf.cpp3
-rw-r--r--zend/value.cpp185
35 files changed, 1164 insertions, 142 deletions
diff --git a/documentation/extension-callbacks.html b/documentation/extension-callbacks.html
index ae266de..b8c2686 100644
--- a/documentation/extension-callbacks.html
+++ b/documentation/extension-callbacks.html
@@ -130,6 +130,42 @@ extern "C" {
right before PHP shuts down. If there is anything to clean up, you can install
such a callback and run the cleanup code from it.
</p>
+<h2 id="apache-prefork-module">Forked engines (like Apache)</h2>
+<p>
+ If you run PHP on a pre-fork web server (like Apache), your extension is
+ loaded and initialized <i>before</i> the various worker threads are
+ forked off. The consequence of this is that the get_module() function
+ and your optional onStartup() callback function are called by the parent
+ process, and all other callbacks and the actual page handling by the
+ child processes. A call to getpid() (or other functions to retrieve
+ information about the current process) will therefore return something
+ else inside the onStartup callback, as it does in any of the other
+ extension functions.
+</p>
+<p>
+ You may have to be careful because of this. It is better not to
+ do things in the startup functions that may not work when the process
+ is forked into different child processes (like opening file descriptors).
+ Something else to keep in mind is that the startup function is only called
+ by the parent processes when Apache starts up (or reloaded, see later),
+ while the shutdown function is called by <i>every</i> child process
+ that gracefully exits. The onShutdown is thus not only called when the
+ Apache process is stopped, but also when one of the worker processes
+ exits because it no longer is needed, or because it is replaced by
+ a fresh and new worker.
+</p>
+<p>
+ The get_module() function - as you've seen countles times before - is
+ called when your extension is initially loaded. But not only then. When
+ apache is reloaded (for example by giving the command line instruction
+ "apachectl reload"), your get_module() gets called for a second time,
+ and the callback that you registered with Extension::onStartup() is
+ called again too.
+ This is normally not a problem, because the static extension object is
+ in a locked state after the first get_module() call, and the functions
+ and classes that you try to add to the extension object in the second
+ invokation of get_module() are simply ignored.
+</p>
<h2 id="multi-threading">Watch out for multi-threading</h2>
<p>
If your extension runs on a multi-threaded PHP installation, you need to take
diff --git a/documentation/ten-reasons-for-using-php-cpp.html b/documentation/ten-reasons-for-using-php-cpp.html
index 484f3fb..7d830d4 100644
--- a/documentation/ten-reasons-for-using-php-cpp.html
+++ b/documentation/ten-reasons-for-using-php-cpp.html
@@ -71,7 +71,7 @@
C++ is a proven language with a more than 40 year long history. C++ has an
official open standard and is controlled by a C++ standards committee with
members that have proven track records. Compilers have been developed by companies
- like Microsoft, IBM, Intel, Apple and there there are several open source
+ like Microsoft, IBM, Intel, Apple and there are several open source
compilers available (GNU, CLANG), so you can always switch to a faster or
more stable alternative. The compiler vendors are constantly motivated to
be better than their competitors and bring out new versions of their compilers
diff --git a/documentation/variables.html b/documentation/variables.html
index 98a6d49..dfc5b18 100644
--- a/documentation/variables.html
+++ b/documentation/variables.html
@@ -223,10 +223,10 @@ Php::Value readExample2(int fd)
// result variable
Php::Value result;
- // resize the buffer to 4096 bytes, the resize() method resizes
+ // resize the buffer to 4096 bytes, the reserve() method resizes
// the internal buffer to the appropriate size, and returns a pointer
// to the buffer
- char *buffer = result.resize(4096);
+ char *buffer = result.reserve(4096);
// read in the bytes directly into the just allocated buffer
ssize_t bytes = read(fd, buffer, 4096);
@@ -235,7 +235,7 @@ Php::Value readExample2(int fd)
// resize the buffer to the actual number of bytes in it (this
// is necessary, otherwise the PHP strlen() returns 4096 even
// when less bytes were available
- result.resize(bytes);
+ result.reserve(bytes);
// return the result
return result;
diff --git a/include/array.h b/include/array.h
index 0b6ceb9..4d6b6b1 100644
--- a/include/array.h
+++ b/include/array.h
@@ -31,7 +31,7 @@ public:
Array(const Value &value) : Value(value)
{
// type must be valid
- if (value.type() != Type::Array) throw Php::Exception("Assigning a non-array to an array variable");
+ if (value.type() != Type::Array) throw FatalError("Assigning a non-array to an array variable");
}
/**
@@ -41,7 +41,7 @@ public:
Array(Value &&value) : Value(std::move(value))
{
// type must be valid
- if (value.type() != Type::Array) throw Php::Exception("Moving a non-array to an array variable");
+ if (value.type() != Type::Array) throw FatalError("Moving a non-array to an array variable");
}
/**
@@ -76,7 +76,7 @@ public:
virtual Value &setType(Type type) override
{
// throw exception if things are going wrong
- if (type != Type::Array) throw Php::Exception("Changing type of a fixed array variable");
+ if (type != Type::Array) throw FatalError("Changing type of a fixed array variable");
// call base
return Value::setType(Type::Array);
@@ -93,7 +93,7 @@ public:
if (this == &value) return *this;
// type must be valid
- if (value.type() != Type::Array) throw Php::Exception("Assigning a non-array to a fixed array variable");
+ if (value.type() != Type::Array) throw FatalError("Assigning a non-array to a fixed array variable");
// call base
Value::operator=(value);
@@ -113,7 +113,7 @@ public:
if (this == &value) return *this;
// type must be valid
- if (value.type() != Type::Array) throw Php::Exception("Moving a non-array to a fixed array variable");
+ if (value.type() != Type::Array) throw FatalError("Moving a non-array to a fixed array variable");
// call base
Value::operator=(std::move(value));
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/class.h b/include/class.h
index e0316df..cffc7bd 100644
--- a/include/class.h
+++ b/include/class.h
@@ -202,13 +202,39 @@ public:
private:
/**
+ * Method to create the object if it is default constructable
+ * @param orig
+ * @return Base*
+ */
+ template <typename X = T>
+ typename std::enable_if<std::is_default_constructible<X>::value, Base*>::type
+ static maybeConstruct()
+ {
+ // create a new instance
+ return new X();
+ }
+
+ /**
+ * Method to create the object if it is not default constructable
+ * @param orig
+ * @return Base*
+ */
+ template <typename X = T>
+ typename std::enable_if<!std::is_default_constructible<X>::value, Base*>::type
+ static maybeConstruct()
+ {
+ // create empty instance
+ return nullptr;
+ }
+
+ /**
* Construct a new instance of the object
* @return Base
*/
virtual Base* construct() const override
{
// construct an instance
- return new T();
+ return maybeConstruct<T>();
}
/**
@@ -225,7 +251,7 @@ private:
}
/**
- * Method to clone the object if it is copy constructable
+ * Method to clone the object if it is not copy constructable
* @param orig
* @return Base*
*/
diff --git a/include/classbase.h b/include/classbase.h
index a736c4e..9f0bac3 100644
--- a/include/classbase.h
+++ b/include/classbase.h
@@ -2,9 +2,9 @@
* ClassBase.h
*
* This is the base class of the "Class" class. This is an internal class that
- * is used by the PHP-CPP library. But because the constructor is protected,
+ * is used by the PHP-CPP library. Because the constructor is protected,
* you can not create any instances if this class yourself (and you are not
- * supposed to do that either.
+ * supposed to do that either).
*
* Further more, because this base class is a 'private' base of Class, all
* features of it are normally also inaccessible.
diff --git a/include/exception.h b/include/exception.h
index 671df9e..aff0afc 100644
--- a/include/exception.h
+++ b/include/exception.h
@@ -75,6 +75,16 @@ public:
// yes, it is native
return true;
}
+
+ /**
+ * Report this error as a fatal error
+ * @return bool
+ */
+ virtual bool report() const
+ {
+ // this is not done here
+ return false;
+ }
};
/**
diff --git a/include/extension.h b/include/extension.h
index f03c8fb..562fceb 100644
--- a/include/extension.h
+++ b/include/extension.h
@@ -111,6 +111,9 @@ public:
*/
Extension &add(Ini &&ini)
{
+ // skip when locked
+ if (locked()) return *this;
+
// and add it to the list of classes
_ini_entries.emplace_back(new Ini(std::move(ini)));
@@ -125,6 +128,9 @@ public:
*/
Extension &add(const Ini &ini)
{
+ // skip when locked
+ if (locked()) return *this;
+
// and add it to the list of classes
_ini_entries.emplace_back(new Ini(ini));
@@ -180,6 +186,16 @@ public:
{
return module();
}
+
+protected:
+ /**
+ * Is the extension object in a locked state? This happens after the
+ * get_module() function was called for the first time ("apache reload"
+ * forces a new call to get_module())
+ *
+ * @return bool
+ */
+ virtual bool locked() const override;
private:
/**
diff --git a/include/fatalerror.h b/include/fatalerror.h
new file mode 100644
index 0000000..b178f25
--- /dev/null
+++ b/include/fatalerror.h
@@ -0,0 +1,66 @@
+/**
+ * FatalError.h
+ *
+ *
+ * Normally, fatal errors are reported with a call to zend_error().
+ *
+ * However, this will trigger a longjmp(), which will cause objects
+ * constructed in the extension not to be destructed. We use therefore
+ * this FatalError class, which is a normally exception that _does_
+ * cause objects to be destructed.
+ *
+ * When it is caught, right before control is handed back to the Zend
+ * engine, it will turn the exception into a zend_error() call and
+ * thus a longjmp.
+ *
+ * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
+ * @copyright 2014 Copernica BV
+ */
+
+/**
+ * Set up namespace
+ */
+namespace Php {
+
+/**
+ * Class definition
+ */
+class FatalError : public Exception
+{
+public:
+ /**
+ * Constructor
+ * @param message
+ */
+ FatalError(const std::string &message) : Exception(message) {}
+
+ /**
+ * Destructor
+ */
+ virtual ~FatalError() throw()
+ {
+ }
+
+ /**
+ * Is this a native exception (one that was thrown from C++ code)
+ * @return bool
+ */
+ virtual bool native() const
+ {
+ // although it is native, we return 0 because it should not persist
+ // as exception, but it should live on as zend_error() in stead
+ return false;
+ }
+
+ /**
+ * Report this error as a fatal error
+ * @return bool
+ */
+ virtual bool report() const override;
+};
+
+/**
+ * End of namespace
+ */
+}
+
diff --git a/include/namespace.h b/include/namespace.h
index 828c12c..e713850 100644
--- a/include/namespace.h
+++ b/include/namespace.h
@@ -48,6 +48,25 @@ protected:
*/
std::list<std::shared_ptr<Namespace>> _namespaces;
+ /**
+ * Is the object locked?
+ *
+ * After the object is locked, no more elements can be added to it.
+ * This happens after the call to get_module - it the no longer makes
+ * sense to add more objects. When 'apache reload' is executed, the
+ * get_module() function is called for a second (or third, or fourth)
+ * time, but the classes, functions and namespaces will then not be
+ * filled.
+ *
+ * @var bool
+ */
+ virtual bool locked() const
+ {
+ // by default, namespaces are not locked (only derived extension
+ // objects can end up in a locked state
+ return false;
+ }
+
public:
/**
@@ -81,6 +100,9 @@ public:
template<typename T>
Namespace &add(Class<T> &&type)
{
+ // skip when locked
+ if (locked()) return *this;
+
// make a copy of the object, and add it to the list of classes
_classes.push_back(std::unique_ptr<ClassBase>(new Class<T>(std::move(type))));
@@ -96,6 +118,9 @@ public:
template<typename T>
Namespace &add(const Class<T> &type)
{
+ // skip when locked
+ if (locked()) return *this;
+
// and add it to the list of classes
_classes.push_back(std::unique_ptr<ClassBase>(new Class<T>(type)));
@@ -110,6 +135,9 @@ public:
*/
Namespace &add(Interface &&interface)
{
+ // skip when locked
+ if (locked()) return *this;
+
// make a copy and add it to the list of classes
_classes.push_back(std::unique_ptr<ClassBase>(new Interface(std::move(interface))));
@@ -124,6 +152,9 @@ public:
*/
Namespace &add(const Interface &interface)
{
+ // skip when locked
+ if (locked()) return *this;
+
// make a copy and add it to the list of classes
_classes.push_back(std::unique_ptr<ClassBase>(new Interface(interface)));
@@ -138,6 +169,9 @@ public:
*/
Namespace &add(Namespace &&ns)
{
+ // skip when locked
+ if (locked()) return *this;
+
// add it to the list of namespaces
_namespaces.push_back(std::unique_ptr<Namespace>(new Namespace(std::move(ns))));
@@ -152,6 +186,9 @@ public:
*/
Namespace &add(const Namespace &ns)
{
+ // skip when locked
+ if (locked()) return *this;
+
// make a copy and add it to the list of namespaces
_namespaces.push_back(std::unique_ptr<Namespace>(new Namespace(ns)));
diff --git a/include/object.h b/include/object.h
index 97c9482..6350259 100644
--- a/include/object.h
+++ b/include/object.h
@@ -31,7 +31,7 @@ public:
Object(Value &&value) : Value(std::move(value))
{
// throw exception in case of problems
- if (value.type() != Type::Object) throw Php::Exception("Constructing an object variable by moving a non object");
+ if (value.type() != Type::Object) throw FatalError("Constructing an object variable by moving a non object");
}
/**
@@ -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);
+ }
}
/**
@@ -123,7 +133,7 @@ public:
virtual Value &setType(Type type) override
{
// throw exception if things are going wrong
- if (type != Type::Object) throw Php::Exception("Changing type of a fixed object variable");
+ if (type != Type::Object) throw FatalError("Changing type of a fixed object variable");
// call base
return Value::setType(type);
@@ -140,7 +150,7 @@ public:
if (this == &value) return *this;
// type must be valid
- if (value.type() != Type::Object) throw Php::Exception("Assigning a non-object to an object variable");
+ if (value.type() != Type::Object) throw FatalError("Assigning a non-object to an object variable");
// call base
Value::operator=(value);
@@ -160,7 +170,7 @@ public:
if (this == &value) return *this;
// type must be valid
- if (value.type() != Type::Object) throw Php::Exception("Moving a non-object to an object variable");
+ if (value.type() != Type::Object) throw FatalError("Moving a non-object to an object variable");
// call base
Value::operator=(std::move(value));
diff --git a/include/value.h b/include/value.h
index c6b1af9..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);
@@ -322,6 +322,17 @@ public:
bool operator> (const char *value) const { return ::strcmp(rawValue(), value) > 0; }
/**
+ * Comparison operators for hardcoded Value
+ * @param value
+ */
+ bool operator==(const Value &value) const;
+ bool operator!=(const Value &value) const { return !operator==(value); }
+ bool operator< (const Value &value) const;
+ bool operator> (const Value &value) const { return value.operator<(*this); }
+ bool operator<=(const Value &value) const { return !operator>(value); }
+ bool operator>=(const Value &value) const { return !operator<(value); }
+
+ /**
* Comparison operators
* @param value
*/
@@ -376,7 +387,7 @@ public:
* variables - other variables return nullptr.
*
* If you are going to write to the buffer, make sure that you first call
- * the resize() method to ensure that the buffer is big enough.
+ * the reserve() method to ensure that the buffer is big enough.
*
* @return char *
*/
@@ -385,7 +396,7 @@ public:
/**
* Resize buffer space. If you want to write directly to the buffer (which
* is returned by the buffer() method), you should first reserve enough
- * space in it. This can be done with this resize() method. This will also
+ * space in it. This can be done with this reserve() method. This will also
* turn the Value object into a string (if it was not already a string).
* The writable buffer is returned.
*
@@ -489,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;
@@ -951,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:
/**
@@ -1036,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 3f94cef..baa91cc 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>
*/
@@ -29,6 +29,7 @@
#include <phpcpp/inivalue.h>
#include <phpcpp/ini.h>
#include <phpcpp/exception.h>
+#include <phpcpp/fatalerror.h>
#include <phpcpp/streams.h>
#include <phpcpp/type.h>
#include <phpcpp/hashparent.h>
diff --git a/tests/cpp/h/variables.h b/tests/cpp/h/variables.h
index 8a074cd..0b684dd 100644
--- a/tests/cpp/h/variables.h
+++ b/tests/cpp/h/variables.h
@@ -27,6 +27,7 @@
#include "../include/variables/025-post-raw1.h"
#include "../include/variables/026-post-raw2.h"
#include "../include/variables/027-env.h"
+#include "../include/variables/028-029-compare.h"
//#include "../include/variables/.h"
diff --git a/tests/cpp/include/variables/028-029-compare.h b/tests/cpp/include/variables/028-029-compare.h
new file mode 100644
index 0000000..bd95298
--- /dev/null
+++ b/tests/cpp/include/variables/028-029-compare.h
@@ -0,0 +1,194 @@
+/**
+ *
+ * Test variables
+ * phptname.phpt
+ *
+ */
+
+
+
+
+/**
+ * Set up namespace
+ */
+namespace TestVariables {
+
+ /*
+ * Test bool Value::operator==(const Value &value) const
+ */
+ void test_compare1()
+ {
+ Php::Value v1(5), v2(5.0), v3("5"), v4("5.0");
+
+ Php::out << "true:" << std::endl;
+ Php::out << (v1 == v2) << std::endl;
+ Php::out << (v1 == v3) << std::endl;
+ Php::out << (v1 == v4) << std::endl;
+ Php::out << (v2 == v1) << std::endl;
+ Php::out << (v2 == v3) << std::endl;
+ Php::out << (v2 == v4) << std::endl;
+ Php::out << (v3 == v1) << std::endl;
+ Php::out << (v3 == v2) << std::endl;
+ Php::out << (v3 == v4) << std::endl;
+ Php::out << (v4 == v1) << std::endl;
+ Php::out << (v4 == v2) << std::endl;
+ Php::out << (v4 == v3) << std::endl;
+
+ Php::Value v5(6), v6(6.0), v7("6"), v8("6.0");
+
+ Php::out << "false:" << std::endl;
+ Php::out << (v1 == v5) << std::endl;
+ Php::out << (v1 == v6) << std::endl;
+ Php::out << (v1 == v7) << std::endl;
+ Php::out << (v1 == v8) << std::endl;
+
+ Php::out << (v2 == v5) << std::endl;
+ Php::out << (v2 == v6) << std::endl;
+ Php::out << (v2 == v7) << std::endl;
+ Php::out << (v2 == v8) << std::endl;
+
+ Php::out << (v3 == v5) << std::endl;
+ Php::out << (v3 == v6) << std::endl;
+ Php::out << (v3 == v7) << std::endl;
+ Php::out << (v3 == v8) << std::endl;
+
+ Php::out << (v4 == v5) << std::endl;
+ Php::out << (v4 == v6) << std::endl;
+ Php::out << (v4 == v7) << std::endl;
+ Php::out << (v4 == v8) << std::endl;
+
+ Php::Value v9, v10, v11, v12;
+ v9[0] = 5;
+ v9[1] = 6;
+
+ v10[0] = 5;
+ v10[1] = "Hello!";
+
+ v11[0] = 5;
+ v11[1] = 6;
+
+ v12[0] = 5;
+
+ Php::out << "Compare array:" << std::endl;
+ Php::out << (v1 == v9) << std::endl;
+ Php::out << (v5 == v9) << std::endl;
+ Php::out << (v9 == v10) << std::endl;
+ Php::out << (v11 == v9) << std::endl;
+ Php::out << (v12 == v9) << std::endl;
+
+ Php::Value v13 = false, v14, v15 = 0;
+ Php::out << "Compare NULL:" << std::endl;
+ Php::out << (v1 == v13) << std::endl;
+ Php::out << (v1 == v14) << std::endl;
+ Php::out << (v1 == v15) << std::endl;
+
+ Php::out << (v13 == v14) << std::endl;
+ Php::out << (v13 == v15) << std::endl;
+ Php::out << (v14 == v15) << std::endl;
+ }
+
+ /*
+ * Test bool Value::operator< (const Value &value) const
+ */
+ void test_compare2()
+ {
+ Php::Value v1(5), v2(5.0), v3("5"), v4("5.0");
+
+ Php::out << "false:" << std::endl;
+ Php::out << (v1 < v2) << std::endl;
+ Php::out << (v1 < v3) << std::endl;
+ Php::out << (v1 < v4) << std::endl;
+ Php::out << (v2 < v1) << std::endl;
+ Php::out << (v2 < v3) << std::endl;
+ Php::out << (v2 < v4) << std::endl;
+ Php::out << (v3 < v1) << std::endl;
+ Php::out << (v3 < v2) << std::endl;
+ Php::out << (v3 < v4) << std::endl;
+ Php::out << (v4 < v1) << std::endl;
+ Php::out << (v4 < v2) << std::endl;
+ Php::out << (v4 < v3) << std::endl;
+
+ Php::Value v5(6), v6(6.0), v7("6"), v8("6.0");
+
+ Php::out << "true:" << std::endl;
+ Php::out << (v1 < v5) << std::endl;
+ Php::out << (v1 < v6) << std::endl;
+ Php::out << (v1 < v7) << std::endl;
+ Php::out << (v1 < v8) << std::endl;
+
+ Php::out << (v2 < v5) << std::endl;
+ Php::out << (v2 < v6) << std::endl;
+ Php::out << (v2 < v7) << std::endl;
+ Php::out << (v2 < v8) << std::endl;
+
+ Php::out << (v3 < v5) << std::endl;
+ Php::out << (v3 < v6) << std::endl;
+ Php::out << (v3 < v7) << std::endl;
+ Php::out << (v3 < v8) << std::endl;
+
+ Php::out << (v4 < v5) << std::endl;
+ Php::out << (v4 < v6) << std::endl;
+ Php::out << (v4 < v7) << std::endl;
+ Php::out << (v4 < v8) << std::endl;
+
+ Php::out << "false:" << std::endl;
+ Php::out << (v1 > v5) << std::endl;
+ Php::out << (v1 > v6) << std::endl;
+ Php::out << (v1 > v7) << std::endl;
+ Php::out << (v1 > v8) << std::endl;
+
+ Php::out << (v2 > v5) << std::endl;
+ Php::out << (v2 > v6) << std::endl;
+ Php::out << (v2 > v7) << std::endl;
+ Php::out << (v2 > v8) << std::endl;
+
+ Php::out << (v3 > v5) << std::endl;
+ Php::out << (v3 > v6) << std::endl;
+ Php::out << (v3 > v7) << std::endl;
+ Php::out << (v3 > v8) << std::endl;
+
+ Php::out << (v4 > v5) << std::endl;
+ Php::out << (v4 > v6) << std::endl;
+ Php::out << (v4 > v7) << std::endl;
+ Php::out << (v4 > v8) << std::endl;
+
+ Php::Value v9, v10, v11, v12;
+ v9[0] = 5;
+ v9[1] = 6;
+
+ v10[0] = 5;
+ v10[1] = "Hello!";
+
+ v11[0] = 5;
+ v11[1] = 6;
+
+ v12[0] = 5;
+
+ Php::out << "Compare array:" << std::endl;
+ Php::out << (v1 < v9) << std::endl;
+ Php::out << (v5 < v9) << std::endl;
+ Php::out << (v9 < v10) << std::endl;
+ Php::out << (v9 > v10) << std::endl;
+ Php::out << (v11 < v9) << std::endl;
+ Php::out << (v12 < v9) << std::endl;
+
+ Php::Value v13 = false, v14, v15 = 0;
+ Php::out << "Compare NULL:" << std::endl;
+ Php::out << (v1 < v13) << std::endl;
+ Php::out << (v1 < v14) << std::endl;
+ Php::out << (v1 < v15) << std::endl;
+
+ Php::out << (v1 > v13) << std::endl;
+ Php::out << (v1 > v14) << std::endl;
+ Php::out << (v1 > v15) << std::endl;
+
+ Php::out << (v13 < v14) << std::endl;
+ Php::out << (v13 < v15) << std::endl;
+ Php::out << (v14 < v15) << std::endl;
+ }
+
+/**
+ * End of namespace
+ */
+}
+
diff --git a/tests/cpp/main.cpp b/tests/cpp/main.cpp
index 37ba5ed..47cd90d 100644
--- a/tests/cpp/main.cpp
+++ b/tests/cpp/main.cpp
@@ -125,6 +125,9 @@ extern "C"
extension.add("TestVariables\\post_raw1", TestVariables::post_raw1);
extension.add("TestVariables\\post_raw2", TestVariables::post_raw2);
extension.add("TestVariables\\test_env", TestVariables::test_env);
+ extension.add("TestVariables\\test_compare1", TestVariables::test_compare1);
+ extension.add("TestVariables\\test_compare2", TestVariables::test_compare2);
+
diff --git a/tests/php/phpt/variables/028-compare1.phpt b/tests/php/phpt/variables/028-compare1.phpt
new file mode 100644
index 0000000..2885fe8
--- /dev/null
+++ b/tests/php/phpt/variables/028-compare1.phpt
@@ -0,0 +1,53 @@
+--TEST--
+Test bool Value::operator==(const Value &value) const
+--SKIPIF--
+<?php if (!extension_loaded("extension_for_tests")) print "skip"; ?>
+--FILEEOF--
+<?php
+
+TestVariables\test_compare1();
+
+--EXPECT--
+true:
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+false:
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+Compare array:
+0
+0
+0
+1
+0
+Compare NULL:
+0
+0
+0
+1
+1
+1 \ No newline at end of file
diff --git a/tests/php/phpt/variables/029-compare2.phpt b/tests/php/phpt/variables/029-compare2.phpt
new file mode 100644
index 0000000..333ede1
--- /dev/null
+++ b/tests/php/phpt/variables/029-compare2.phpt
@@ -0,0 +1,74 @@
+--TEST--
+Test bool Value::operator< (const Value &value) const
+--SKIPIF--
+<?php if (!extension_loaded("extension_for_tests")) print "skip"; ?>
+--FILEEOF--
+<?php
+
+TestVariables\test_compare2();
+
+--EXPECT--
+false:
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+true:
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+1
+false:
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+0
+Compare array:
+1
+1
+0
+1
+0
+1
+Compare NULL:
+0
+0
+0
+1
+1
+1
+0
+0
+0 \ No newline at end of file
diff --git a/zend/classimpl.cpp b/zend/classimpl.cpp
index d63956a..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
}
/**
@@ -330,52 +328,56 @@ int ClassImpl::getClosure(zval *object, zend_class_entry **entry_ptr, zend_funct
*/
zend_object_handlers *ClassImpl::objectHandlers()
{
- // keep static structure
- static zend_object_handlers handlers;
-
- // is the object already initialized?
- static bool initialized = false;
-
// already initialized?
- if (initialized) return &handlers;
+ if (_initialized) return &_handlers;
// initialize the handlers
- memcpy(&handlers, &std_object_handlers, sizeof(zend_object_handlers));
+ memcpy(&_handlers, &std_object_handlers, sizeof(zend_object_handlers));
// install custom clone function
- if (!_base->clonable()) handlers.clone_obj = nullptr;
- else handlers.clone_obj = &ClassImpl::cloneObject;
+ if (!_base->clonable()) _handlers.clone_obj = nullptr;
+ else _handlers.clone_obj = &ClassImpl::cloneObject;
// functions for the Countable interface
- handlers.count_elements = &ClassImpl::countElements;
+ _handlers.count_elements = &ClassImpl::countElements;
// functions for the ArrayAccess interface
- handlers.write_dimension = &ClassImpl::writeDimension;
- handlers.read_dimension = &ClassImpl::readDimension;
- handlers.has_dimension = &ClassImpl::hasDimension;
- handlers.unset_dimension = &ClassImpl::unsetDimension;
+ _handlers.write_dimension = &ClassImpl::writeDimension;
+ _handlers.read_dimension = &ClassImpl::readDimension;
+ _handlers.has_dimension = &ClassImpl::hasDimension;
+ _handlers.unset_dimension = &ClassImpl::unsetDimension;
// functions for the magic properties handlers (__get, __set, __isset and __unset)
- handlers.write_property = &ClassImpl::writeProperty;
- handlers.read_property = &ClassImpl::readProperty;
- handlers.has_property = &ClassImpl::hasProperty;
- handlers.unset_property = &ClassImpl::unsetProperty;
+ _handlers.write_property = &ClassImpl::writeProperty;
+ _handlers.read_property = &ClassImpl::readProperty;
+ _handlers.has_property = &ClassImpl::hasProperty;
+ _handlers.unset_property = &ClassImpl::unsetProperty;
// when a method is called (__call and __invoke)
- handlers.get_method = &ClassImpl::getMethod;
- handlers.get_closure = &ClassImpl::getClosure;
+ _handlers.get_method = &ClassImpl::getMethod;
+ _handlers.get_closure = &ClassImpl::getClosure;
// handler to cast to a different type
- handlers.cast_object = &ClassImpl::cast;
+ _handlers.cast_object = &ClassImpl::cast;
// method to compare two objects
- handlers.compare_objects = &ClassImpl::compare;
+ _handlers.compare_objects = &ClassImpl::compare;
// remember that object is now initialized
- initialized = true;
+ _initialized = true;
// done
- return &handlers;
+ return &_handlers;
+}
+
+/**
+ * Alternative way to retrieve object handlers, given a class entry
+ * @param entry
+ * @return zend_object_handlers
+ */
+zend_object_handlers *ClassImpl::objectHandlers(zend_class_entry *entry)
+{
+ return self(entry)->objectHandlers();
}
/**
@@ -465,7 +467,7 @@ int ClassImpl::cast(zval *val, zval *retval, int type TSRMLS_DC)
case Type::Float: result = meta->callToFloat(object).detach(); break;
case Type::Bool: result = meta->callToBool(object).detach(); break;
case Type::String: result = meta->callToString(object).detach(); break;
- default: throw NotImplemented(); break;
+ default: throw NotImplemented(); break;
}
// @todo do we turn into endless conversion if the __toString object returns 'this' ??
@@ -520,8 +522,10 @@ zend_object_value ClassImpl::cloneObject(zval *val TSRMLS_DC)
// report error on failure (this does not occur because the cloneObject()
// method is only installed as handler when we have seen that there is indeed
- // a copy constructor)
- if (!cpp) throw Php::Exception(std::string("Unable to clone ") + entry->name);
+ // a copy constructor). Because this function is directly called from the
+ // Zend engine, we can call zend_error() (which does a longjmp()) to throw
+ // an exception back to the Zend engine)
+ if (!cpp) zend_error(E_ERROR, "Unable to clone %s", entry->name);
// the thing we're going to return
zend_object_value result;
@@ -1179,8 +1183,10 @@ zend_object_value ClassImpl::createObject(zend_class_entry *entry TSRMLS_DC)
// create a new base C++ object
auto *cpp = impl->_base->construct();
- // report error on failure
- if (!cpp) throw Php::Exception(std::string("Unable to instantiate ") + entry->name);
+ // report error on failure, because this function is called directly from the
+ // Zend engine, we can call zend_error() here (which does a longjmp() back to
+ // the Zend engine)
+ if (!cpp) zend_error(E_ERROR, "Unable to instantiate %s", entry->name);
// the thing we're going to return
zend_object_value result;
@@ -1208,8 +1214,10 @@ zend_object_value ClassImpl::createObject(zend_class_entry *entry TSRMLS_DC)
*/
zend_object_iterator *ClassImpl::getIterator(zend_class_entry *entry, zval *object, int by_ref TSRMLS_DC)
{
- // by-ref is not possible (copied from SPL)
- if (by_ref) throw Php::Exception("Foreach by ref is not possible");
+ // by-ref is not possible (copied from SPL), this function is called directly
+ // from the Zend engine, so we can use zend_error() to longjmp() back to the
+ // Zend engine)
+ if (by_ref) zend_error(E_ERROR, "Foreach by ref is not possible");
// retrieve the traversable object
Traversable *traversable = dynamic_cast<Traversable*>(ObjectImpl::find(object TSRMLS_CC)->object());
@@ -1372,14 +1380,25 @@ void ClassImpl::initialize(ClassBase *base, const std::string &prefix TSRMLS_DC)
if (_parent)
{
// check if the base class was already defined
- if (_parent->_entry) entry.parent = _parent->_entry;
-
- // otherwise an error is reported
- else std::cerr << "Derived class " << name() << " is initialized before base class " << _parent->name() << ": base class is ignored" << std::endl;
+ if (_parent->_entry)
+ {
+ // register the class
+ _entry = zend_register_internal_class_ex(&entry, _parent->_entry, const_cast<char*>(_parent->name().c_str()) TSRMLS_CC);
+ }
+ else
+ {
+ // report an error - the extension programmer probably made an error
+ std::cerr << "Derived class " << name() << " is initialized before base class " << _parent->name() << ": base class is ignored" << std::endl;
+
+ // register the class, but without the base class
+ _entry = zend_register_internal_class(&entry TSRMLS_CC);
+ }
+ }
+ else
+ {
+ // register the class
+ _entry = zend_register_internal_class(&entry TSRMLS_CC);
}
-
- // register the class
- _entry = zend_register_internal_class(&entry TSRMLS_CC);
// register the classes
for (auto &interface : _interfaces)
@@ -1390,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 ec28322..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
*/
@@ -84,6 +78,18 @@ private:
* @var std::shared_ptr
*/
std::shared_ptr<ClassImpl> _parent;
+
+ /**
+ * The object handlers for instances of this class
+ * @var zend_object_handlers
+ */
+ zend_object_handlers _handlers;
+
+ /**
+ * Are the handlers already initialized?
+ * @var bool
+ */
+ bool _initialized = false;
/**
@@ -204,6 +210,13 @@ public:
zend_object_handlers *objectHandlers();
/**
+ * Alternative way to retrieve object handlers, given a class entry
+ * @param entry
+ * @return zend_object_handlers
+ */
+ static zend_object_handlers *objectHandlers(zend_class_entry *entry);
+
+ /**
* Function to create a new iterator to iterate over an object
* @param entry The class entry
* @param object The object to iterate over
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/extension.cpp b/zend/extension.cpp
index 9685b32..1545f89 100644
--- a/zend/extension.cpp
+++ b/zend/extension.cpp
@@ -97,6 +97,18 @@ void *Extension::module()
}
/**
+ * Is the extension object in a locked state? This happens after the
+ * get_module() function was called for the first time ("apache reload"
+ * forces a new call to get_module())
+ *
+ * @return bool
+ */
+bool Extension::locked() const
+{
+ return _impl->locked();
+}
+
+/**
* End of namespace
*/
}
diff --git a/zend/extensionimpl.cpp b/zend/extensionimpl.cpp
index ea0c049..f073acf 100644
--- a/zend/extensionimpl.cpp
+++ b/zend/extensionimpl.cpp
@@ -142,6 +142,10 @@ int ExtensionImpl::processStartup(int type, int module_number TSRMLS_DC)
// initialize the extension
extension->initialize(TSRMLS_C);
+ // remember that we're initialized (when you use "apache reload" it is
+ // possible that the processStartup() method is called more than once)
+ extension->_locked = true;
+
// is the callback registered?
if (extension->_onStartup) extension->_onStartup();
@@ -164,6 +168,12 @@ int ExtensionImpl::processShutdown(int type, int module_number TSRMLS_DC)
// unregister the ini entries
zend_unregister_ini_entries(module_number TSRMLS_CC);
+ // destruct the ini entries
+ if (extension->_ini) delete[] extension->_ini;
+
+ // forget the ini entries
+ extension->_ini = nullptr;
+
// is the callback registered?
if (extension->_onShutdown) extension->_onShutdown();
@@ -272,7 +282,7 @@ ExtensionImpl::~ExtensionImpl()
*/
zend_module_entry *ExtensionImpl::module()
{
- // check if functions we're already defined
+ // check if functions were already defined
if (_entry.functions) return &_entry;
// the number of functions
diff --git a/zend/extensionimpl.h b/zend/extensionimpl.h
index ba3e6c8..0e9a289 100644
--- a/zend/extensionimpl.h
+++ b/zend/extensionimpl.h
@@ -20,16 +20,18 @@ class ExtensionImpl : public ExtensionBase
protected:
/**
* The information that is passed to the Zend engine
- *
- * Although it would be slightly faster to not make this a pointer, this
- * would require that client code also includes the PHP header files, which
- * we try to prevent with the PHP-CPP library, so we allocate it dynamically.
- *
* @var zend_module_entry
*/
zend_module_entry _entry;
/**
+ * Is the object locked? This prevents crashes for 'apache reload'
+ * because we then do not have to re-initialize the entire php engine
+ * @var bool
+ */
+ bool _locked = false;
+
+ /**
* The .ini entries
*
* @var zend_ini_entry
@@ -56,6 +58,17 @@ public:
*/
virtual ~ExtensionImpl();
+ /**
+ * Is the object locked (true) or is it still possible to add more functions,
+ * classes and other elements to it?
+ * @return bool
+ */
+ bool locked()
+ {
+ // return member
+ return _locked;
+ }
+
/**
* Retrieve the module entry
*
diff --git a/zend/fatalerror.cpp b/zend/fatalerror.cpp
new file mode 100644
index 0000000..e57be73
--- /dev/null
+++ b/zend/fatalerror.cpp
@@ -0,0 +1,31 @@
+/**
+ * FatalError.cpp
+ *
+ * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
+ * @copyright 2014 Copernica BV
+ */
+#include "includes.h"
+
+/**
+ * Set up namespace
+ */
+namespace Php {
+
+/**
+ * Report this error as a fatal error
+ * @return bool
+ */
+bool FatalError::report() const
+{
+ // report the error
+ zend_error(E_ERROR, "%s", what());
+
+ // return true: it was reported
+ return true;
+}
+
+/**
+ * End of namespace
+ */
+}
+
diff --git a/zend/hashiterator.h b/zend/hashiterator.h
index b1f409f..36c3d6c 100644
--- a/zend/hashiterator.h
+++ b/zend/hashiterator.h
@@ -29,7 +29,7 @@ public:
* @param first Should it start on the first position?
* @param tsrm_ls
*/
- HashIterator(HashTable *hashtable, bool first) : _table(hashtable)
+ HashIterator(HashTable *hashtable, bool first, bool is_array = false) : _table(hashtable), _is_array(is_array)
{
// reset the hash pointer to the internal position
if (hashtable && first)
@@ -56,7 +56,7 @@ public:
* @param tsrm_ls
*/
HashIterator(const HashIterator &that TSRMLS_DC) :
- _table(that._table), _position(that._position)
+ _table(that._table), _position(that._position), _is_array(that._is_array)
{
// read current position
read();
@@ -166,7 +166,13 @@ private:
* @var HashPosition
*/
Bucket *_position = nullptr;
-
+
+ /**
+ * Is a hash interator in array
+ * @var bool
+ */
+ bool _is_array = false;
+
/**
* The current key and value
* @var std::pair
@@ -207,7 +213,7 @@ private:
// numeric keys are the easiest ones
if (type == HASH_KEY_IS_LONG) key = (int64_t)num_key;
- else key = string_key;
+ else key = std::string(string_key, str_len - 1);
#endif
@@ -223,7 +229,7 @@ private:
// if the key is private (it starts with a null character) we should return
// false to report that the object is not in a completely valid state
- return !_current.first.isString() || _current.first.rawValue()[0];
+ return _is_array || !_current.first.isString() || _current.first.rawValue()[0];
}
/**
diff --git a/zend/includes.h b/zend/includes.h
index cabb096..63b435e 100644
--- a/zend/includes.h
+++ b/zend/includes.h
@@ -48,6 +48,7 @@
#include "../include/inivalue.h"
#include "../include/ini.h"
#include "../include/exception.h"
+#include "../include/fatalerror.h"
#include "../include/streams.h"
#include "../include/type.h"
#include "../include/hashparent.h"
diff --git a/zend/namespace.cpp b/zend/namespace.cpp
index 2b4b62a..9274bf0 100644
--- a/zend/namespace.cpp
+++ b/zend/namespace.cpp
@@ -22,6 +22,9 @@ namespace Php {
*/
Namespace &Namespace::add(const char *name, const native_callback_0 &function, const Arguments &arguments)
{
+ // skip when locked
+ if (locked()) return *this;
+
// add a function
_functions.push_back(std::make_shared<Function>(name, function, arguments));
@@ -38,6 +41,9 @@ Namespace &Namespace::add(const char *name, const native_callback_0 &function, c
*/
Namespace &Namespace::add(const char *name, const native_callback_1 &function, const Arguments &arguments)
{
+ // skip when locked
+ if (locked()) return *this;
+
// add a function
_functions.push_back(std::make_shared<Function>(name, function, arguments));
@@ -54,6 +60,9 @@ Namespace &Namespace::add(const char *name, const native_callback_1 &function, c
*/
Namespace &Namespace::add(const char *name, const native_callback_2 &function, const Arguments &arguments)
{
+ // skip when locked
+ if (locked()) return *this;
+
// add a function
_functions.push_back(std::make_shared<Function>(name, function, arguments));
@@ -70,6 +79,9 @@ Namespace &Namespace::add(const char *name, const native_callback_2 &function, c
*/
Namespace &Namespace::add(const char *name, const native_callback_3 &function, const Arguments &arguments)
{
+ // skip when locked
+ if (locked()) return *this;
+
// add a function
_functions.push_back(std::make_shared<Function>(name, function, arguments));
diff --git a/zend/object.cpp b/zend/object.cpp
index 08553b5..dc548cc 100644
--- a/zend/object.cpp
+++ b/zend/object.cpp
@@ -22,7 +22,7 @@ Object::Object(const char *name, Base *base)
// does the object already have a handle?
if (base->implementation())
{
- // the object is already instantiated, we can assign it the this object
+ // the object is already instantiated, we can assign it to this object
operator=(Value(base));
}
else
@@ -32,9 +32,12 @@ Object::Object(const char *name, Base *base)
// this is a brand new object that should be allocated, the C++ instance
// is already there (created by the extension) but it is not yet stored
- // in PHP, find out the classname first
- auto *entry = zend_fetch_class(name, ::strlen(name), 0 TSRMLS_CC);
- if (!entry) throw Php::Exception(std::string("Unknown class name ") + name);
+ // in PHP, find out the classname first (we use the FatalError class
+ // here because this function is called from C++ context, and zend_error()
+ // would cause a longjmp() which does not clean up C++ objects created
+ // by the extension).
+ auto *entry = zend_fetch_class(name, ::strlen(name), ZEND_FETCH_CLASS_SILENT TSRMLS_CC);
+ if (!entry) throw FatalError(std::string("Unknown class name ") + name);
// construct an implementation (this will also set the implementation
// member in the base object)
@@ -42,6 +45,9 @@ Object::Object(const char *name, Base *base)
// now we can store it
operator=(Value(base));
+
+ // install the object handlers
+ Z_OBJVAL_P(_val).handlers = ClassImpl::objectHandlers(entry);
}
}
@@ -54,9 +60,12 @@ void Object::instantiate(const char *name)
// we need the tsrm_ls variable
TSRMLS_FETCH();
- // convert the name into a class_entry
- auto *entry = zend_fetch_class(name, ::strlen(name), 0 TSRMLS_CC);
- if (!entry) throw Php::Exception(std::string("Unknown class name ") + name);
+ // convert the name into a class_entry (we use the FatalError class
+ // here because this function is called from C++ context, and zend_error()
+ // would cause a longjmp() which does not clean up C++ objects created
+ // by the extension).
+ auto *entry = zend_fetch_class(name, ::strlen(name), ZEND_FETCH_CLASS_SILENT TSRMLS_CC);
+ if (!entry) throw FatalError(std::string("Unknown class name ") + name);
// initiate the zval (which was already allocated in the base constructor)
object_init_ex(_val, entry);
diff --git a/zend/objectimpl.h b/zend/objectimpl.h
index f66a6ab..7f16320 100644
--- a/zend/objectimpl.h
+++ b/zend/objectimpl.h
@@ -66,7 +66,7 @@ public:
*/
ObjectImpl(zend_class_entry *entry, Base *base TSRMLS_DC)
{
- // allocate a mixed object (for some reason this does not have to deallocated)
+ // allocate a mixed object (for some reason this does not have to be deallocated)
_mixed = (MixedObject *)emalloc(sizeof(MixedObject));
// copy properties to the mixed object
diff --git a/zend/origexception.h b/zend/origexception.h
index 775e412..55f89cf 100644
--- a/zend/origexception.h
+++ b/zend/origexception.h
@@ -127,10 +127,12 @@ inline void process(Exception &exception TSRMLS_DC)
// the exception is native, call the zend throw method
zend_throw_exception(zend_exception_get_default(TSRMLS_C), (char *)exception.what(), 0 TSRMLS_CC);
}
- else
+
+ // or does it have its own report function?
+ else if (!exception.report())
{
// this is not a native exception, so it was originally thrown by a
- // php script, and then not caught by the c++ of the extensiont, we are
+ // php script, and then not caught by the c++ of the extension, we are
// going to tell to the exception that it is still active
OrigException &orig = static_cast<OrigException&>(exception);
diff --git a/zend/streambuf.cpp b/zend/streambuf.cpp
index 86e5f03..11d928d 100644
--- a/zend/streambuf.cpp
+++ b/zend/streambuf.cpp
@@ -33,7 +33,6 @@ int StreamBuf::sync()
// not null terminated and (2) it could contain % signs and allow all
// sorts of buffer overflows.
zend_error(_error, "%.*s", (int)size, pbase());
-
}
else
{
@@ -52,4 +51,4 @@ int StreamBuf::sync()
* End namespace
*/
}
- \ No newline at end of file
+
diff --git a/zend/value.cpp b/zend/value.cpp
index d3ad06f..ac999c7 100644
--- a/zend/value.cpp
+++ b/zend/value.cpp
@@ -124,17 +124,18 @@ Value::Value(const std::string &value)
*/
Value::Value(const char *value, int size)
{
+ // allocate the zval
+ MAKE_STD_ZVAL(_val);
+
// is there a value?
if (value)
{
// create a string zval
- MAKE_STD_ZVAL(_val);
ZVAL_STRINGL(_val, value, size < 0 ? ::strlen(value) : size, 1);
}
else
{
- // create a null zval
- MAKE_STD_ZVAL(_val);
+ // store null
ZVAL_NULL(_val);
}
}
@@ -191,7 +192,7 @@ Value::Value(const Base *object)
auto *impl = object->implementation();
// do we have a handle?
- if (!impl) throw Php::Exception("Assigning an unassigned object to a variable");
+ if (!impl) throw FatalError("Assigning an unassigned object to a variable");
// make a regular zval, and set it to an object
MAKE_STD_ZVAL(_val);
@@ -402,7 +403,7 @@ Value &Value::operator=(Value &&value)
if (this == &value) return *this;
// is the object a reference?
- if (Z_ISREF_P(_val))
+ if (_val && Z_ISREF_P(_val))
{
// @todo difference if the other object is a reference or not?
@@ -448,7 +449,7 @@ Value &Value::operator=(Value &&value)
{
// destruct the zval (this function will decrement the reference counter,
// and only destruct if there are no other references left)
- zval_ptr_dtor(&_val);
+ if (_val) zval_ptr_dtor(&_val);
// just copy the zval completely
_val = value._val;
@@ -1311,6 +1312,45 @@ Value Value::exec(const char *name, int argc, struct _zval_struct ***params)
}
/**
+ * Comparison operators== for hardcoded Value
+ * @param value
+ */
+bool Value::operator==(const Value &value) const
+{
+ // we need the tsrm_ls variable
+ TSRMLS_FETCH();
+
+ // zval that will hold the result of the comparison
+ zval result;
+
+ // run the comparison
+ if (SUCCESS != compare_function(&result, _val, value._val TSRMLS_CC)) return false;
+
+ // convert to boolean
+ return result.value.lval == 0;
+}
+
+/**
+ * Comparison operators< for hardcoded Value
+ * @param value
+ * @return bool
+ */
+bool Value::operator<(const Value &value) const
+{
+ // we need the tsrm_ls variable
+ TSRMLS_FETCH();
+
+ // zval that will hold the result of the comparison
+ zval result;
+
+ // run the comparison
+ if (SUCCESS != compare_function(&result, _val, value._val TSRMLS_CC)) return false;
+
+ // convert to boolean
+ return result.value.lval < 0;
+}
+
+/**
* The type of object
* @return Type
*/
@@ -1333,7 +1373,10 @@ Value &Value::setType(Type type)
// if this is not a reference variable, we should detach it to implement copy on write
SEPARATE_ZVAL_IF_NOT_REF(&_val);
- // run the conversion
+ // run the conversion, when it fails we throw a fatal error which will
+ // in the end result in a zend_error() call. This FatalError class is necessary
+ // because a direct call to zend_error() will do a longjmp() which may not
+ // clean up the C++ objects created by the extension
switch (type) {
case Type::Null: convert_to_null(_val); break;
case Type::Numeric: convert_to_long(_val); break;
@@ -1342,10 +1385,10 @@ Value &Value::setType(Type type)
case Type::Array: convert_to_array(_val); break;
case Type::Object: convert_to_object(_val); break;
case Type::String: convert_to_string(_val); break;
- case Type::Resource: throw Php::Exception("Resource types can not be handled by the PHP-CPP library"); break;
- case Type::Constant: throw Php::Exception("Constant types can not be assigned to a PHP-CPP library variable"); break;
- case Type::ConstantArray: throw Php::Exception("Constant types can not be assigned to a PHP-CPP library variable"); break;
- case Type::Callable: throw Php::Exception("Callable types can not be assigned to a PHP-CPP library variable"); break;
+ case Type::Resource: throw FatalError("Resource types can not be handled by the PHP-CPP library"); break;
+ case Type::Constant: throw FatalError("Constant types can not be assigned to a PHP-CPP library variable"); break;
+ case Type::ConstantArray: throw FatalError("Constant types can not be assigned to a PHP-CPP library variable"); break;
+ case Type::Callable: throw FatalError("Callable types can not be assigned to a PHP-CPP library variable"); break;
}
// done
@@ -1366,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
*/
@@ -1567,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;
}
@@ -1583,7 +1736,7 @@ std::map<std::string,Php::Value> Value::mapValue() const
ValueIterator Value::createIterator(bool begin) const
{
// check type
- if (isArray()) return ValueIterator(new HashIterator(Z_ARRVAL_P(_val), begin));
+ if (isArray()) return ValueIterator(new HashIterator(Z_ARRVAL_P(_val), begin, true));
// get access to the hast table
if (isObject())
@@ -1604,7 +1757,7 @@ ValueIterator Value::createIterator(bool begin) const
else
{
// construct a regular iterator
- return ValueIterator(new HashIterator(Z_OBJ_HT_P(_val)->get_properties(_val TSRMLS_CC), begin));
+ return ValueIterator(new HashIterator(Z_OBJPROP_P(_val), begin));
}
}