diff options
author | Emiel Bruijntjes <emiel.bruijntjes@copernica.com> | 2014-08-26 13:08:39 +0200 |
---|---|---|
committer | Emiel Bruijntjes <emiel.bruijntjes@copernica.com> | 2014-08-26 13:08:39 +0200 |
commit | f526c4c7a7ada1ab534ca0976c7b66718b9a5660 (patch) | |
tree | 68b4db309ee00d3b7ebc1743b4825057b3db7f28 | |
parent | a286c34777e48d59410e7511f52f4a6f08000ba3 (diff) | |
parent | 97bc6757346d394a4b7d5898983be298e0b0ea98 (diff) |
fixed conflict
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; @@ -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)); } } |