From 927e54ecde25ed7ba718697631dc8ffd208e361a Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 6 May 2014 09:33:22 +0200 Subject: [PATCH 01/41] Value objects constructed with (const char *)NULL caused a crash, this has been fixed so they hold a PHP NULL value --- zend/value.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/zend/value.cpp b/zend/value.cpp index 90402344..7fc45d65 100644 --- a/zend/value.cpp +++ b/zend/value.cpp @@ -124,9 +124,20 @@ Value::Value(const std::string &value) */ Value::Value(const char *value, int size) { - // create a string zval + // allocate the zval MAKE_STD_ZVAL(_val); - ZVAL_STRINGL(_val, value, size < 0 ? ::strlen(value) : size, 1); + + // do we have a valid value? + if (value) + { + // create a string zval + ZVAL_STRINGL(_val, value, size < 0 ? ::strlen(value) : size, 1); + } + else + { + // store null + ZVAL_NULL(_val); + } } /** From f57607d2d58f6e7689a3550c84ba68ce42c6a7b3 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 6 May 2014 09:34:12 +0200 Subject: [PATCH 02/41] When "apache reload" is called, the PHP-CPP library made the entire Apache process crash. This has been fixed --- include/extension.h | 16 ++++++++++++++++ include/namespace.h | 37 +++++++++++++++++++++++++++++++++++++ zend/extension.cpp | 12 ++++++++++++ zend/extensionimpl.cpp | 12 +++++++++++- zend/extensionimpl.h | 23 ++++++++++++++++++----- zend/namespace.cpp | 12 ++++++++++++ 6 files changed, 106 insertions(+), 6 deletions(-) diff --git a/include/extension.h b/include/extension.h index f03c8fb7..562fcebd 100644 --- a/include/extension.h +++ b/include/extension.h @@ -111,6 +111,9 @@ class Extension : public Namespace */ 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 @@ class Extension : public Namespace */ 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 @@ class Extension : public Namespace { 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/namespace.h b/include/namespace.h index 828c12c6..e7138503 100644 --- a/include/namespace.h +++ b/include/namespace.h @@ -48,6 +48,25 @@ class Namespace */ std::list> _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 @@ class Namespace template Namespace &add(Class &&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(new Class(std::move(type)))); @@ -96,6 +118,9 @@ class Namespace template Namespace &add(const Class &type) { + // skip when locked + if (locked()) return *this; + // and add it to the list of classes _classes.push_back(std::unique_ptr(new Class(type))); @@ -110,6 +135,9 @@ class Namespace */ 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(new Interface(std::move(interface)))); @@ -124,6 +152,9 @@ class Namespace */ 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(new Interface(interface))); @@ -138,6 +169,9 @@ class Namespace */ Namespace &add(Namespace &&ns) { + // skip when locked + if (locked()) return *this; + // add it to the list of namespaces _namespaces.push_back(std::unique_ptr(new Namespace(std::move(ns)))); @@ -152,6 +186,9 @@ class Namespace */ 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(new Namespace(ns))); diff --git a/zend/extension.cpp b/zend/extension.cpp index 9685b32c..1545f893 100644 --- a/zend/extension.cpp +++ b/zend/extension.cpp @@ -96,6 +96,18 @@ void *Extension::module() return _impl->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 ea0c0493..f073acf2 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 ba3e6c8e..0e9a289c 100644 --- a/zend/extensionimpl.h +++ b/zend/extensionimpl.h @@ -20,15 +20,17 @@ 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 * @@ -56,6 +58,17 @@ class ExtensionImpl : public ExtensionBase */ 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/namespace.cpp b/zend/namespace.cpp index 2b4b62af..9274bf05 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(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(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(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(name, function, arguments)); From 9497b86d1a5a31684f8fcce9c6db3d66b5a79260 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 6 May 2014 10:03:42 +0200 Subject: [PATCH 03/41] update documentation about extension callbacks --- documentation/extension-callbacks.html | 35 ++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/documentation/extension-callbacks.html b/documentation/extension-callbacks.html index ae266dec..4821f9bf 100644 --- a/documentation/extension-callbacks.html +++ b/documentation/extension-callbacks.html @@ -130,6 +130,41 @@

Extension callbacks

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.

+

Forked engines (like Apache)

+

+ If you run PHP on a pre-fork web server (like Apache), your extension is + loaded and initialized before 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. +

+

+ 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 every 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 necessary, or because it is replaced by + a fresh and new worker. +

+

+ 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() 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. +

Watch out for multi-threading

If your extension runs on a multi-threaded PHP installation, you need to take From 9502a731c09593db0d3b7ad3a1d3ee90b8b44901 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 6 May 2014 10:06:20 +0200 Subject: [PATCH 04/41] tiny changes in documentation about extension callbacks --- documentation/extension-callbacks.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/documentation/extension-callbacks.html b/documentation/extension-callbacks.html index 4821f9bf..b8c26866 100644 --- a/documentation/extension-callbacks.html +++ b/documentation/extension-callbacks.html @@ -151,7 +151,7 @@

Forked engines (like Apache)

while the shutdown function is called by every 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 necessary, or because it is replaced by + exits because it no longer is needed, or because it is replaced by a fresh and new worker.

@@ -159,7 +159,8 @@

Forked engines (like Apache)

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() too. + 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 From 5ba897e11fc7a4648a9323ed2bb74c1ac6aa57d8 Mon Sep 17 00:00:00 2001 From: valmat Date: Sun, 8 Jun 2014 22:45:27 +0600 Subject: [PATCH 05/41] Comparison operators for hardcoded Value --- include/value.h | 11 +++++++ zend/value.cpp | 80 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/include/value.h b/include/value.h index 63e8e6f9..c478eb26 100644 --- a/include/value.h +++ b/include/value.h @@ -321,6 +321,17 @@ class Value : private HashParent bool operator< (const char *value) const { return ::strcmp(rawValue(), value) < 0; } 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; + bool operator<=(const Value &value) const { return !operator>(value); } + bool operator>=(const Value &value) const { return !operator<(value); } + /** * Comparison operators * @param value diff --git a/zend/value.cpp b/zend/value.cpp index 7fc45d65..477017a5 100644 --- a/zend/value.cpp +++ b/zend/value.cpp @@ -1311,6 +1311,86 @@ Value Value::exec(const char *name, int argc, struct _zval_struct ***params) return do_exec(&_val, method._val, argc, params); } + +/** + * Comparison operators for hardcoded Value + * @param value + */ +bool Value::operator==(const Value &value) const +{ + auto tp = type(); + if(tp != value.type()) + return false; + + switch (tp) { + case Type::Null: return true; + case Type::Numeric: return (_val->value.lval == value._val->value.lval); + case Type::Float: return (_val->value.dval == value._val->value.dval); + case Type::Bool: return (_val->value.lval == value._val->value.lval); + case Type::String: return (_val->value.str.len && value._val->value.str.len) && (::strcmp(_val->value.str.val, value._val->value.str.val) == 0); + // @todo + case Type::Array: throw Php::Exception("TODO implement comparison arrays"); break; + // @todo + case Type::Object: throw Php::Exception("TODO implement comparison arrays"); break; + case Type::Resource: return (_val->value.lval == value._val->value.lval); + 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; + + } + return false; +} +bool Value::operator< (const Value &value) const +{ + auto tp = type(); + if(tp != value.type()){ + // @todo maybe this can be improved + throw Php::Exception("It is not possible to compare different types"); + return false; + } + switch (tp) { + case Type::Null: return false; + case Type::Numeric: return (_val->value.lval < value._val->value.lval); + case Type::Float: return (_val->value.dval < value._val->value.dval); + case Type::Bool: return (_val->value.lval < value._val->value.lval); + case Type::String: return (::strcmp(_val->value.str.val, value._val->value.str.val) < 0); + // @todo + case Type::Array: throw Php::Exception("TODO implement comparison arrays"); break; + // @todo + case Type::Object: throw Php::Exception("TODO implement comparison arrays"); break; + case Type::Resource: throw Php::Exception("Resource types is not comparable"); 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; + } + return true; +} +bool Value::operator> (const Value &value) const +{ + auto tp = type(); + if(tp != value.type()){ + // @todo maybe this can be improved + throw Php::Exception("It is not possible to compare different types"); + return false; + } + switch (tp) { + case Type::Null: return false; + case Type::Numeric: return (_val->value.lval > value._val->value.lval); + case Type::Float: return (_val->value.dval > value._val->value.dval); + case Type::Bool: return (_val->value.lval > value._val->value.lval); + case Type::String: return (::strcmp(_val->value.str.val, value._val->value.str.val) > 0); + // @todo + case Type::Array: throw Php::Exception("TODO implement comparison arrays"); break; + // @todo + case Type::Object: throw Php::Exception("TODO implement comparison arrays"); break; + case Type::Resource: throw Php::Exception("Resource types is not comparable"); 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; + } + return true; +} + /** * The type of object * @return Type From b4936cbc31c20c390d50333da5c83ff31795acf7 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Wed, 11 Jun 2014 10:45:01 +0200 Subject: [PATCH 06/41] when an object was created using Php::Object("MyClass", new MyClass()), the object handlers were not installed, which caused the magic methods not to be functional (issue #94) --- include/classbase.h | 4 ++-- zend/classimpl.cpp | 56 ++++++++++++++++++++++++--------------------- zend/classimpl.h | 19 +++++++++++++++ zend/object.cpp | 5 +++- zend/objectimpl.h | 2 +- 5 files changed, 56 insertions(+), 30 deletions(-) diff --git a/include/classbase.h b/include/classbase.h index a736c4e0..9f0bac3c 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/zend/classimpl.cpp b/zend/classimpl.cpp index d63956a8..953738a9 100644 --- a/zend/classimpl.cpp +++ b/zend/classimpl.cpp @@ -330,52 +330,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 +469,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' ?? diff --git a/zend/classimpl.h b/zend/classimpl.h index ec28322f..bd631b8b 100644 --- a/zend/classimpl.h +++ b/zend/classimpl.h @@ -84,6 +84,18 @@ class ClassImpl * @var std::shared_ptr */ std::shared_ptr _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; /** @@ -203,6 +215,13 @@ class ClassImpl */ 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 diff --git a/zend/object.cpp b/zend/object.cpp index 08553b5d..940b1436 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 @@ -42,6 +42,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); } } diff --git a/zend/objectimpl.h b/zend/objectimpl.h index f66a6ab8..7f16320a 100644 --- a/zend/objectimpl.h +++ b/zend/objectimpl.h @@ -66,7 +66,7 @@ class ObjectImpl */ 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 From b17b09c3b482f51a842f5272e88a7ed2820a1925 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Wed, 11 Jun 2014 11:10:31 +0200 Subject: [PATCH 07/41] issue #93: fix crash when moving to null Php::Value object --- zend/value.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zend/value.cpp b/zend/value.cpp index 7fc45d65..7592f575 100644 --- a/zend/value.cpp +++ b/zend/value.cpp @@ -403,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? @@ -449,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; From a3aea78834102b7d0f206c73463de69050f15076 Mon Sep 17 00:00:00 2001 From: valmat Date: Wed, 11 Jun 2014 16:36:01 +0600 Subject: [PATCH 08/41] Corrected in accordance with Proposition https://github.com/CopernicaMarketingSoftware/PHP-CPP/pull/95#issuecomment-45716890 --- zend/value.cpp | 49 +++++++++++++++++++++---------------------------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/zend/value.cpp b/zend/value.cpp index 477017a5..74c84e58 100644 --- a/zend/value.cpp +++ b/zend/value.cpp @@ -1318,21 +1318,20 @@ Value Value::exec(const char *name, int argc, struct _zval_struct ***params) */ bool Value::operator==(const Value &value) const { - auto tp = type(); - if(tp != value.type()) - return false; + Type tp = type(); + const _zval_struct *thatval = (tp == value.type()) ? value._val : value.clone(tp)._val; switch (tp) { case Type::Null: return true; - case Type::Numeric: return (_val->value.lval == value._val->value.lval); - case Type::Float: return (_val->value.dval == value._val->value.dval); - case Type::Bool: return (_val->value.lval == value._val->value.lval); - case Type::String: return (_val->value.str.len && value._val->value.str.len) && (::strcmp(_val->value.str.val, value._val->value.str.val) == 0); + case Type::Numeric: return (_val->value.lval == thatval->value.lval); + case Type::Float: return (_val->value.dval == thatval->value.dval); + case Type::Bool: return (_val->value.lval == thatval->value.lval); + case Type::String: return (_val->value.str.len && thatval->value.str.len) && (::strcmp(_val->value.str.val, thatval->value.str.val) == 0); // @todo case Type::Array: throw Php::Exception("TODO implement comparison arrays"); break; // @todo case Type::Object: throw Php::Exception("TODO implement comparison arrays"); break; - case Type::Resource: return (_val->value.lval == value._val->value.lval); + case Type::Resource: return (_val->value.lval == thatval->value.lval); 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; @@ -1342,18 +1341,15 @@ bool Value::operator==(const Value &value) const } bool Value::operator< (const Value &value) const { - auto tp = type(); - if(tp != value.type()){ - // @todo maybe this can be improved - throw Php::Exception("It is not possible to compare different types"); - return false; - } + Type tp = type(); + const _zval_struct *thatval = (tp == value.type()) ? value._val : value.clone(tp)._val; + switch (tp) { case Type::Null: return false; - case Type::Numeric: return (_val->value.lval < value._val->value.lval); - case Type::Float: return (_val->value.dval < value._val->value.dval); - case Type::Bool: return (_val->value.lval < value._val->value.lval); - case Type::String: return (::strcmp(_val->value.str.val, value._val->value.str.val) < 0); + case Type::Numeric: return (_val->value.lval < thatval->value.lval); + case Type::Float: return (_val->value.dval < thatval->value.dval); + case Type::Bool: return (_val->value.lval < thatval->value.lval); + case Type::String: return (::strcmp(_val->value.str.val, thatval->value.str.val) < 0); // @todo case Type::Array: throw Php::Exception("TODO implement comparison arrays"); break; // @todo @@ -1367,18 +1363,15 @@ bool Value::operator< (const Value &value) const } bool Value::operator> (const Value &value) const { - auto tp = type(); - if(tp != value.type()){ - // @todo maybe this can be improved - throw Php::Exception("It is not possible to compare different types"); - return false; - } + Type tp = type(); + const _zval_struct *thatval = (tp == value.type()) ? value._val : value.clone(tp)._val; + switch (tp) { case Type::Null: return false; - case Type::Numeric: return (_val->value.lval > value._val->value.lval); - case Type::Float: return (_val->value.dval > value._val->value.dval); - case Type::Bool: return (_val->value.lval > value._val->value.lval); - case Type::String: return (::strcmp(_val->value.str.val, value._val->value.str.val) > 0); + case Type::Numeric: return (_val->value.lval > thatval->value.lval); + case Type::Float: return (_val->value.dval > thatval->value.dval); + case Type::Bool: return (_val->value.lval > thatval->value.lval); + case Type::String: return (::strcmp(_val->value.str.val, thatval->value.str.val) > 0); // @todo case Type::Array: throw Php::Exception("TODO implement comparison arrays"); break; // @todo From e350c8f1b33d7c6c605f5450e8cf901cc3e5e1c4 Mon Sep 17 00:00:00 2001 From: valmat Date: Wed, 11 Jun 2014 18:34:13 +0600 Subject: [PATCH 09/41] Value::operator== --- include/value.h | 8 ++- zend/value.cpp | 151 ++++++++++++++++++++++++++++++++++-------------- 2 files changed, 115 insertions(+), 44 deletions(-) diff --git a/include/value.h b/include/value.h index c478eb26..307702ef 100644 --- a/include/value.h +++ b/include/value.h @@ -321,6 +321,12 @@ class Value : private HashParent bool operator< (const char *value) const { return ::strcmp(rawValue(), value) < 0; } bool operator> (const char *value) const { return ::strcmp(rawValue(), value) > 0; } + /** + * Helper method to prepare for comparison + * @param value + */ + bool comparePrepare(const Value &value) const; + /** * Comparison operators for hardcoded Value * @param value @@ -328,7 +334,7 @@ class Value : private HashParent 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; + 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); } diff --git a/zend/value.cpp b/zend/value.cpp index 74c84e58..e1f66838 100644 --- a/zend/value.cpp +++ b/zend/value.cpp @@ -1311,36 +1311,127 @@ Value Value::exec(const char *name, int argc, struct _zval_struct ***params) return do_exec(&_val, method._val, argc, params); } +/** + * Helper method to prepare for comparison + * @param value + */ +bool Value::comparePrepare(const Value &value) const +{ + Type thisTp = type(), thatTp = value.type(); + if(thisTp == Type::Resource || thatTp == Type::Resource) + { + throw Php::Exception("Resource types is not comparable"); + return false; + } + if(thisTp == Type::Constant || thatTp == Type::Constant || thisTp == Type::ConstantArray || thatTp == Type::ConstantArray) + { + throw Php::Exception("Constant types can not be assigned to a PHP-CPP library variable"); + return false; + } + if(thisTp == Type::Callable || thatTp == Type::Callable) + { + throw Php::Exception("Callable types can not be assigned to a PHP-CPP library variable"); + return false; + } + return true; +} /** - * Comparison operators for hardcoded Value + * Comparison operators== for hardcoded Value * @param value */ bool Value::operator==(const Value &value) const { - Type tp = type(); - const _zval_struct *thatval = (tp == value.type()) ? value._val : value.clone(tp)._val; + if(!comparePrepare(value)) return false; - switch (tp) { - case Type::Null: return true; - case Type::Numeric: return (_val->value.lval == thatval->value.lval); - case Type::Float: return (_val->value.dval == thatval->value.dval); - case Type::Bool: return (_val->value.lval == thatval->value.lval); - case Type::String: return (_val->value.str.len && thatval->value.str.len) && (::strcmp(_val->value.str.val, thatval->value.str.val) == 0); - // @todo - case Type::Array: throw Php::Exception("TODO implement comparison arrays"); break; - // @todo - case Type::Object: throw Php::Exception("TODO implement comparison arrays"); break; - case Type::Resource: return (_val->value.lval == thatval->value.lval); - 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; + Type thisTp = type(), thatTp = value.type(); + //Type tp = type(); + //const _zval_struct *thatval = (tp == value.type()) ? value._val : value.clone(tp)._val; + + if( (thatTp == Type::Array && thisTp != Type::Array) || (thisTp == Type::Array && thatTp != Type::Array) ) + { + return false; + } + if(thatTp == Type::Array && thisTp == Type::Array) + { + if(size() != value.size()) return false; + auto thisIt = begin(), thatIt = value.begin(), thisEnd = end();; + while(thisIt != thisEnd) + { + if(*thisIt != *thatIt) return false; + ++thisIt; + ++thatIt; + } + return true; } + + if(thatTp == Type::Null) + { + return (thisTp == Type::Null) || + ( + (thisTp == Type::Numeric || thisTp == Type::Bool) && + _val->value.lval == 0 + ) || + ( (thisTp == Type::String) && _val->value.str.len == 0); + } + if(thisTp == Type::Null) + { + return ( + (thatTp == Type::Numeric || thatTp == Type::Bool) && + value._val->value.lval == 0 + ) || + ( (thatTp == Type::String) && value._val->value.str.len == 0); + } + + if(thatTp == Type::Float) + { + return clone(Type::Float) == value._val->value.dval; + } + if(thisTp == Type::Float) + { + return value.clone(Type::Float) == _val->value.dval; + } + + if(thatTp == Type::String) + { + return clone(Type::String) == value.rawValue(); + } + if(thisTp == Type::String) + { + return value.clone(Type::String) == rawValue(); + } + + if(thatTp == Type::Numeric || thatTp == Type::Bool) + { + return clone(thatTp) == value._val->value.lval; + } + if(thisTp == Type::String) + { + return value.clone(thisTp) == _val->value.lval; + } + + + if( (thatTp == Type::Object && thisTp != Type::Object) || (thisTp == Type::Object && thatTp != Type::Object) ) + { + return false; + } + if(thatTp == Type::Object && thisTp == Type::Object) + { + return !ClassImpl::compare(_val, value._val TSRMLS_CC); + } + return false; } + +/** + * Comparison operators< for hardcoded Value + * @param value + */ bool Value::operator< (const Value &value) const { + if(!comparePrepare(value)) return false; + Type tp = type(); const _zval_struct *thatval = (tp == value.type()) ? value._val : value.clone(tp)._val; @@ -1354,32 +1445,6 @@ bool Value::operator< (const Value &value) const case Type::Array: throw Php::Exception("TODO implement comparison arrays"); break; // @todo case Type::Object: throw Php::Exception("TODO implement comparison arrays"); break; - case Type::Resource: throw Php::Exception("Resource types is not comparable"); 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; - } - return true; -} -bool Value::operator> (const Value &value) const -{ - Type tp = type(); - const _zval_struct *thatval = (tp == value.type()) ? value._val : value.clone(tp)._val; - - switch (tp) { - case Type::Null: return false; - case Type::Numeric: return (_val->value.lval > thatval->value.lval); - case Type::Float: return (_val->value.dval > thatval->value.dval); - case Type::Bool: return (_val->value.lval > thatval->value.lval); - case Type::String: return (::strcmp(_val->value.str.val, thatval->value.str.val) > 0); - // @todo - case Type::Array: throw Php::Exception("TODO implement comparison arrays"); break; - // @todo - case Type::Object: throw Php::Exception("TODO implement comparison arrays"); break; - case Type::Resource: throw Php::Exception("Resource types is not comparable"); 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; } return true; } From 4f3769855dd65a8ef0b647c7e98860b8aa747c26 Mon Sep 17 00:00:00 2001 From: valmat Date: Wed, 11 Jun 2014 19:34:40 +0600 Subject: [PATCH 10/41] done --- include/value.h | 6 - tests/cpp/h/variables.h | 1 + tests/cpp/include/variables/028-029-compare.h | 194 ++++++++++++++++++ tests/cpp/main.cpp | 3 + tests/php/phpt/variables/028-compare1.phpt | 53 +++++ tests/php/phpt/variables/029-compare2.phpt | 74 +++++++ zend/value.cpp | 128 +----------- 7 files changed, 335 insertions(+), 124 deletions(-) create mode 100644 tests/cpp/include/variables/028-029-compare.h create mode 100644 tests/php/phpt/variables/028-compare1.phpt create mode 100644 tests/php/phpt/variables/029-compare2.phpt diff --git a/include/value.h b/include/value.h index 307702ef..e8337a03 100644 --- a/include/value.h +++ b/include/value.h @@ -321,12 +321,6 @@ class Value : private HashParent bool operator< (const char *value) const { return ::strcmp(rawValue(), value) < 0; } bool operator> (const char *value) const { return ::strcmp(rawValue(), value) > 0; } - /** - * Helper method to prepare for comparison - * @param value - */ - bool comparePrepare(const Value &value) const; - /** * Comparison operators for hardcoded Value * @param value diff --git a/tests/cpp/h/variables.h b/tests/cpp/h/variables.h index 8a074cdf..0b684ddc 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 00000000..bd95298d --- /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 37ba5edd..47cd90d5 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 00000000..2885fe8f --- /dev/null +++ b/tests/php/phpt/variables/028-compare1.phpt @@ -0,0 +1,53 @@ +--TEST-- +Test bool Value::operator==(const Value &value) const +--SKIPIF-- + +--FILEEOF-- + +--FILEEOF-- +value.lval == 0 - ) || - ( (thisTp == Type::String) && _val->value.str.len == 0); - } - if(thisTp == Type::Null) - { - return ( - (thatTp == Type::Numeric || thatTp == Type::Bool) && - value._val->value.lval == 0 - ) || - ( (thatTp == Type::String) && value._val->value.str.len == 0); - } - - if(thatTp == Type::Float) - { - return clone(Type::Float) == value._val->value.dval; - } - if(thisTp == Type::Float) - { - return value.clone(Type::Float) == _val->value.dval; - } - - if(thatTp == Type::String) - { - return clone(Type::String) == value.rawValue(); - } - if(thisTp == Type::String) - { - return value.clone(Type::String) == rawValue(); - } - - if(thatTp == Type::Numeric || thatTp == Type::Bool) - { - return clone(thatTp) == value._val->value.lval; - } - if(thisTp == Type::String) - { - return value.clone(thisTp) == _val->value.lval; - } - - - if( (thatTp == Type::Object && thisTp != Type::Object) || (thisTp == Type::Object && thatTp != Type::Object) ) + zval result; + if(SUCCESS != compare_function(&result, _val, value._val TSRMLS_CC) ) { + throw Php::Exception("Not comparable"); return false; } - if(thatTp == Type::Object && thisTp == Type::Object) - { - return !ClassImpl::compare(_val, value._val TSRMLS_CC); - } - - return false; + return (0 == result.value.lval); } /** @@ -1430,23 +1332,13 @@ bool Value::operator==(const Value &value) const */ bool Value::operator< (const Value &value) const { - if(!comparePrepare(value)) return false; - - Type tp = type(); - const _zval_struct *thatval = (tp == value.type()) ? value._val : value.clone(tp)._val; - - switch (tp) { - case Type::Null: return false; - case Type::Numeric: return (_val->value.lval < thatval->value.lval); - case Type::Float: return (_val->value.dval < thatval->value.dval); - case Type::Bool: return (_val->value.lval < thatval->value.lval); - case Type::String: return (::strcmp(_val->value.str.val, thatval->value.str.val) < 0); - // @todo - case Type::Array: throw Php::Exception("TODO implement comparison arrays"); break; - // @todo - case Type::Object: throw Php::Exception("TODO implement comparison arrays"); break; + zval result; + if(SUCCESS != compare_function(&result, _val, value._val TSRMLS_CC) ) + { + throw Php::Exception("Not comparable"); + return false; } - return true; + return (-1 == result.value.lval); } /** From 7b64409dfb1e399c7c980fd3227d5c0432c39e6c Mon Sep 17 00:00:00 2001 From: valmat Date: Thu, 19 Jun 2014 00:03:46 +0600 Subject: [PATCH 11/41] implementation issue #97 --- include/class.h | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/include/class.h b/include/class.h index e0316df9..cffc7bd3 100644 --- a/include/class.h +++ b/include/class.h @@ -201,6 +201,32 @@ class Class : private ClassBase Class &extends(const Class &base) { ClassBase::extends(base); return *this; } private: + /** + * Method to create the object if it is default constructable + * @param orig + * @return Base* + */ + template + typename std::enable_if::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 std::enable_if::value, Base*>::type + static maybeConstruct() + { + // create empty instance + return nullptr; + } + /** * Construct a new instance of the object * @return Base @@ -208,7 +234,7 @@ class Class : private ClassBase virtual Base* construct() const override { // construct an instance - return new T(); + return maybeConstruct(); } /** @@ -225,7 +251,7 @@ class Class : private ClassBase } /** - * 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* */ From c6c68cbc60711b43e8a570d708db3768240fcc5a Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Thu, 19 Jun 2014 13:48:59 +0200 Subject: [PATCH 12/41] errors are no longer thrown as exceptions, but are php fatal errors, so that they more closely match the zend error reporting system --- include/fatalerror.h | 66 ++++++++++++++++++++++++++++++++++++++++++++ zend/fatalerror.cpp | 31 +++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 include/fatalerror.h create mode 100644 zend/fatalerror.cpp diff --git a/include/fatalerror.h b/include/fatalerror.h new file mode 100644 index 00000000..b178f250 --- /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 + * @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/zend/fatalerror.cpp b/zend/fatalerror.cpp new file mode 100644 index 00000000..e57be737 --- /dev/null +++ b/zend/fatalerror.cpp @@ -0,0 +1,31 @@ +/** + * FatalError.cpp + * + * @author Emiel Bruijntjes + * @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 + */ +} + From ca60a32c601fe9b0236d3d4717c7b94368a3c172 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Thu, 19 Jun 2014 13:49:07 +0200 Subject: [PATCH 13/41] errors are no longer thrown as exceptions, but are php fatal errors, so that they more closely match the zend error reporting system --- include/array.h | 10 ++++----- include/exception.h | 10 +++++++++ include/object.h | 8 +++---- phpcpp.h | 1 + zend/classimpl.cpp | 18 ++++++++++------ zend/includes.h | 1 + zend/object.cpp | 18 ++++++++++------ zend/origexception.h | 6 ++++-- zend/streambuf.cpp | 3 +-- zend/value.cpp | 50 +++++++++++++++++++++++++++----------------- 10 files changed, 81 insertions(+), 44 deletions(-) diff --git a/include/array.h b/include/array.h index 0b6ceb9b..4d6b6b18 100644 --- a/include/array.h +++ b/include/array.h @@ -31,7 +31,7 @@ class Array : public Value 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 @@ class Array : public Value 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 @@ class Array : public Value 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 @@ class Array : public Value 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 @@ class Array : public Value 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/exception.h b/include/exception.h index 671df9e5..aff0afc3 100644 --- a/include/exception.h +++ b/include/exception.h @@ -75,6 +75,16 @@ class Exception : public std::exception // 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/object.h b/include/object.h index 97c9482b..2e6628a9 100644 --- a/include/object.h +++ b/include/object.h @@ -31,7 +31,7 @@ class Object : public Value 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"); } /** @@ -123,7 +123,7 @@ class Object : public Value 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 +140,7 @@ class Object : public Value 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 +160,7 @@ class Object : public Value 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/phpcpp.h b/phpcpp.h index 3f94cefa..c3f33659 100644 --- a/phpcpp.h +++ b/phpcpp.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include diff --git a/zend/classimpl.cpp b/zend/classimpl.cpp index 953738a9..ca75c5e8 100644 --- a/zend/classimpl.cpp +++ b/zend/classimpl.cpp @@ -524,8 +524,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; @@ -1183,8 +1185,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; @@ -1212,8 +1216,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(ObjectImpl::find(object TSRMLS_CC)->object()); diff --git a/zend/includes.h b/zend/includes.h index cabb0969..63b435eb 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/object.cpp b/zend/object.cpp index 940b1436..dc548cc1 100644 --- a/zend/object.cpp +++ b/zend/object.cpp @@ -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) @@ -57,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/origexception.h b/zend/origexception.h index 775e4129..55f89cf2 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(exception); diff --git a/zend/streambuf.cpp b/zend/streambuf.cpp index 86e5f034..11d928d1 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 6458bd5c..9a1a5dbb 100644 --- a/zend/value.cpp +++ b/zend/value.cpp @@ -192,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); @@ -1317,28 +1317,37 @@ Value Value::exec(const char *name, int argc, struct _zval_struct ***params) */ 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; - if(SUCCESS != compare_function(&result, _val, value._val TSRMLS_CC) ) - { - throw Php::Exception("Not comparable"); - return false; - } - return (0 == result.value.lval); + + // 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 +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; - if(SUCCESS != compare_function(&result, _val, value._val TSRMLS_CC) ) - { - throw Php::Exception("Not comparable"); - return false; - } - return (-1 == result.value.lval); + + // run the comparison + if (SUCCESS != compare_function(&result, _val, value._val TSRMLS_CC)) return false; + + // convert to boolean + return result.value.lval < 0; } /** @@ -1364,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; @@ -1373,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 From 80e9566d52a682b011e38083fbe18f233298f03e Mon Sep 17 00:00:00 2001 From: valmat Date: Mon, 23 Jun 2014 20:16:58 +0600 Subject: [PATCH 14/41] Fixed issue #100 --- zend/classimpl.cpp | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/zend/classimpl.cpp b/zend/classimpl.cpp index ca75c5e8..23c39cfb 100644 --- a/zend/classimpl.cpp +++ b/zend/classimpl.cpp @@ -1382,14 +1382,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; - + if (_parent->_entry) + { + // register the class + _entry = zend_register_internal_class_ex(&entry, _parent->_entry, const_cast(_parent->name().c_str()) TSRMLS_CC); + } + // otherwise an error is reported - else std::cerr << "Derived class " << name() << " is initialized before base class " << _parent->name() << ": base class is ignored" << std::endl; + else + { + std::cerr << "Derived class " << name() << " is initialized before base class " << _parent->name() << ": base class is ignored" << std::endl; + // register the 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) From d0dbed9ed21123455adcd0277991036ad9bdbb06 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 8 Jul 2014 15:03:43 +0200 Subject: [PATCH 15/41] in the documentation and source code comments, the old method name Value::resize() was used, while the actual name is reserve(), solves issue #102 --- documentation/variables.html | 6 +++--- include/value.h | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/documentation/variables.html b/documentation/variables.html index 98a6d492..dfc5b18e 100644 --- a/documentation/variables.html +++ b/documentation/variables.html @@ -223,10 +223,10 @@

Strings

// 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 @@

Strings

// 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/value.h b/include/value.h index e8337a03..f72304cf 100644 --- a/include/value.h +++ b/include/value.h @@ -387,7 +387,7 @@ class Value : private HashParent * 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 * */ @@ -396,7 +396,7 @@ class Value : private HashParent /** * 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. * From 28578382589dab25ea5fbd35b7754687706abc07 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Tue, 8 Jul 2014 15:15:00 +0200 Subject: [PATCH 16/41] changed comments to match coding style --- zend/classimpl.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zend/classimpl.cpp b/zend/classimpl.cpp index 23c39cfb..b2acc24a 100644 --- a/zend/classimpl.cpp +++ b/zend/classimpl.cpp @@ -1387,12 +1387,12 @@ void ClassImpl::initialize(ClassBase *base, const std::string &prefix TSRMLS_DC) // register the class _entry = zend_register_internal_class_ex(&entry, _parent->_entry, const_cast(_parent->name().c_str()) TSRMLS_CC); } - - // otherwise an error is reported 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 + + // register the class, but without the base class _entry = zend_register_internal_class(&entry TSRMLS_CC); } } From d24ce36eaa07e9e924e7084fafa4f504cde39c2f Mon Sep 17 00:00:00 2001 From: andot Date: Thu, 10 Jul 2014 12:45:04 +0800 Subject: [PATCH 17/41] Implementation issue #98 --- include/fastcall.h | 13 +++++++++++++ phpcpp.h | 3 ++- zend/fastcall.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ zend/includes.h | 1 + 4 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 include/fastcall.h create mode 100644 zend/fastcall.cpp diff --git a/include/fastcall.h b/include/fastcall.h new file mode 100644 index 00000000..29f274db --- /dev/null +++ b/include/fastcall.h @@ -0,0 +1,13 @@ +/** + * fastcall.h + * + * This file holds some PHP functions implementation in C directly. + * + */ + +namespace Php { + + Value eval(const std::string &phpCode); + +} + diff --git a/phpcpp.h b/phpcpp.h index c3f33659..8be1a251 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 */ @@ -60,6 +60,7 @@ #include #include #include +#include /** * Macro to export a function diff --git a/zend/fastcall.cpp b/zend/fastcall.cpp new file mode 100644 index 00000000..3ecd5987 --- /dev/null +++ b/zend/fastcall.cpp @@ -0,0 +1,40 @@ +/** + * fastcall.cpp + * + * This file holds some PHP functions implementation in C directly. + * + */ + +#include "includes.h" + +namespace Php { + + 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) + { + // throw an exception, php couldn't evaluate code + throw Exception("PHP couldn't evaluate: " + phpCode); + + // unreachable, but let's return at least something to prevent compiler warnings + return nullptr; + } + else + { + // was an exception thrown inside the function? 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; + } + } + +} \ No newline at end of file diff --git a/zend/includes.h b/zend/includes.h index 63b435eb..8af557cc 100644 --- a/zend/includes.h +++ b/zend/includes.h @@ -79,6 +79,7 @@ #include "../include/namespace.h" #include "../include/extension.h" #include "../include/call.h" +#include "../include/fastcall.h" /** * Common header files for internal use only From 894ef44bacd1f44f6f7f3c74284af94ddda89f7e Mon Sep 17 00:00:00 2001 From: andot Date: Thu, 10 Jul 2014 12:54:31 +0800 Subject: [PATCH 18/41] Add Php::class_exists implementation. --- include/fastcall.h | 13 +++++++++++++ phpcpp.h | 3 ++- zend/fastcall.cpp | 42 ++++++++++++++++++++++++++++++++++++++++++ zend/includes.h | 1 + 4 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 include/fastcall.h create mode 100644 zend/fastcall.cpp diff --git a/include/fastcall.h b/include/fastcall.h new file mode 100644 index 00000000..a5a8e1c4 --- /dev/null +++ b/include/fastcall.h @@ -0,0 +1,13 @@ +/** + * fastcall.h + * + * This file holds some PHP functions implementation in C directly. + * + */ + +namespace Php { + + bool class_exists(const std::string &classname, bool autoload = true); + +} + diff --git a/phpcpp.h b/phpcpp.h index c3f33659..8be1a251 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 */ @@ -60,6 +60,7 @@ #include #include #include +#include /** * Macro to export a function diff --git a/zend/fastcall.cpp b/zend/fastcall.cpp new file mode 100644 index 00000000..e198e699 --- /dev/null +++ b/zend/fastcall.cpp @@ -0,0 +1,42 @@ +/** + * fastcall.cpp + * + * This file holds some PHP functions implementation in C directly. + * + */ + +#include "includes.h" + +namespace Php { + + bool class_exists(const std::string &classname, bool autoload) { + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + zend_class_entry **ce; + int found; + const char * str = classname.c_str(); + int32_t len = (int32_t)classname.length(); + + + if (autoload) { + char lc_name[len + 1]; + zend_str_tolower_copy(lc_name, str, len); + + char *name = lc_name; + if (lc_name[0] == '\\') { + name = &lc_name[1]; + --len; + } + + found = zend_hash_find(EG(class_table), name, len + 1, (void **) &ce); + return (found == SUCCESS && !(((*ce)->ce_flags & (ZEND_ACC_INTERFACE | ZEND_ACC_TRAIT)) > ZEND_ACC_EXPLICIT_ABSTRACT_CLASS)); + } + + if (zend_lookup_class(str, len, &ce TSRMLS_CC) == SUCCESS) { + return (((*ce)->ce_flags & (ZEND_ACC_INTERFACE | (ZEND_ACC_TRAIT - ZEND_ACC_EXPLICIT_ABSTRACT_CLASS))) == 0); + } + return false; + } + +} \ No newline at end of file diff --git a/zend/includes.h b/zend/includes.h index 63b435eb..8af557cc 100644 --- a/zend/includes.h +++ b/zend/includes.h @@ -79,6 +79,7 @@ #include "../include/namespace.h" #include "../include/extension.h" #include "../include/call.h" +#include "../include/fastcall.h" /** * Common header files for internal use only From b6cc95e9a96edf3a1733c4f562d42eed79dd8946 Mon Sep 17 00:00:00 2001 From: andot Date: Thu, 10 Jul 2014 13:06:22 +0800 Subject: [PATCH 19/41] Add Php::is_a implementation. Add is, isSubClassOf for Php::Value. Add Php::is_a implementation. Add Php::is_subclass_of implementation. --- include/fastcall.h | 18 ++++++++++++++++++ include/value.h | 21 +++++++++++++++++++++ phpcpp.h | 3 ++- zend/includes.h | 1 + zend/value.cpp | 38 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 include/fastcall.h diff --git a/include/fastcall.h b/include/fastcall.h new file mode 100644 index 00000000..778a9b2c --- /dev/null +++ b/include/fastcall.h @@ -0,0 +1,18 @@ +/** + * fastcall.h + * + * This file holds some PHP functions implementation in C directly. + * + */ + +namespace Php { + + inline bool is_a(const Value &obj, const std::string classname, bool allow_string = false) { + return obj.is(classname, allow_string); + } + inline bool is_subclass_of(const Value &obj, const std::string classname, bool allow_string = true) { + return obj.isSubClassOf(classname, allow_string); + } + +} + diff --git a/include/value.h b/include/value.h index f72304cf..b3c288c9 100644 --- a/include/value.h +++ b/include/value.h @@ -965,7 +965,28 @@ class Value : private HashParent return dynamic_cast(base); } + /** + * Checks if this object is of the class or has the class as one of its parents + * @param classname + * @param allow_string + * @return bool + */ + inline bool is(const std::string &classname, bool allow_string=false) const { + return isImpl(classname, allow_string, false); + } + + /** + * Checks if this object has the class as one of its parents + * @param classname + * @return bool + */ + inline bool isSubClassOf(const std::string &classname, bool allow_string=true) const { + return isImpl(classname, allow_string, true); + } + private: + + bool isImpl(const std::string &classname, bool allow_string, bool only_subclass) const; /** * Call function with a number of parameters * @param argc Number of parameters diff --git a/phpcpp.h b/phpcpp.h index c3f33659..8be1a251 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 */ @@ -60,6 +60,7 @@ #include #include #include +#include /** * Macro to export a function diff --git a/zend/includes.h b/zend/includes.h index 63b435eb..8af557cc 100644 --- a/zend/includes.h +++ b/zend/includes.h @@ -79,6 +79,7 @@ #include "../include/namespace.h" #include "../include/extension.h" #include "../include/call.h" +#include "../include/fastcall.h" /** * Common header files for internal use only diff --git a/zend/value.cpp b/zend/value.cpp index 9a1a5dbb..38a0425f 100644 --- a/zend/value.cpp +++ b/zend/value.cpp @@ -1408,6 +1408,44 @@ bool Value::isCallable() const return zend_is_callable(_val, 0, NULL TSRMLS_CC); } +bool Value::isImpl(const std::string &classname, bool allow_string, bool only_subclass) const { + /* + * allow_string - is default is false, isSubclassOf is true. + * if it's allowed, the the autoloader will be called if the class does not exist. + * default behaviour is different, as 'is' used to be used to test mixed return + * values and there is no easy way to deprecate this. + */ + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + zend_class_entry *instance_ce; + zend_class_entry **ce; + + if (allow_string && isString()) { + zend_class_entry **the_ce; + if (zend_lookup_class(Z_STRVAL_P(_val), Z_STRLEN_P(_val), &the_ce TSRMLS_CC) == FAILURE) { + return false; + } + instance_ce = *the_ce; + } + else if (isObject() && HAS_CLASS_ENTRY(*_val)) { + instance_ce = Z_OBJCE_P(_val); + } + else { + return false; + } + + if (zend_lookup_class_ex(classname.c_str(), (int32_t)classname.length(), NULL, 0, &ce TSRMLS_CC) == FAILURE) { + return false; + } + + if (only_subclass && instance_ce == *ce) { + return false; + } + + return instanceof_function(instance_ce, *ce TSRMLS_CC); +} + /** * Make a clone of the type * @return Value From 6dba54f21a1972d0558299c865c03dcb3371d0b9 Mon Sep 17 00:00:00 2001 From: andot Date: Thu, 10 Jul 2014 13:44:38 +0800 Subject: [PATCH 20/41] Fixed issue #107 --- include/value.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/value.h b/include/value.h index f72304cf..8eac3945 100644 --- a/include/value.h +++ b/include/value.h @@ -100,7 +100,7 @@ class Value : private HashParent * @param value */ template - Value(const std::map &value) + Value(const std::map &value) : Value(Type::Array) { // set all elements for (auto &iter : value) setRaw(iter.first.c_str(), iter.first.size(), iter.second); @@ -502,6 +502,8 @@ class Value : private HashParent // result variable std::map result; + + for (auto &iter : map) result[iter.first] = iter.second; // done return result; From 15e6ddb931324bae6866e35898f41e066312a46b Mon Sep 17 00:00:00 2001 From: andot Date: Thu, 10 Jul 2014 14:08:34 +0800 Subject: [PATCH 21/41] Fixed Object Constructor. The old copy constructor of Php::Object forgot the call("__construct") --- include/object.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/include/object.h b/include/object.h index 2e6628a9..47935d70 100644 --- a/include/object.h +++ b/include/object.h @@ -42,8 +42,10 @@ class Object : public Value Object(const Value &value) : Value() { // string types are instantiated - if (value.isString()) instantiate(value); - + if (value.isString()) { + instantiate(value); + call("__construct"); + } // otherwise copy the other object else operator=(value); } From bf88eac6d32771891c8335e56f0d3fde6d53edd8 Mon Sep 17 00:00:00 2001 From: andot Date: Fri, 11 Jul 2014 16:59:58 +0800 Subject: [PATCH 22/41] Fixed a bug of HashIterator The old implementation of HashIterator can't support `"\0"` prefix key. I think the array and the object is different. Maybe the user didn't want to get the private property of an object. but in an array, `"\0"` prefix key doesn't mean private key. so we should return `"\0"` prefix key when it is an array. --- zend/hashiterator.h | 16 +++++++++++----- zend/value.cpp | 4 ++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/zend/hashiterator.h b/zend/hashiterator.h index b1f409f6..36c3d6cf 100644 --- a/zend/hashiterator.h +++ b/zend/hashiterator.h @@ -29,7 +29,7 @@ class HashIterator : public ValueIteratorImpl * @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 @@ class HashIterator : public ValueIteratorImpl * @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 @@ class HashIterator : public ValueIteratorImpl * @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 @@ class HashIterator : public ValueIteratorImpl // 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 @@ class HashIterator : public ValueIteratorImpl // 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/value.cpp b/zend/value.cpp index 9a1a5dbb..352e90d3 100644 --- a/zend/value.cpp +++ b/zend/value.cpp @@ -1626,7 +1626,7 @@ std::map 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()) @@ -1647,7 +1647,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)); } } From 78c5a1463d51d8a1e92545d4dfda18845e1f23c4 Mon Sep 17 00:00:00 2001 From: andot Date: Mon, 14 Jul 2014 21:40:39 +0800 Subject: [PATCH 23/41] Change C99 VLA C++ dynamic array --- zend/fastcall.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zend/fastcall.cpp b/zend/fastcall.cpp index e198e699..359c3ada 100644 --- a/zend/fastcall.cpp +++ b/zend/fastcall.cpp @@ -20,7 +20,7 @@ namespace Php { if (autoload) { - char lc_name[len + 1]; + char *lc_name = new char[len + 1]; zend_str_tolower_copy(lc_name, str, len); char *name = lc_name; @@ -30,6 +30,7 @@ namespace Php { } found = zend_hash_find(EG(class_table), name, len + 1, (void **) &ce); + delete [] lc_name; return (found == SUCCESS && !(((*ce)->ce_flags & (ZEND_ACC_INTERFACE | ZEND_ACC_TRAIT)) > ZEND_ACC_EXPLICIT_ABSTRACT_CLASS)); } From 3f1b209984d8932a667681278c6610772079b12b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ad=C3=A1n=20Lobato?= Date: Mon, 21 Jul 2014 11:28:35 +0200 Subject: [PATCH 24/41] Fix typo --- documentation/ten-reasons-for-using-php-cpp.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/ten-reasons-for-using-php-cpp.html b/documentation/ten-reasons-for-using-php-cpp.html index 484f3fbd..7d830d4b 100644 --- a/documentation/ten-reasons-for-using-php-cpp.html +++ b/documentation/ten-reasons-for-using-php-cpp.html @@ -71,7 +71,7 @@

7. Proven technology

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 From 340b05438b13fbe7cd937835e1abe935967330d3 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sat, 26 Jul 2014 13:39:16 +0200 Subject: [PATCH 25/41] renamed fastcall.cpp to eval.cpp, and moved the Php::eval() definition to the call.h header file --- include/call.h | 5 +++++ include/fastcall.h | 13 ----------- phpcpp.h | 1 - zend/eval.cpp | 56 ++++++++++++++++++++++++++++++++++++++++++++++ zend/fastcall.cpp | 40 --------------------------------- zend/includes.h | 1 - 6 files changed, 61 insertions(+), 55 deletions(-) delete mode 100644 include/fastcall.h create mode 100644 zend/eval.cpp delete mode 100644 zend/fastcall.cpp diff --git a/include/call.h b/include/call.h index 16de1fef..05c59556 100644 --- a/include/call.h +++ b/include/call.h @@ -12,6 +12,11 @@ */ namespace Php { +/** + * List of functions that are available for use in PHP + */ +Value eval(const std::string &phpCode); + /** * Call a function in PHP * @param name Name of the function to call diff --git a/include/fastcall.h b/include/fastcall.h deleted file mode 100644 index 29f274db..00000000 --- a/include/fastcall.h +++ /dev/null @@ -1,13 +0,0 @@ -/** - * fastcall.h - * - * This file holds some PHP functions implementation in C directly. - * - */ - -namespace Php { - - Value eval(const std::string &phpCode); - -} - diff --git a/phpcpp.h b/phpcpp.h index 8be1a251..baa91cca 100644 --- a/phpcpp.h +++ b/phpcpp.h @@ -60,7 +60,6 @@ #include #include #include -#include /** * Macro to export a function diff --git a/zend/eval.cpp b/zend/eval.cpp new file mode 100644 index 00000000..e0baaed6 --- /dev/null +++ b/zend/eval.cpp @@ -0,0 +1,56 @@ +/** + * Eval.cpp + * + * This file holds the implementation for the Php::eval() function + * + * @author 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) + { + // throw an exception, php couldn't evaluate code + throw Exception("PHP eval error"); + + // unreachable, but let's return at least something to prevent compiler warnings + return nullptr; + } + else + { + // was an exception thrown inside the function? 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/fastcall.cpp b/zend/fastcall.cpp deleted file mode 100644 index 3ecd5987..00000000 --- a/zend/fastcall.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/** - * fastcall.cpp - * - * This file holds some PHP functions implementation in C directly. - * - */ - -#include "includes.h" - -namespace Php { - - 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) - { - // throw an exception, php couldn't evaluate code - throw Exception("PHP couldn't evaluate: " + phpCode); - - // unreachable, but let's return at least something to prevent compiler warnings - return nullptr; - } - else - { - // was an exception thrown inside the function? 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; - } - } - -} \ No newline at end of file diff --git a/zend/includes.h b/zend/includes.h index 8af557cc..63b435eb 100644 --- a/zend/includes.h +++ b/zend/includes.h @@ -79,7 +79,6 @@ #include "../include/namespace.h" #include "../include/extension.h" #include "../include/call.h" -#include "../include/fastcall.h" /** * Common header files for internal use only From 513db71261813c2785e4292cfe05bc1d5b814f4f Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sat, 26 Jul 2014 13:40:11 +0200 Subject: [PATCH 26/41] minor change so that phpcpp.h does not have any changes in this project --- phpcpp.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpcpp.h b/phpcpp.h index baa91cca..c3f33659 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 */ From 8801ed40905040115d8af6031c4f3c48d1f67e18 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sat, 26 Jul 2014 14:36:29 +0200 Subject: [PATCH 27/41] no more exceptions for wrong eval()'ed code --- zend/eval.cpp | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/zend/eval.cpp b/zend/eval.cpp index e0baaed6..4632fff5 100644 --- a/zend/eval.cpp +++ b/zend/eval.cpp @@ -33,16 +33,28 @@ Value eval(const std::string &phpCode) zval* retval = nullptr; if (zend_eval_stringl_ex((char *)phpCode.c_str(), (int32_t)phpCode.length(), retval, (char *)"", 1 TSRMLS_CC) != SUCCESS) { - // throw an exception, php couldn't evaluate code - throw Exception("PHP eval error"); - - // unreachable, but let's return at least something to prevent compiler warnings + // 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 function? In that case we throw a C++ new exception - // to give the C++ code the chance to catch it + // 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 From fec2fca2df568aa059e93d59bcfb63daec686583 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sat, 26 Jul 2014 15:17:41 +0200 Subject: [PATCH 28/41] modifed class_exists function to work with a char* without having to convert it into a std::string --- include/call.h | 3 ++ include/fastcall.h | 13 --------- phpcpp.h | 1 - zend/exists.cpp | 68 ++++++++++++++++++++++++++++++++++++++++++++++ zend/fastcall.cpp | 43 ----------------------------- zend/includes.h | 1 - 6 files changed, 71 insertions(+), 58 deletions(-) delete mode 100644 include/fastcall.h create mode 100644 zend/exists.cpp delete mode 100644 zend/fastcall.cpp diff --git a/include/call.h b/include/call.h index 05c59556..80ef6c97 100644 --- a/include/call.h +++ b/include/call.h @@ -15,6 +15,9 @@ namespace Php { /** * List of functions that are available for use in PHP */ +bool class_exists(const char *classname, size_t size, bool autoload = true); +bool class_exists(const char *classname, bool autoload = true) { return class_exists(classname, strlen(classname), autoload); } +bool class_exists(const std::string &classname, bool autoload = true) { return class_exists(classname.c_str(), classname.size(), autoload); } Value eval(const std::string &phpCode); /** diff --git a/include/fastcall.h b/include/fastcall.h deleted file mode 100644 index a5a8e1c4..00000000 --- a/include/fastcall.h +++ /dev/null @@ -1,13 +0,0 @@ -/** - * fastcall.h - * - * This file holds some PHP functions implementation in C directly. - * - */ - -namespace Php { - - bool class_exists(const std::string &classname, bool autoload = true); - -} - diff --git a/phpcpp.h b/phpcpp.h index 8be1a251..baa91cca 100644 --- a/phpcpp.h +++ b/phpcpp.h @@ -60,7 +60,6 @@ #include #include #include -#include /** * Macro to export a function diff --git a/zend/exists.cpp b/zend/exists.cpp new file mode 100644 index 00000000..6bab8ba9 --- /dev/null +++ b/zend/exists.cpp @@ -0,0 +1,68 @@ +/** + * Exists.cpp + * + * This file holds the implementation of all *_exists() functions, + * like class_exists(), et cetera + * + * @author andot + */ + +/** + * Dependencies + */ +#include "includes.h" + +/** + * 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) + { + // 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 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, classname, len); + + // see if there is a class with this name + if (SUCCESS != zend_hash_find(EG(class_table), name, 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); + } + else + { + // no auto-load + if (SUCCESS != zend_lookup_class(str, len, &ce TSRMLS_CC) == SUCCESS) 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; + } +} + +/** + * End of namespace + */ +} diff --git a/zend/fastcall.cpp b/zend/fastcall.cpp deleted file mode 100644 index 359c3ada..00000000 --- a/zend/fastcall.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/** - * fastcall.cpp - * - * This file holds some PHP functions implementation in C directly. - * - */ - -#include "includes.h" - -namespace Php { - - bool class_exists(const std::string &classname, bool autoload) { - // we need the tsrm_ls variable - TSRMLS_FETCH(); - - zend_class_entry **ce; - int found; - const char * str = classname.c_str(); - int32_t len = (int32_t)classname.length(); - - - if (autoload) { - char *lc_name = new char[len + 1]; - zend_str_tolower_copy(lc_name, str, len); - - char *name = lc_name; - if (lc_name[0] == '\\') { - name = &lc_name[1]; - --len; - } - - found = zend_hash_find(EG(class_table), name, len + 1, (void **) &ce); - delete [] lc_name; - return (found == SUCCESS && !(((*ce)->ce_flags & (ZEND_ACC_INTERFACE | ZEND_ACC_TRAIT)) > ZEND_ACC_EXPLICIT_ABSTRACT_CLASS)); - } - - if (zend_lookup_class(str, len, &ce TSRMLS_CC) == SUCCESS) { - return (((*ce)->ce_flags & (ZEND_ACC_INTERFACE | (ZEND_ACC_TRAIT - ZEND_ACC_EXPLICIT_ABSTRACT_CLASS))) == 0); - } - return false; - } - -} \ No newline at end of file diff --git a/zend/includes.h b/zend/includes.h index 8af557cc..63b435eb 100644 --- a/zend/includes.h +++ b/zend/includes.h @@ -79,7 +79,6 @@ #include "../include/namespace.h" #include "../include/extension.h" #include "../include/call.h" -#include "../include/fastcall.h" /** * Common header files for internal use only From fe19521099b620869c5dbbe64b2ddf9aeddcbe51 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sat, 26 Jul 2014 15:18:22 +0200 Subject: [PATCH 29/41] removed space from docblock --- phpcpp.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpcpp.h b/phpcpp.h index baa91cca..c3f33659 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 */ From 8e089ea9174ca133f938bdb16ef14e7a1027ccaf Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sat, 26 Jul 2014 15:21:33 +0200 Subject: [PATCH 30/41] compile issues --- include/call.h | 8 ++++---- zend/exists.cpp | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/call.h b/include/call.h index 80ef6c97..e1bb12d3 100644 --- a/include/call.h +++ b/include/call.h @@ -15,10 +15,10 @@ namespace Php { /** * List of functions that are available for use in PHP */ -bool class_exists(const char *classname, size_t size, bool autoload = true); -bool class_exists(const char *classname, bool autoload = true) { return class_exists(classname, strlen(classname), autoload); } -bool class_exists(const std::string &classname, bool autoload = true) { return class_exists(classname.c_str(), classname.size(), autoload); } -Value eval(const std::string &phpCode); +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); /** * Call a function in PHP diff --git a/zend/exists.cpp b/zend/exists.cpp index 6bab8ba9..b5bc64f9 100644 --- a/zend/exists.cpp +++ b/zend/exists.cpp @@ -44,10 +44,10 @@ bool class_exists(const char *classname, size_t len, bool autoload) std::unique_ptr 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, classname, len); + 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), name, len + 1, (void **) &ce)) return false; + 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); @@ -55,7 +55,7 @@ bool class_exists(const char *classname, size_t len, bool autoload) else { // no auto-load - if (SUCCESS != zend_lookup_class(str, len, &ce TSRMLS_CC) == SUCCESS) return false; + 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; From 4104abb21a40fa0057c488199769b24519aa1da7 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sat, 26 Jul 2014 15:31:23 +0200 Subject: [PATCH 31/41] autoload check had to be exactly the other way around --- zend/exists.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/zend/exists.cpp b/zend/exists.cpp index b5bc64f9..3add227a 100644 --- a/zend/exists.cpp +++ b/zend/exists.cpp @@ -34,6 +34,14 @@ bool class_exists(const char *classname, size_t len, bool autoload) // 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--; } @@ -52,14 +60,6 @@ bool class_exists(const char *classname, size_t len, bool autoload) // 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); } - else - { - // 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; - } } /** From 3f1d5ca94f721cf7feff0fc9cb05a5c1b6d39873 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sat, 26 Jul 2014 17:00:10 +0200 Subject: [PATCH 32/41] added Value::subclassOf(), and implemented Php::is_a() and Php::is_subclass_of() --- include/call.h | 7 +++ include/fastcall.h | 18 ------- include/value.h | 45 +++++++++++------ zend/value.cpp | 119 ++++++++++++++++++++++++++++++++++----------- 4 files changed, 129 insertions(+), 60 deletions(-) delete mode 100644 include/fastcall.h diff --git a/include/call.h b/include/call.h index e1bb12d3..dff815c1 100644 --- a/include/call.h +++ b/include/call.h @@ -19,6 +19,13 @@ extern bool class_exists(const char *classname, size_t size, bool autoload = tr 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.subclassOf(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 diff --git a/include/fastcall.h b/include/fastcall.h deleted file mode 100644 index 778a9b2c..00000000 --- a/include/fastcall.h +++ /dev/null @@ -1,18 +0,0 @@ -/** - * fastcall.h - * - * This file holds some PHP functions implementation in C directly. - * - */ - -namespace Php { - - inline bool is_a(const Value &obj, const std::string classname, bool allow_string = false) { - return obj.is(classname, allow_string); - } - inline bool is_subclass_of(const Value &obj, const std::string classname, bool allow_string = true) { - return obj.isSubClassOf(classname, allow_string); - } - -} - diff --git a/include/value.h b/include/value.h index b3c288c9..e6b8d69f 100644 --- a/include/value.h +++ b/include/value.h @@ -964,29 +964,39 @@ class Value : private HashParent // try casting it return dynamic_cast(base); } - + /** - * Checks if this object is of the class or has the class as one of its parents - * @param classname - * @param allow_string + * 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 */ - inline bool is(const std::string &classname, bool allow_string=false) const { - return isImpl(classname, allow_string, false); - } + 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); } /** - * Checks if this object has the class as one of its parents - * @param classname + * 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 */ - inline bool isSubClassOf(const std::string &classname, bool allow_string=true) const { - return isImpl(classname, allow_string, true); - } + bool subclassOf(const char *classname, size_t size, bool allowString = false) const; + bool subclassOf(const char *classname, bool allowString = false) const { return subclassOf(classname, strlen(classname), allowString); } + bool subclassOf(const std::string &classname, bool allowString = false) const { return subclassOf(classname.c_str(), classname.size(), allowString); } -private: - bool isImpl(const std::string &classname, bool allow_string, bool only_subclass) const; +private: /** * Call function with a number of parameters * @param argc Number of parameters @@ -1069,6 +1079,13 @@ class Value : private HashParent */ 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 */ diff --git a/zend/value.cpp b/zend/value.cpp index 9b409f78..9258dbd8 100644 --- a/zend/value.cpp +++ b/zend/value.cpp @@ -1408,42 +1408,105 @@ bool Value::isCallable() const return zend_is_callable(_val, 0, NULL TSRMLS_CC); } -bool Value::isImpl(const std::string &classname, bool allow_string, bool only_subclass) const { - /* - * allow_string - is default is false, isSubclassOf is true. - * if it's allowed, the the autoloader will be called if the class does not exist. - * default behaviour is different, as 'is' used to be used to test mixed return - * values and there is no easy way to deprecate this. - */ +/** + * 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(); - zend_class_entry *instance_ce; - zend_class_entry **ce; - - if (allow_string && isString()) { - zend_class_entry **the_ce; - if (zend_lookup_class(Z_STRVAL_P(_val), Z_STRLEN_P(_val), &the_ce TSRMLS_CC) == FAILURE) { - return false; - } - instance_ce = *the_ce; - } - else if (isObject() && HAS_CLASS_ENTRY(*_val)) { - instance_ce = Z_OBJCE_P(_val); + // the class-entry of 'this' + zend_class_entry *this_entry; + + // 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 { - return false; + 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 return; + + // found the entry + return *ce; } +} - if (zend_lookup_class_ex(classname.c_str(), (int32_t)classname.length(), NULL, 0, &ce TSRMLS_CC) == FAILURE) { - return false; - } +/** + * 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(); - if (only_subclass && instance_ce == *ce) { - return false; - } + // 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 + if (zend_lookup_class_ex(classname, size, NULL, 0, &ce TSRMLS_CC) == FAILURE) return false; + + // 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::subclassOf(const char *classname, size_t size, bool allowString) const +{ + // we need the tsrm_ls variable + TSRMLS_FETCH(); - return instanceof_function(instance_ce, *ce TSRMLS_CC); + // 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 + if (zend_lookup_class_ex(classname, size, NULL, 0, &ce TSRMLS_CC) == FAILURE) return false; + + // 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); } /** From fc24b07c6572c018a7311a2e6a4ee6da79661bcc Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sat, 26 Jul 2014 17:01:16 +0200 Subject: [PATCH 33/41] removed includes for fastcall.h --- phpcpp.h | 1 - zend/includes.h | 1 - 2 files changed, 2 deletions(-) diff --git a/phpcpp.h b/phpcpp.h index 8be1a251..baa91cca 100644 --- a/phpcpp.h +++ b/phpcpp.h @@ -60,7 +60,6 @@ #include #include #include -#include /** * Macro to export a function diff --git a/zend/includes.h b/zend/includes.h index 8af557cc..63b435eb 100644 --- a/zend/includes.h +++ b/zend/includes.h @@ -79,7 +79,6 @@ #include "../include/namespace.h" #include "../include/extension.h" #include "../include/call.h" -#include "../include/fastcall.h" /** * Common header files for internal use only From 5e8c9017107dd48150790ac7027d9cdc67d8b09c Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sat, 26 Jul 2014 17:02:59 +0200 Subject: [PATCH 34/41] fixed compile issues --- zend/value.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/zend/value.cpp b/zend/value.cpp index 9258dbd8..d48e08b3 100644 --- a/zend/value.cpp +++ b/zend/value.cpp @@ -1413,14 +1413,11 @@ bool Value::isCallable() const * @param allowString * @return zend_class_entry */ -zend_class_entry Value::classEntry(bool allowString) const +zend_class_entry *Value::classEntry(bool allowString) const { // we need the tsrm_ls variable TSRMLS_FETCH(); - // the class-entry of 'this' - zend_class_entry *this_entry; - // is this an object if (isObject()) { @@ -1439,7 +1436,7 @@ zend_class_entry Value::classEntry(bool allowString) const 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 return; + if (zend_lookup_class(Z_STRVAL_P(_val), Z_STRLEN_P(_val), &ce TSRMLS_CC) == FAILURE) return nullptr; // found the entry return *ce; From 5cf1b5d53db6ddeb4b8d371baef0d9c37c0fa231 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sun, 27 Jul 2014 16:30:17 +0200 Subject: [PATCH 35/41] renamed subclassOf() to derivedFrom() (added to pull request #110) --- include/call.h | 2 +- include/value.h | 6 +++--- zend/value.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/call.h b/include/call.h index dff815c1..1a0c59ff 100644 --- a/include/call.h +++ b/include/call.h @@ -22,7 +22,7 @@ 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.subclassOf(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); } diff --git a/include/value.h b/include/value.h index e6b8d69f..853be966 100644 --- a/include/value.h +++ b/include/value.h @@ -991,9 +991,9 @@ class Value : private HashParent * @param allowString Is it allowed for 'this' to be a string * @return bool */ - bool subclassOf(const char *classname, size_t size, bool allowString = false) const; - bool subclassOf(const char *classname, bool allowString = false) const { return subclassOf(classname, strlen(classname), allowString); } - bool subclassOf(const std::string &classname, bool allowString = false) const { return subclassOf(classname.c_str(), classname.size(), allowString); } + 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: diff --git a/zend/value.cpp b/zend/value.cpp index d48e08b3..61efe2fe 100644 --- a/zend/value.cpp +++ b/zend/value.cpp @@ -1484,7 +1484,7 @@ bool Value::instanceOf(const char *classname, size_t size, bool allowString) con * @param allowString Is it allowed for 'this' to be a string * @return bool */ -bool Value::subclassOf(const char *classname, size_t size, bool allowString) const +bool Value::derivedFrom(const char *classname, size_t size, bool allowString) const { // we need the tsrm_ls variable TSRMLS_FETCH(); From e46c2ec8eb8a8dc6e3750b8e3fdb764956b18e42 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sun, 27 Jul 2014 16:36:08 +0200 Subject: [PATCH 36/41] added comment --- include/value.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/value.h b/include/value.h index fe28e6d5..b0b5ed20 100644 --- a/include/value.h +++ b/include/value.h @@ -503,6 +503,7 @@ class Value : private HashParent // result variable std::map result; + // loop through the original map, and copy everything to the result for (auto &iter : map) result[iter.first] = iter.second; // done From 23579ad5a15377a4832163290b00ab23f504ba14 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sun, 27 Jul 2014 16:48:31 +0200 Subject: [PATCH 37/41] update coding style --- include/object.h | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/include/object.h b/include/object.h index 47935d70..6350259e 100644 --- a/include/object.h +++ b/include/object.h @@ -41,13 +41,21 @@ class Object : public Value */ Object(const Value &value) : Value() { - // string types are instantiated - if (value.isString()) { + // 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"); } - // otherwise copy the other object - else operator=(value); + else + { + // this simply copies the other object + operator=(value); + } } /** From eac464b8b4428864817edbedf1fb79f6dac0dd3b Mon Sep 17 00:00:00 2001 From: Toon Schoenmakers Date: Fri, 15 Aug 2014 17:17:05 +0200 Subject: [PATCH 38/41] Properly use numeric keys in case we're requesting a mapValue --- zend/value.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/zend/value.cpp b/zend/value.cpp index 61efe2fe..3f38498d 100644 --- a/zend/value.cpp +++ b/zend/value.cpp @@ -1710,7 +1710,12 @@ std::map Value::mapValue() const std::map result; // iterate over the object - for (auto &iter : *this) result[iter.first.rawValue()] = iter.second; + for (auto &iter : *this) + { + auto &key = iter.first; + if (key.isNumeric()) result[std::to_string(key.numericValue())] = iter.second; + else result[iter.first.rawValue()] = iter.second; + } // done return result; From cc106471bee34549d0db18a6d3797e791853fc6c Mon Sep 17 00:00:00 2001 From: Toon Schoenmakers Date: Tue, 19 Aug 2014 11:07:37 +0200 Subject: [PATCH 39/41] Use stringValue() rather than checking if it's a numeric value to then use std::to_string() --- zend/value.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/zend/value.cpp b/zend/value.cpp index 3f38498d..50c4b649 100644 --- a/zend/value.cpp +++ b/zend/value.cpp @@ -1708,15 +1708,10 @@ std::map Value::mapValue() const { // result variable std::map result; - + // iterate over the object - for (auto &iter : *this) - { - auto &key = iter.first; - if (key.isNumeric()) result[std::to_string(key.numericValue())] = iter.second; - else result[iter.first.rawValue()] = iter.second; - } - + for (auto &iter : *this) result[iter.first.stringValue()] = iter.second; + // done return result; } From 36ab68bd25aaedc81fdf6745faf5f3a14474c53f Mon Sep 17 00:00:00 2001 From: Toon Schoenmakers Date: Tue, 19 Aug 2014 14:20:11 +0200 Subject: [PATCH 40/41] Fixed the compile issues against php 5.3 --- zend/exists.cpp | 9 +++++++++ zend/value.cpp | 34 +++++++++++++++++++++++----------- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/zend/exists.cpp b/zend/exists.cpp index 3add227a..1177e47b 100644 --- a/zend/exists.cpp +++ b/zend/exists.cpp @@ -12,6 +12,15 @@ */ #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 */ diff --git a/zend/value.cpp b/zend/value.cpp index 50c4b649..69f7e2c5 100644 --- a/zend/value.cpp +++ b/zend/value.cpp @@ -1445,10 +1445,10 @@ zend_class_entry *Value::classEntry(bool allowString) const /** * 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 @@ -1462,23 +1462,29 @@ bool Value::instanceOf(const char *classname, size_t size, bool allowString) con // 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 @@ -1492,16 +1498,22 @@ bool Value::derivedFrom(const char *classname, size_t size, bool allowString) co // 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); } From 97bc6757346d394a4b7d5898983be298e0b0ea98 Mon Sep 17 00:00:00 2001 From: Toon Schoenmakers Date: Fri, 22 Aug 2014 15:23:05 +0200 Subject: [PATCH 41/41] Store the impl pointer for ClassImpl after the name in the zend_class_entry on php5.3 Turns out the apache reload issue from f57607d2d58f6e7689a3550c84ba68ce42c6a7b3 was never actually fixed. This commit however does finally fix it. The previously comment trick however is still used with php 5.4 and php 5.5 as this 'new' trick doesn't work with these versions of php as char* name in the zend_class_entry is a const char* and is no longer internally copied and all (meaning we can't realloc it). --- zend/classimpl.cpp | 65 +++++++++++++++++++++++----------------------- zend/classimpl.h | 6 ----- 2 files changed, 33 insertions(+), 38 deletions(-) diff --git a/zend/classimpl.cpp b/zend/classimpl.cpp index b2acc24a..9c28a0a3 100644 --- a/zend/classimpl.cpp +++ b/zend/classimpl.cpp @@ -20,11 +20,6 @@ ClassImpl::~ClassImpl() { // destruct the entries if (_entries) delete[] _entries; - - // php 5.3 deallocates the doc_comment by iself -#if PHP_VERSION_ID >= 50400 - if (_comment) free(_comment); -#endif } /** @@ -41,18 +36,21 @@ static ClassImpl *self(zend_class_entry *entry) // we need the base class (in user space the class may have been overridden, // but we are not interested in these user space classes) while (entry->parent) entry = entry->parent; - + #if PHP_VERSION_ID >= 50400 // retrieve the comment (it has a pointer hidden in it to the ClassBase object) const char *comment = entry->info.user.doc_comment; -#else - // retrieve the comment php5.3 style (it has a pointer hidden in it to the ClassBase object) - const char *comment = entry->doc_comment; -#endif - + // the first byte of the comment is an empty string (null character), but // the next bytes contain a pointer to the ClassBase class return *((ClassImpl **)(comment + 1)); +#else + // on php 5.3 we store the pointer to impl after the name in the entry + ClassImpl** impl = (ClassImpl**)(entry->name + 1 + entry->name_length); + + // return the actual implementation + return *impl; +#endif } /** @@ -1411,34 +1409,37 @@ void ClassImpl::initialize(ClassBase *base, const std::string &prefix TSRMLS_DC) // otherwise report an error else std::cerr << "Derived class " << name() << " is initialized before base class " << interface->name() << ": interface is ignored" << std::endl; } - + + // this pointer has to be copied to temporary pointer, as &this causes compiler error + ClassImpl *impl = this; + +#if PHP_VERSION_ID >= 50400 + // allocate doc comment to contain an empty string + a hidden pointer - if (!_comment) - { - // allocate now - _comment = (char *)malloc(1 + sizeof(ClassBase *)); - - // empty string on first position - _comment[0] = '\0'; - - // this pointer has to be copied to temporary pointer, as &this causes compiler error - ClassImpl *impl = this; - - // copy the 'this' pointer to the doc-comment - memcpy(_comment+1, &impl, sizeof(ClassImpl *)); - } - - // store pointer to the class in the unused doc_comment member -#if PHP_VERSION_ID >= 50400 + char *_comment = (char *)malloc(1 + sizeof(ClassImpl *)); + + // empty string on first position + _comment[0] = '\0'; + + // copy the 'this' pointer to the doc-comment + memcpy(_comment+1, &impl, sizeof(ClassImpl *)); + + // set our comment in the actual class entry _entry->info.user.doc_comment = _comment; + #else - // and store the wrapper inside the comment - _entry->doc_comment = _comment; + + // Reallocate some extra space in the name in the zend_class_entry so we can fit a pointer behind it + _entry->name = (char *) realloc(_entry->name, _entry->name_length + 1 + sizeof(ClassImpl *)); + + // Copy the pointer after it + memcpy(_entry->name + _entry->name_length + 1, &impl, sizeof(ClassImpl *)); + #endif // set access types flags for class _entry->ce_flags = (int)_type; - + // declare all member variables for (auto &member : _members) member->initialize(_entry TSRMLS_CC); } diff --git a/zend/classimpl.h b/zend/classimpl.h index bd631b8b..26cf0307 100644 --- a/zend/classimpl.h +++ b/zend/classimpl.h @@ -31,12 +31,6 @@ class ClassImpl */ 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