diff options
41 files changed, 1504 insertions, 729 deletions
@@ -1,5 +1,4 @@ *.o +*.a *.so *.txt -create_config -/include/config.h @@ -17,7 +17,7 @@ # installed in the default directory, you can change that here. # -PHP_CONFIG = php-config +PHP_CONFIG = php-config # @@ -29,7 +29,7 @@ PHP_CONFIG = php-config # Usually /usr/bin/php # -PHP_BIN = $(shell ${PHP_CONFIG} --php-binary) +PHP_BIN = $(shell ${PHP_CONFIG} --php-binary) # @@ -42,9 +42,9 @@ PHP_BIN = $(shell ${PHP_CONFIG} --php-binary) # and /usr/local/lib. You can of course change it to whatever suits you best # -INSTALL_PREFIX = /usr -INSTALL_HEADERS = ${INSTALL_PREFIX}/include -INSTALL_LIB = ${INSTALL_PREFIX}/lib +INSTALL_PREFIX = /usr +INSTALL_HEADERS = ${INSTALL_PREFIX}/include +INSTALL_LIB = ${INSTALL_PREFIX}/lib # @@ -55,8 +55,10 @@ INSTALL_LIB = ${INSTALL_PREFIX}/lib # you can change that here. # -PHP_LIBRARY = libphpcpp.so -HHVM_LIBRARY = libhhvmcpp.so +PHP_SHARED_LIBRARY = libphpcpp.so +PHP_STATIC_LIBRARY = libphpcpp.a +HHVM_SHARED_LIBRARY = libhhvmcpp.so +HHVM_STATIC_LIBRARY = libhhvmcpp.a # @@ -69,9 +71,19 @@ HHVM_LIBRARY = libhhvmcpp.so # library file. By default, g++ (the GNU C++ compiler) is used for both. # -COMPILER = g++ -LINKER = g++ +ifdef CXX + COMPILER = ${CXX} + LINKER = ${CXX} +else + COMPILER = g++ + LINKER = g++ +endif +ifdef AR + ARCHIVER = ${AR} rcs +else + ARCHIVER = ar rcs +endif # # Compiler flags @@ -85,9 +97,11 @@ LINKER = g++ # you want to leave that flag out on production servers). # -COMPILER_FLAGS = -Wall -c -g -std=c++11 -fpic -PHP_COMPILER_FLAGS = ${COMPILER_FLAGS} `php-config --includes` -HHVM_COMPILER_FLAGS = ${COMPILER_FLAGS} +COMPILER_FLAGS = -Wall -c -g -std=c++11 +SHARED_COMPILER_FLAGS = -fpic +STATIC_COMPILER_FLAGS = +PHP_COMPILER_FLAGS = ${COMPILER_FLAGS} `${PHP_CONFIG} --includes` +HHVM_COMPILER_FLAGS = ${COMPILER_FLAGS} # # Linker flags @@ -99,9 +113,9 @@ HHVM_COMPILER_FLAGS = ${COMPILER_FLAGS} # to the linker flags # -LINKER_FLAGS = -shared -PHP_LINKER_FLAGS = ${LINKER_FLAGS} `php-config --ldflags` -HHVM_LINKER_FLAGS = ${LINKER_FLAGS} +LINKER_FLAGS = -shared +PHP_LINKER_FLAGS = ${LINKER_FLAGS} `${PHP_CONFIG} --ldflags` +HHVM_LINKER_FLAGS = ${LINKER_FLAGS} # @@ -111,9 +125,9 @@ HHVM_LINKER_FLAGS = ${LINKER_FLAGS} # So you can probably leave this as it is # -RM = rm -f -CP = cp -f -MKDIR = mkdir -p +RM = rm -fr +CP = cp -f +MKDIR = mkdir -p # @@ -124,9 +138,9 @@ MKDIR = mkdir -p # probably necessary here # -COMMON_SOURCES = $(wildcard common/*.cpp) -PHP_SOURCES = $(wildcard zend/*.cpp) -HHVM_SOURCES = $(wildcard hhvm/*.cpp) +COMMON_SOURCES = $(wildcard common/*.cpp) +PHP_SOURCES = $(wildcard zend/*.cpp) +HHVM_SOURCES = $(wildcard hhvm/*.cpp) # # The object files @@ -137,9 +151,12 @@ HHVM_SOURCES = $(wildcard hhvm/*.cpp) # takes all source files. # -COMMON_OBJECTS = $(COMMON_SOURCES:%.cpp=%.o) -PHP_OBJECTS = $(PHP_SOURCES:%.cpp=%.o) -HHVM_OBJECTS = $(HHVM_SOURCES:%.cpp=%.o) +COMMON_SHARED_OBJECTS = $(COMMON_SOURCES:%.cpp=shared/%.o) +PHP_SHARED_OBJECTS = $(PHP_SOURCES:%.cpp=shared/%.o) +HHVM_SHARED_OBJECTS = $(HHVM_SOURCES:%.cpp=shared/%.o) +COMMON_STATIC_OBJECTS = $(COMMON_SOURCES:%.cpp=static/%.o) +PHP_STATIC_OBJECTS = $(PHP_SOURCES:%.cpp=static/%.o) +HHVM_STATIC_OBJECTS = $(HHVM_SOURCES:%.cpp=static/%.o) # @@ -147,39 +164,71 @@ HHVM_OBJECTS = $(HHVM_SOURCES:%.cpp=%.o) # dependencies that are used by the compiler. # -all: ${PHP_LIBRARY} +all: phpcpp -phpcpp: ${PHP_LIBRARY} - -hhvmcpp: ${HHVM_LIBRARY} +phpcpp: ${PHP_SHARED_LIBRARY} ${PHP_STATIC_LIBRARY} + @echo + @echo "Build complete." + @echo "Don't forget to run 'make test'." -${PHP_LIBRARY}: ${COMMON_OBJECTS} ${PHP_OBJECTS} - ${LINKER} ${PHP_LINKER_FLAGS} -o $@ ${COMMON_OBJECTS} ${PHP_OBJECTS} +hhvmcpp: ${HHVM_SHARED_LIBRARY} ${PHP_STATIC_LIBRARY} @echo @echo "Build complete." @echo "Don't forget to run 'make test'." -${HHVM_LIBRARY}: ${COMMON_OBJECTS} ${HHVM_OBJECTS} - ${LINKER} ${HHVM_LINKER_FLAGS} -o $@ ${COMMON_OBJECTS} ${HHVM_OBJECTS} +${PHP_SHARED_LIBRARY}: shared_directories ${COMMON_SHARED_OBJECTS} ${PHP_SHARED_OBJECTS} + ${LINKER} ${PHP_LINKER_FLAGS} -o $@ ${COMMON_SHARED_OBJECTS} ${PHP_SHARED_OBJECTS} + +${PHP_STATIC_LIBRARY}: static_directories ${COMMON_STATIC_OBJECTS} ${PHP_STATIC_OBJECTS} + ${ARCHIVER} $@ ${COMMON_STATIC_OBJECTS} ${PHP_STATIC_OBJECTS} + +${HHVM_SHARED_LIBRARY}: shared_directories ${COMMON_SHARED_OBJECTS} ${HHVM_SHARED_OBJECTS} + ${LINKER} ${HHVM_LINKER_FLAGS} -o $@ ${COMMON_SHARED_OBJECTS} ${HHVM_SHARED_OBJECTS} + +${HHVM_STATIC_LIBRARY}: static_directories ${COMMON_STATIC_OBJECTS} ${HHVM_STATIC_OBJECTS} + ${ARCHIVER} $@ ${COMMON_STATIC_OBJECTS} ${HHVM_STATIC_OBJECTS} + +shared_directories: + ${MKDIR} shared/common + ${MKDIR} shared/zend + ${MKDIR} shared/hhvm + +static_directories: + ${MKDIR} static/common + ${MKDIR} static/zend + ${MKDIR} static/hhvm clean: - ${RM} ${COMMON_OBJECTS} ${PHP_OBJECTS} ${HHVM_OBJECTS} ${PHP_LIBRARY} ${HHVM_LIBRARY} + ${RM} shared ${PHP_SHARED_LIBRARY} ${HHVM_SHARED_LIBRARY} + ${RM} static ${PHP_STATIC_LIBRARY} ${HHVM_STATIC_LIBRARY} + find -name *.o | xargs ${RM} + +${COMMON_SHARED_OBJECTS}: + ${COMPILER} ${COMPILER_FLAGS} ${SHARED_COMPILER_FLAGS} -o $@ ${@:shared/%.o=%.cpp} + +${COMMON_STATIC_OBJECTS}: + ${COMPILER} ${COMPILER_FLAGS} ${STATIC_COMPILER_FLAGS} -o $@ ${@:static/%.o=%.cpp} + +${PHP_SHARED_OBJECTS}: + ${COMPILER} ${PHP_COMPILER_FLAGS} ${SHARED_COMPILER_FLAGS} -o $@ ${@:shared/%.o=%.cpp} -${COMMON_OBJECTS}: - ${COMPILER} ${COMPILER_FLAGS} -o $@ ${@:%.o=%.cpp} +${PHP_STATIC_OBJECTS}: + ${COMPILER} ${PHP_COMPILER_FLAGS} ${STATIC_COMPILER_FLAGS} -o $@ ${@:static/%.o=%.cpp} -${PHP_OBJECTS}: - ${COMPILER} ${PHP_COMPILER_FLAGS} -o $@ ${@:%.o=%.cpp} +${HHVM_SHARED_OBJECTS}: + ${COMPILER} ${HHVM_COMPILER_FLAGS} ${SHARED_COMPILER_FLAGS} -o $@ ${@:shared/%.o=%.cpp} -${HHVM_OBJECTS}: - ${COMPILER} ${HHVM_COMPILER_FLAGS} -o $@ ${@:%.o=%.cpp} +${HHVM_STATIC_OBJECTS}: + ${COMPILER} ${HHVM_COMPILER_FLAGS} ${STATIC_COMPILER_FLAGS} -o $@ ${@:static/%.o=%.cpp} install: ${MKDIR} ${INSTALL_HEADERS}/phpcpp ${CP} phpcpp.h ${INSTALL_HEADERS} ${CP} include/*.h ${INSTALL_HEADERS}/phpcpp - if [ -e ${PHP_LIBRARY} ]; then ${CP} ${PHP_LIBRARY} ${INSTALL_LIB}; fi - if [ -e ${HHVM_LIBRARY} ]; then ${CP} ${HHVM_LIBRARY} ${INSTALL_LIB}; fi + if [ -e ${PHP_SHARED_LIBRARY} ]; then ${CP} ${PHP_SHARED_LIBRARY} ${INSTALL_LIB}; fi + if [ -e ${PHP_STATIC_LIBRARY} ]; then ${CP} ${PHP_STATIC_LIBRARY} ${INSTALL_LIB}; fi + if [ -e ${HHVM_SHARED_LIBRARY} ]; then ${CP} ${HHVM_SHARED_LIBRARY} ${INSTALL_LIB}; fi + if [ -e ${HHVM_STATIC_LIBRARY} ]; then ${CP} ${HHVM_STATIC_LIBRARY} ${INSTALL_LIB}; fi test: mkdir -p ./tests/include/zts/phpcpp @@ -12,7 +12,7 @@ C++, and the PHP-CPP library uses all the power offered by C++11 to convert the values from your functions to/and from PHP: ```c -Php::Value hello_word() +Php::Value hello_world() { return "hello world!"; } diff --git a/documentation/ten-reasons-for-using-php-cpp.html b/documentation/ten-reasons-for-using-php-cpp.html index 484f3fb..7d830d4 100644 --- a/documentation/ten-reasons-for-using-php-cpp.html +++ b/documentation/ten-reasons-for-using-php-cpp.html @@ -71,7 +71,7 @@ C++ is a proven language with a more than 40 year long history. C++ has an official open standard and is controlled by a C++ standards committee with members that have proven track records. Compilers have been developed by companies - like Microsoft, IBM, Intel, Apple and there there are several open source + like Microsoft, IBM, Intel, Apple and there are several open source compilers available (GNU, CLANG), so you can always switch to a faster or more stable alternative. The compiler vendors are constantly motivated to be better than their competitors and bring out new versions of their compilers diff --git a/include/array.h b/include/array.h index 4d6b6b1..d238862 100644 --- a/include/array.h +++ b/include/array.h @@ -38,7 +38,7 @@ public: * Move constructor from a value object * @param value */ - Array(Value &&value) : Value(std::move(value)) + Array(Value &&value) noexcept : Value(std::move(value)) { // type must be valid if (value.type() != Type::Array) throw FatalError("Moving a non-array to an array variable"); @@ -107,7 +107,7 @@ public: * @param value * @return Array */ - Array &operator=(Value &&value) + Array &operator=(Value &&value) noexcept { // skip self assignment if (this == &value) return *this; diff --git a/include/base.h b/include/base.h index 87c7483..f5ebed1 100644 --- a/include/base.h +++ b/include/base.h @@ -87,7 +87,7 @@ public: */ Value operator[](const char *name) const { - return Value(this)[name]; + return Value(this).get(name); } /** @@ -97,7 +97,7 @@ public: */ Value operator[](const std::string &name) const { - return Value(this)[name]; + return Value(this).get(name); } /** @@ -107,7 +107,7 @@ public: */ Value property(const char *name) const { - return Value(this)[name]; + return Value(this).get(name); } /** @@ -117,7 +117,7 @@ public: */ Value property(const std::string &name) const { - return Value(this)[name]; + return Value(this).get(name); } /** diff --git a/include/byref.h b/include/byref.h index e828660..ff60981 100644 --- a/include/byref.h +++ b/include/byref.h @@ -24,8 +24,8 @@ public: * @param type Argument type * @param required Is this argument required? */ - ByRef(const char *name, Type type, bool required = true) : Argument(name, type, required, true) {} - + ByRef(const char *name, Type type = Type::Null, bool required = true) : Argument(name, type, required, true) {} + /** * Constructor * @param name Name of the argument @@ -34,7 +34,7 @@ public: * @param required Is this argument required? */ ByRef(const char *name, const char *classname, bool nullable = false, bool required = true) : Argument(name, classname, nullable, required, true) {} - + /** * Copy constructor * @param argument @@ -45,8 +45,8 @@ public: * Move constructor * @param argument */ - ByRef(ByRef &&argument) : Argument(argument) {} - + ByRef(ByRef &&argument) noexcept : Argument(argument) {} + /** * Destructor */ diff --git a/include/byval.h b/include/byval.h index 40a458c..3606df7 100644 --- a/include/byval.h +++ b/include/byval.h @@ -24,8 +24,8 @@ public: * @param type Argument type * @param required Is this argument required? */ - ByVal(const char *name, Type type, bool required = true) : Argument(name, type, required, false) {} - + ByVal(const char *name, Type type = Type::Null, bool required = true) : Argument(name, type, required, false) {} + /** * Constructor * @param name Name of the argument @@ -34,7 +34,7 @@ public: * @param required Is this argument required? */ ByVal(const char *name, const char *classname, bool nullable = false, bool required = true) : Argument(name, classname, nullable, required, false) {} - + /** * Copy constructor * @param argument @@ -45,8 +45,8 @@ public: * Move constructor * @param argument */ - ByVal(ByVal &&argument) : Argument(argument) {} - + ByVal(ByVal &&argument) noexcept : Argument(argument) {} + /** * Destructor */ diff --git a/include/call.h b/include/call.h index 16de1fe..279cbac 100644 --- a/include/call.h +++ b/include/call.h @@ -13,6 +13,24 @@ namespace Php { /** + * List of functions that are available for use in PHP + */ +extern bool class_exists(const char *classname, size_t size, bool autoload = true); +inline bool class_exists(const char *classname, bool autoload = true) { return class_exists(classname, strlen(classname), autoload); } +inline bool class_exists(const std::string &classname, bool autoload = true) { return class_exists(classname.c_str(), classname.size(), autoload); } +extern Value eval(const std::string &phpCode); +extern Value include(const std::string &filename); +extern Value include_once(const std::string &filename); +inline bool is_a(const Value &obj, const char *classname, size_t size, bool allow_string = false) { return obj.instanceOf(classname, size, allow_string); } +inline bool is_a(const Value &obj, const char *classname, bool allow_string = false) { return is_a(obj, classname, strlen(classname), allow_string); } +inline bool is_a(const Value &obj, const std::string &classname, bool allow_string = false) { return is_a(obj, classname.c_str(), classname.size(), allow_string); } +inline bool is_subclass_of(const Value &obj, const char *classname, size_t size, bool allow_string = true) { return obj.derivedFrom(classname, size, allow_string); } +inline bool is_subclass_of(const Value &obj, const char *classname, bool allow_string = true) { return is_subclass_of(obj, classname, strlen(classname), allow_string); } +inline bool is_subclass_of(const Value &obj, const std::string &classname, bool allow_string = true) { return is_subclass_of(obj, classname.c_str(), classname.size(), allow_string); } +extern Value require(const std::string &filename); +extern Value require_once(const std::string &filename); + +/** * Call a function in PHP * @param name Name of the function to call * @param params Variable number of parameters diff --git a/include/class.h b/include/class.h index cffc7bd..9257213 100644 --- a/include/class.h +++ b/include/class.h @@ -48,7 +48,7 @@ public: * Move constructor * @param that */ - Class(Class<T> &&that) : ClassBase(std::move(that)) {} + Class(Class<T> &&that) noexcept : ClassBase(std::move(that)) {} /** * Destructor diff --git a/include/classbase.h b/include/classbase.h index 9f0bac3..1129298 100644 --- a/include/classbase.h +++ b/include/classbase.h @@ -85,7 +85,7 @@ public: * Move constructor * @param that */ - ClassBase(ClassBase &&that) : _impl(std::move(that._impl)) {} + ClassBase(ClassBase &&that) noexcept : _impl(std::move(that._impl)) {} /** * Destructor diff --git a/include/exception.h b/include/exception.h index aff0afc..94aaa85 100644 --- a/include/exception.h +++ b/include/exception.h @@ -52,7 +52,11 @@ public: * Overridden what method * @return const char * */ +#ifdef _NOEXCEPT + virtual const char *what() const _NOEXCEPT override +#else virtual const char *what() const noexcept override +#endif { return _message.c_str(); } diff --git a/include/file.h b/include/file.h new file mode 100644 index 0000000..7029bb6 --- /dev/null +++ b/include/file.h @@ -0,0 +1,106 @@ +/** + * File.h + * + * Extended script, a PHP source file name can be passed to a Php::File object + * to have it evaluated. + * + * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com> + * @copyright 2014 Copernica BV + */ + +/** + * Set up namespace + */ +namespace Php { + +/** + * Class definition + */ +class File +{ +public: + /** + * Constructor + * + * The constructor receives a filename as parameter. It uses the normal + * PHP include path resolve algorithms to find the location of the file. + * + * @param name the filename + * @param size size of the filename + */ + File(const char *name, size_t size); + + /** + * Alternative constructor with just a filename + * + * @param name the filename + */ + File(const char *name) : File(name, ::strlen(name)) {} + + /** + * Alternative constructor with a string object + * @param name the filename + */ + File(const std::string &name) : File(name.c_str(), name.size()) {} + + /** + * Alternative constructor with a Value object + * @param name the filename + */ + File(const Value &value) : File(value.stringValue()) {} + + /** + * Destructor + */ + virtual ~File(); + + /** + * Does the file exist? + * @return boolean + */ + bool exists(); + + /** + * Is this a valid file? + * @return boolean + */ + bool valid(); + + /** + * Execute the file once (do nothing if the file already was executed) + * @return Php::Value + */ + Value once(); + + /** + * Execute the file + * @return Php::Value + */ + Value execute(); + +private: + /** + * The full resolved path name + * @var const char * + */ + char *_path = nullptr; + + /** + * The opcodes of this file + * @var Opcodes + */ + Opcodes *_opcodes = nullptr; + + /** + * Compile the file + * @return bool + */ + bool compile(); + +}; + +/** + * End of namespace + */ +} + diff --git a/include/global.h b/include/global.h index ae54212..4593e5d 100644 --- a/include/global.h +++ b/include/global.h @@ -34,7 +34,7 @@ public: * Move constructor * @param global */ - Global(Global &&global) : Value(std::move(global)), _name(std::move(global._name)), _exists(global._exists) {} + Global(Global &&global) noexcept : Value(std::move(global)), _name(std::move(global._name)), _exists(global._exists) {} /** * Destructor @@ -70,7 +70,7 @@ public: * @return Global */ /* - Global &operator=(Global &&global) + Global &operator=(Global &&global) noexcept { // skip self assignment if (&global == this) return *this; diff --git a/include/hashmember.h b/include/hashmember.h index 6853228..b5b6c17 100644 --- a/include/hashmember.h +++ b/include/hashmember.h @@ -579,7 +579,7 @@ protected: * Move constructor * @param value Other element */ -// HashMember(HashMember<Type> &&member) : +// HashMember(HashMember<Type> &&member) noexcept : // _parent(std::move(member._parent)), _index(std::move(member._index)) {} private: diff --git a/include/hiddenpointer.h b/include/hiddenpointer.h index dc981c0..5465ebb 100644 --- a/include/hiddenpointer.h +++ b/include/hiddenpointer.h @@ -84,7 +84,7 @@ public: * Move constructor * @param that */ - HiddenPointer(HiddenPointer<Type> &&that) : _allocated(that._allocated), _buffer(that._buffer) + HiddenPointer(HiddenPointer<Type> &&that) noexcept : _allocated(that._allocated), _buffer(that._buffer) { // the other object is no longer allocated that._allocated = false; diff --git a/include/ini.h b/include/ini.h index 1d881e9..69137a2 100644 --- a/include/ini.h +++ b/include/ini.h @@ -125,7 +125,11 @@ private: * @param value * @return string */ +#ifdef _MSC_VER + static const char* bool2str(const bool value) +#else static constexpr const char* bool2str(const bool value) +#endif { // cast to a string return ( static_cast<bool>(value) ? "On" : "Off"); diff --git a/include/object.h b/include/object.h index 2e6628a..2e92f6a 100644 --- a/include/object.h +++ b/include/object.h @@ -25,28 +25,11 @@ public: Object() : Value(Type::Object) {} /** - * Move constructor is passed to the parent - * @param value - */ - Object(Value &&value) : Value(std::move(value)) - { - // throw exception in case of problems - if (value.type() != Type::Object) throw FatalError("Constructing an object variable by moving a non object"); - } - - /** * Copy constructor is valid if the passed in object is also an object, * or when it is a string holding a classname * @param that An other object */ - Object(const Value &value) : Value() - { - // string types are instantiated - if (value.isString()) instantiate(value); - - // otherwise copy the other object - else operator=(value); - } + Object(const Value &value); /** * Constructor to create a new instance of a builtin class @@ -88,28 +71,10 @@ public: * number of parameters that are passed to the constructor * * @param name Name of the class to instantiate - * @param arg0 Optional argument 1 - * @param arg1 Optional argument 2 - * @param arg2 Optional argument 3 - * @param arg3 Optional argument 4 - * @param arg4 Optional argument 5 - * @param arg5 Optional argument 6 - * @param arg6 Optional argument 7 - * @param arg7 Optional argument 8 - * @param arg8 Optional argument 9 - * @param arg9 Optional argument 10 + * @param args Optional arguments */ - Object(const char *name) { instantiate(name); call("__construct"); } - Object(const char *name, Value p0) { instantiate(name); call("__construct", p0); } - Object(const char *name, Value p0, Value p1) { instantiate(name); call("__construct", p0, p1); } - Object(const char *name, Value p0, Value p1, Value p2) { instantiate(name); call("__construct", p0, p1, p2); } - Object(const char *name, Value p0, Value p1, Value p2, Value p3) { instantiate(name); call("__construct", p0, p1, p2, p3); } - Object(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4) { instantiate(name); call("__construct", p0, p1, p2, p3, p4); } - Object(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5) { instantiate(name); call("__construct", p0, p1, p2, p3, p4, p5); } - Object(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6) { instantiate(name); call("__construct", p0, p1, p2, p3, p4, p5, p6); } - Object(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7) { instantiate(name); call("__construct", p0, p1, p2, p3, p4, p5, p6, p7); } - Object(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7, Value p8) { instantiate(name); call("__construct", p0, p1, p2, p3, p4, p5, p6, p7, p8); } - Object(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7, Value p8, Value p9) { instantiate(name); call("__construct", p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); } + template <typename ...Args> + Object(const char *name, Args&&... args) : Value() { if (instantiate(name)) call("__construct", std::forward<Value>(args)...); } /** * Destructor @@ -154,7 +119,7 @@ public: * @param value * @return ForcedValue */ - Object &operator=(Value &&value) + Object &operator=(Value &&value) noexcept { // skip self assignment if (this == &value) return *this; @@ -172,10 +137,14 @@ public: private: /** * Helper method to instantiate an object + * + * This method returns true if there is a __construct() function, and + * false otherwise + * * @param name Class name + * @return bool */ - void instantiate(const char *name); - + bool instantiate(const char *name); }; /** diff --git a/include/opcodes.h b/include/opcodes.h new file mode 100644 index 0000000..5a40d05 --- /dev/null +++ b/include/opcodes.h @@ -0,0 +1,69 @@ +/** + * Opcodes.h + * + * Class represents a set of opcodes of a PHP script that can be executed. This + * is an internal file that you normally do not have to instantiate yourself. + * Better use the Php::Script of Php::File classes. + * + * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com> + * @copyright 2014 Copernica BV + */ + +/** + * Forward declarations + */ +struct _zend_op_array; + +/** + * Namespace + */ +namespace Php { + +/** + * Class definition + */ +class Opcodes +{ +public: + /** + * Constructor + * @param opcodes + */ + Opcodes(struct _zend_op_array *opcodes) : _opcodes(opcodes) + { + // no other initialisation is necessary + } + + /** + * Destructor + */ + virtual ~Opcodes(); + + /** + * Are the opcodes valid? + * @return bool + */ + bool valid() const + { + return _opcodes != nullptr; + } + + /** + * Execute the opcodes + * @return Value + */ + Value execute() const; + +private: + /** + * The opcodes + * @var zend_op_array + */ + struct _zend_op_array *_opcodes; +}; + +/** + * End of namespace + */ +} + diff --git a/include/script.h b/include/script.h new file mode 100644 index 0000000..a9c2921 --- /dev/null +++ b/include/script.h @@ -0,0 +1,107 @@ +/** + * Script.h + * + * Class that can be used to evaluate a PHP script in the current PHP context. + * + * The difference between directly calling eval() is that the script object + * will first evaluate the string, and then it can be executed multiple times. + * + * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com> + * @copyright 2014 Copernica BV + */ + +/** + * Forward declarations + */ +struct _zend_op_array; + +/** + * Set up namespace + */ +namespace Php { + +/** + * Class definition + */ +class Script +{ +public: + /** + * Constructor + * + * The constructor will not throw any exceptions, even when invalid + * PHP code is passed to it that can not be evaluated. You should call + * the valid() to find out if the script was valid (could be parsed). + * + * @param name Name of the PHP script + * @param source PHP source code to be evaluated + * @param size Length of the source code + */ + Script(const char *name, const char *source, size_t size) noexcept; + + /** + * Alternative constructor without a size + * @param name Name of the PHP script + * @param source PHP source code to be evaluated + */ + Script(const char *name, const char *source) noexcept : Script(name, source, ::strlen(source)) {} + + /** + * Alternative constructor without a name + * @param source PHP source code to be evaluated + * @param size Length of the source code + */ + Script(const char *source, size_t size) noexcept : Script("Unknown", source, size) {} + + /** + * Alternative constructor without a name and without a size + * @param source PHP source code to be evaluated + */ + Script(const char *source) noexcept : Script("Unknown", source, ::strlen(source)) {} + + /** + * Destructor + */ + virtual ~Script() {} + + /** + * Is the script a valid PHP script without syntax errors? + * @return bool + */ + bool valid() const + { + return _opcodes.valid(); + } + + /** + * Execute the script + * The return value of the script is returned + * @return Value + */ + Value execute() const + { + return _opcodes.execute(); + } + +private: + /** + * The opcodes + * @var Opcodes + */ + Opcodes _opcodes; + + /** + * Helper function to compile the source code + * @param name name of the script + * @param script actual PHP code + * @param size length of the string + * @return opcodes + */ + static struct _zend_op_array *compile(const char *name, const char *phpcode, size_t size); + +}; + +/** + * End of namespace + */ +} diff --git a/include/type.h b/include/type.h index bec0fd5..d248455 100644 --- a/include/type.h +++ b/include/type.h @@ -18,7 +18,7 @@ namespace Php { * The values are the same as the ones used internally in Zend */ enum class Type : unsigned char { - Null = 0, + Null = 0, // Null will allow any type Numeric = 1, Float = 2, Bool = 3, diff --git a/include/value.h b/include/value.h index f72304c..363779d 100644 --- a/include/value.h +++ b/include/value.h @@ -100,7 +100,7 @@ public: * @param value */ template <typename T> - Value(const std::map<std::string,T> &value) + Value(const std::map<std::string,T> &value) : Value(Type::Array) { // set all elements for (auto &iter : value) setRaw(iter.first.c_str(), iter.first.size(), iter.second); @@ -110,7 +110,6 @@ public: * Wrap object around zval * @param zval Zval to wrap * @param ref Force this to be a reference - * @param tsrm_ls Optional pointer to thread safe data */ Value(struct _zval_struct *zval, bool ref=false); @@ -130,7 +129,7 @@ public: * Move constructor * @param value */ - Value(Value &&that); + Value(Value &&that) noexcept; /** * Destructor @@ -142,7 +141,7 @@ public: * @param value * @return Value */ - Value &operator=(Value &&value); + Value &operator=(Value &&value) noexcept; /** * Assignment operator for various types @@ -406,7 +405,7 @@ public: char *reserve(size_t size); /** - * Get access to the raw buffer for read operationrs. + * Get access to the raw buffer for read operations. * @return const char * */ const char *rawValue() const; @@ -452,35 +451,64 @@ public: template <typename T> std::vector<T> vectorValue() const { - - // only works for arrays, other types return an empty vector if (!isArray()) return std::vector<T>(); // allocate a result std::vector<T> result; - + // reserve enough space size_t count = size(); result.reserve(count); - + // and fill the result vector - for (size_t i = 0; i<count; i++) + for (size_t i = 0; i<count; i++) { // check if the index exists if (!contains(i)) continue; - - // get the value object - Value value(get(i)); - - // add it to the vector - result.push_back(value); + + // get the value and add it to the vector + result.push_back(get(i)); } - + // done return result; } - + + /** + * Convert the object to a set + * + * This only works for regular arrays that are indexed by a number, start + * with position 0 and have no empty spaces. + * + * return std::vector + */ + template <typename T> + std::set<T> setValue() const + { + // only works for arrays, other types return an empty set + if (!isArray()) return std::set<T>(); + + // allocate a result + std::set<T> result; + + // how many elements are we inserting + size_t count = size(); + + // and fill the result set + for (size_t i = 0; i<count; i++) + { + // check if the index exists + if (!contains(i)) continue; + + // get the value and add it to the vector + result.insert(get(i)); + } + + // done + return result; + } + /** * Convert the object to a map with string index and Php::Value value * @return std::map @@ -497,12 +525,20 @@ public: // must be an array or an object, otherwise the map is empty if (!isArray() && !isObject()) return std::map<std::string,T>(); - // get the original map value - std::map<std::string,Php::Value> map(mapValue()); - // result variable std::map<std::string,T> result; + // iterate over the values + iterate([&result](const Value &key, const Value &value) { + + // first convert the value to the appropriate type (otherwise + // compiler errors occur) + T val = value; + + // add the value to the array + result[key] = val; + }); + // done return result; } @@ -618,7 +654,7 @@ public: /** * Cast to a number - * @return uint64_t + * @return int64_t */ operator int64_t () const { @@ -642,7 +678,7 @@ public: { return stringValue(); } - + /** * Cast to byte array * @return const char * @@ -672,6 +708,16 @@ public: } /** + * Convert the object to a set + * @return std::set + */ + template <typename T> + operator std::set<T>() const + { + return setValue<T>(); + } + + /** * Convert the object to a map with string index and Php::Value value * @return std::map */ @@ -898,24 +944,32 @@ public: return get(key.stringValue()); } - /** * Call the function in PHP - * We have ten variants of this function, depending on the number of parameters * This call operator is only useful when the variable represents a callable * @return Value */ Value operator()() const; - Value operator()(Value p0) const; - Value operator()(Value p0, Value p1) const; - Value operator()(Value p0, Value p1, Value p2) const; - Value operator()(Value p0, Value p1, Value p2, Value p3) const; - Value operator()(Value p0, Value p1, Value p2, Value p3, Value p4) const; - Value operator()(Value p0, Value p1, Value p2, Value p3, Value p4, Value p5) const; - Value operator()(Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6) const; - Value operator()(Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7) const; - Value operator()(Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7, Value p8) const; - Value operator()(Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7, Value p8, Value p9) const; + + /** + * Call the function - if the variable holds a callable thing + * @param args Optional arguments + * @return Value + */ + template <typename ...Args> + Value operator()(Args&&... args) const + { + // store arguments + Value vargs[] = { static_cast<Value>(args)... }; + //Value vargs[] = { std::forward<Value>(args)... }; + + // array of parameters + _zval_struct **params[sizeof...(Args)]; + for(unsigned i=0; i < sizeof...(Args); i++) {params[i] = &vargs[i]._val;} + + // call the function + return exec(sizeof...(Args), params); + } /** * Call a method @@ -925,16 +979,27 @@ public: * @return Value */ Value call(const char *name); - Value call(const char *name, Value p0); - Value call(const char *name, Value p0, Value p1); - Value call(const char *name, Value p0, Value p1, Value p2); - Value call(const char *name, Value p0, Value p1, Value p2, Value p3); - Value call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4); - Value call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5); - Value call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6); - Value call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7); - Value call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7, Value p8); - Value call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7, Value p8, Value p9); + + /** + * + * Call the method - if the variable holds an object with the given method + * @param name name of the method to call + * @param p0 The first parameter + * @return Value + */ + template <typename ...Args> + Value call(const char *name, Args&&... args) + { + // store arguments + Value vargs[] = { static_cast<Value>(args)... }; + + // array of parameters + _zval_struct **params[sizeof...(Args)]; + for(unsigned i=0; i < sizeof...(Args); i++) {params[i] = &vargs[i]._val;} + + // call the function + return exec(name, sizeof...(Args), params); + } /** * Retrieve the original implementation @@ -964,9 +1029,46 @@ public: // try casting it return dynamic_cast<T*>(base); } + + /** + * Check whether this object is an instance of a certain class + * + * If you set the parameter 'allowString' to true, and the Value object + * holds a string, the string will be treated as class name. + * + * @param classname The class of which this should be an instance + * @param size Length of the classname string + * @param allowString Is it allowed for 'this' to be a string + * @return bool + */ + bool instanceOf(const char *classname, size_t size, bool allowString = false) const; + bool instanceOf(const char *classname, bool allowString = false) const { return instanceOf(classname, strlen(classname), allowString); } + bool instanceOf(const std::string &classname, bool allowString = false) const { return instanceOf(classname.c_str(), classname.size(), allowString); } + + /** + * Check whether this object is derived from a certain class. + * + * If you set the parameter 'allowString' to true, and the Value object + * holds a string, the string will be treated as class name. + * + * @param classname The class of which this should be an instance + * @param size Length of the classname string + * @param allowString Is it allowed for 'this' to be a string + * @return bool + */ + bool derivedFrom(const char *classname, size_t size, bool allowString = false) const; + bool derivedFrom(const char *classname, bool allowString = false) const { return derivedFrom(classname, strlen(classname), allowString); } + bool derivedFrom(const std::string &classname, bool allowString = false) const { return derivedFrom(classname.c_str(), classname.size(), allowString); } + private: /** + * Iterate over key value pairs + * @param callback + */ + void iterate(const std::function<void(const Php::Value &,const Php::Value &)> &callback) const; + + /** * Call function with a number of parameters * @param argc Number of parameters * @param argv The parameters @@ -987,7 +1089,7 @@ private: * Refcount - the number of references to the value * @return int */ - int refcount(); + int refcount() const; protected: /** @@ -1049,6 +1151,13 @@ protected: iterator createIterator(bool begin) const; /** + * Retrieve the class entry + * @param allowString Allow the 'this' object to be a string + * @return zend_class_entry + */ + struct _zend_class_entry *classEntry(bool allowString = true) const; + + /** * The Globals and Member classes can access the zval directly */ friend class Globals; @@ -1061,6 +1170,7 @@ protected: friend class HashMember<int>; friend class HashMember<std::string>; friend class Callable; + friend class Script; }; /** @@ -2,7 +2,7 @@ * phpcpp.h * * Library to build PHP extensions with CPP - * + * * @copyright 2013 CopernicA BV * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com> */ @@ -22,6 +22,7 @@ #include <exception> #include <map> #include <set> +#include <functional> /** * Include all headers files that are related to this library @@ -60,6 +61,9 @@ #include <phpcpp/namespace.h> #include <phpcpp/extension.h> #include <phpcpp/call.h> +#include <phpcpp/opcodes.h> +#include <phpcpp/script.h> +#include <phpcpp/file.h> /** * Macro to export a function diff --git a/tests/cpp/include/doubl2str.h b/tests/cpp/include/doubl2str.h index 1a5e3fc..66f2b86 100644 --- a/tests/cpp/include/doubl2str.h +++ b/tests/cpp/include/doubl2str.h @@ -4,99 +4,13 @@ * */ - - -char num2char(unsigned int num) +#include <sstream> +#include <iomanip> +std::string double2str(long double d) { - switch(num) - { - case 0: - return '0'; - case 1: - return '1'; - case 2: - return '2'; - case 3: - return '3'; - case 4: - return '4'; - case 5: - return '5'; - case 6: - return '6'; - case 7: - return '7'; - case 8: - return '8'; - case 9: - return '9'; - } - - //return '\0'; - return '-'; -} - -std::string double2str(long double D) -{ - int sign = (D > 0) ? 1 : (D*=-1.0, -1); - unsigned long long int Ceil = D; - - // if D is ceil - if(Ceil == D) - { - return std::to_string( (long long)(D*sign) ); - } - - // size of result buffer - const int bs = 32; - - // size of temporary buffer - const int pw = 16; - // Temporary buffer - char buf[pw]; - // Result buffer - char rez[bs]; - - int i, size = 0; - // set sign - if(sign < 0) rez[size++] = '-'; - - // set ceil - std::string sceil = std::to_string(Ceil); - const char * bceil = sceil.c_str(); - int sceillen = sceil.size(); - for(i = 0; i < sceillen; i++) - { - rez[size++] = bceil[i]; - } - - // set point - rez[size++] = '.'; - - unsigned long long int I = D * 10000000000000000; // D * 10**pw - // .14159265359 -> 14159265359000000 - I -= Ceil * 10000000000000000; - - // Remove the tail of zeros - // 14159265359000000 -> 14159265359 - while(0 == I % 10) I /= 10; - - int ind = 0; - while(I > 0) - { - buf[ind++] = num2char(I%10); - I = (I - I%10) / 10; - } - - // set fraction part - for(i = 0; i < ind; i++) - { - rez[size] = buf[ind-i-1]; - size++; - } - - return std::string(rez, size); - //rez[size] = '\0'; + std::ostringstream strs; + strs << std::setprecision(16) << d; + return strs.str(); } diff --git a/zend/callable.cpp b/zend/callable.cpp index a85ab72..f0dc3e8 100644 --- a/zend/callable.cpp +++ b/zend/callable.cpp @@ -51,14 +51,14 @@ void Callable::invoke(INTERNAL_FUNCTION_PARAMETERS) { // get the result Value result(callable->invoke(params)); - - // detach the zval (we don't want it to be destructed) - zval *val = result.detach(); - + + // we're ready if the return value is not even used + if (!return_value_used) return; + // @todo php 5.6 has a RETVAL_ZVAL_FAST macro that can be used instead (and is faster) - + // return a full copy of the zval, and do not destruct it - RETVAL_ZVAL(val, 1, 0); + RETVAL_ZVAL(result._val, 1, 0); } catch (Exception &exception) { diff --git a/zend/callable.h b/zend/callable.h index 9958a2a..bd74f27 100644 --- a/zend/callable.h +++ b/zend/callable.h @@ -144,7 +144,7 @@ protected: { // fill members info->name = arg.name(); - info->name_len = ::strlen(arg.name()); + info->name_len = ::strlen(arg.name()); #if PHP_VERSION_ID >= 50400 diff --git a/zend/classimpl.cpp b/zend/classimpl.cpp index b2acc24..e42605e 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 } /** @@ -536,7 +534,7 @@ zend_object_value ClassImpl::cloneObject(zval *val TSRMLS_DC) result.handlers = impl->objectHandlers(); // store the object - ObjectImpl *new_object = new ObjectImpl(entry, cpp TSRMLS_CC); + ObjectImpl *new_object = new ObjectImpl(entry, cpp, 1 TSRMLS_CC); // store the object in the object cache result.handle = new_object->handle(); @@ -1197,7 +1195,7 @@ zend_object_value ClassImpl::createObject(zend_class_entry *entry TSRMLS_DC) result.handlers = impl->objectHandlers(); // create the object in the zend engine - ObjectImpl *object = new ObjectImpl(entry, cpp TSRMLS_CC); + ObjectImpl *object = new ObjectImpl(entry, cpp, 1 TSRMLS_CC); // store the object in the object cache result.handle = object->handle(); @@ -1411,34 +1409,37 @@ void ClassImpl::initialize(ClassBase *base, const std::string &prefix TSRMLS_DC) // otherwise report an error else std::cerr << "Derived class " << name() << " is initialized before base class " << interface->name() << ": interface is ignored" << std::endl; } - + + // this pointer has to be copied to temporary pointer, as &this causes compiler error + ClassImpl *impl = this; + +#if PHP_VERSION_ID >= 50400 + // allocate doc comment to contain an empty string + a hidden pointer - if (!_comment) - { - // allocate now - _comment = (char *)malloc(1 + sizeof(ClassBase *)); - - // empty string on first position - _comment[0] = '\0'; - - // this pointer has to be copied to temporary pointer, as &this causes compiler error - ClassImpl *impl = this; - - // copy the 'this' pointer to the doc-comment - memcpy(_comment+1, &impl, sizeof(ClassImpl *)); - } - - // store pointer to the class in the unused doc_comment member -#if PHP_VERSION_ID >= 50400 + char *_comment = (char *)malloc(1 + sizeof(ClassImpl *)); + + // empty string on first position + _comment[0] = '\0'; + + // copy the 'this' pointer to the doc-comment + memcpy(_comment+1, &impl, sizeof(ClassImpl *)); + + // set our comment in the actual class entry _entry->info.user.doc_comment = _comment; + #else - // and store the wrapper inside the comment - _entry->doc_comment = _comment; + + // Reallocate some extra space in the name in the zend_class_entry so we can fit a pointer behind it + _entry->name = (char *) realloc(_entry->name, _entry->name_length + 1 + sizeof(ClassImpl *)); + + // Copy the pointer after it + memcpy(_entry->name + _entry->name_length + 1, &impl, sizeof(ClassImpl *)); + #endif // set access types flags for class _entry->ce_flags = (int)_type; - + // declare all member variables for (auto &member : _members) member->initialize(_entry TSRMLS_CC); } diff --git a/zend/classimpl.h b/zend/classimpl.h index bd631b8..26cf030 100644 --- a/zend/classimpl.h +++ b/zend/classimpl.h @@ -32,12 +32,6 @@ private: std::string _name; /** - * The comment for reflexion, with a stored pointer to ourselves - * @var char* - */ - char *_comment = nullptr; - - /** * The class type (this can be values like Php::Abstract and Php::Final) * @var ClassType */ diff --git a/zend/compileroptions.h b/zend/compileroptions.h new file mode 100644 index 0000000..798cb59 --- /dev/null +++ b/zend/compileroptions.h @@ -0,0 +1,57 @@ +/** + * CompilerOptions.h + * + * Helper class to temporarily set compiler options + * + * When an object is destructed, it automatically restored the previous compiler settings + * + * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com> + * @copyright 2014 Copernica BV + */ + +/** + * Open PHP namespace + */ +namespace Php { + +/** + * Class definition + */ +class CompilerOptions +{ +private: + /** + * The original compiler options + * @var int + */ + zend_uint _original; + +public: + /** + * Constructor + * @param options + */ + CompilerOptions(zend_uint options) + { + // remember the old compiler options before we set temporary compile options + _original = CG(compiler_options); + + // we're going to evaluate only once + CG(compiler_options) = options; + } + + /** + * Destructor + */ + virtual ~CompilerOptions() + { + // restore original options + CG(compiler_options) = _original; + } +}; + +/** + * End of namespace + */ +} + diff --git a/zend/eval.cpp b/zend/eval.cpp new file mode 100644 index 0000000..674d39d --- /dev/null +++ b/zend/eval.cpp @@ -0,0 +1,145 @@ +/** + * Eval.cpp + * + * This file holds the implementation for the Php::eval() function + * + * @author andot <https://github.com/andot> + */ + +/** + * Dependencies + */ +#include "includes.h" + +/** + * Open PHP namespace + */ +namespace Php { + +/** + * Evaluate a PHP string + * @param phpCode The PHP code to evaluate + * @return Value The result of the evaluation + */ +Value eval(const std::string &phpCode) +{ + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + // the current exception + zval* oldException = EG(exception); + + // the return va + zval *retval = nullptr; + MAKE_STD_ZVAL(retval); + + // evaluate the string + if (zend_eval_stringl_ex((char *)phpCode.c_str(), (int32_t)phpCode.length(), retval, (char *)"", 1 TSRMLS_CC) != SUCCESS) + { + // Do we want to throw an exception here? The original author of this code + // did, but there are some reasons not to: + // + // 1. the PHP eval() function also does not throw exceptions. + // + // 2. the zend_eval_string() function already triggers a + // 'PHP parse error' when an error occurs, which also has + // to be handled. If we also throw an exception here, the + // user will have to write two error checks: for the error + // and the exception. + // + // if we _do_ want to throw an exception, we will first have to + // prevent the original zend_error to occur, and then turn it + // into an exception. An exception would be nicer from a C++ + // point of view, but because of the extra complexity, we do not + // this for now. + return nullptr; + } + else + { + // was an exception thrown inside the eval()'ed code? In that case we + // throw a C++ new exception to give the C++ code the chance to catch it + if (oldException != EG(exception) && EG(exception)) throw OrigException(EG(exception) TSRMLS_CC); + + // wrap the return value in a value object + Value result(retval); + + // the retval should now have two references: the value object and the + // retval itselves, so we can remove one of it (the zval_ptr_dtor only + // decrements refcount and won't destruct anything because there still + // is one reference left inside the Value object) + zval_ptr_dtor(&retval); + + // done + return result; + } +} + +/** + * Include a file + * @param filename + * @return Value + */ +Value include(const std::string &filename) +{ + // we can simply execute a file + return File(filename).execute(); +} + +/** + * Include a file only once + * @param filename + * @return Value + */ +Value include_once(const std::string &filename) +{ + // we can simply execute a file + return File(filename).execute(); +} + +/** + * Require a file + * This causes a fatal error if the file does not exist + * @param filename + * @return Value + */ +Value require(const std::string &filename) +{ + // create the file + File file(filename); + + // execute if it exists + if (file.exists()) return file.execute(); + + // trigger fatal error + error << filename << " does not exist" << std::flush; + + // unreachable + return nullptr; +} + +/** + * Require a file only once + * This causes a fatal error if the file does not exist + * @param filename + * @return Value + */ +Value require_once(const std::string &filename) +{ + // create the file + File file(filename); + + // execute if it exists + if (file.exists()) return file.once(); + + // trigger fatal error + error << filename << " does not exist" << std::flush; + + // unreachable + return nullptr; +} + + +/** + * End of namespace + */ +} diff --git a/zend/exists.cpp b/zend/exists.cpp new file mode 100644 index 0000000..1177e47 --- /dev/null +++ b/zend/exists.cpp @@ -0,0 +1,77 @@ +/** + * Exists.cpp + * + * This file holds the implementation of all *_exists() functions, + * like class_exists(), et cetera + * + * @author andot <https://github.com/andot> + */ + +/** + * Dependencies + */ +#include "includes.h" + +/** + * On php 5.3 ZEND_ACC_TRAIT isn't defined, so we simply define it to 0 + * so all operations with it are basically no-ops. Currently unconfirmed + * if this actually works correctly on php 5.3, but it at least compiles. + */ +#ifndef ZEND_ACC_TRAIT +#define ZEND_ACC_TRAIT 0 +#endif + +/** + * Open the PHP namespace + */ +namespace Php { + +/** + * Check whether a class with a certain name exists + * @param classname + * @param len + * @param autoload + * @return bool + */ +bool class_exists(const char *classname, size_t len, bool autoload) +{ + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + // we're going to load a class-entry + zend_class_entry **ce; + + // should we autoload the class? + if (autoload) + { + // no auto-load + if (SUCCESS != zend_lookup_class(classname, len, &ce TSRMLS_CC)) return false; + + // the found "class" could also be an interface or trait, which we do no want + return ((*ce)->ce_flags & (ZEND_ACC_INTERFACE | (ZEND_ACC_TRAIT - ZEND_ACC_EXPLICIT_ABSTRACT_CLASS))) == 0; + } + else + { + // starting slashes can be ignored + if (len > 0 && classname[0] == '\\') { classname++; len--; } + + // all classes are in lowercase in the hash, so we make + // a temporary buffer for storing the lowercase class name + // (is this smart? memory allocation is expensive!) + std::unique_ptr<char[]> lc_name(new char[len + 1]); + + // copy the name to lowercase, but ignore the starting slash (if there is one) + zend_str_tolower_copy(lc_name.get(), classname, len); + + // see if there is a class with this name + if (SUCCESS != zend_hash_find(EG(class_table), lc_name.get(), len + 1, (void **) &ce)) return false; + + // the found "class" could also be an interface or trait, which we do no want + return !(((*ce)->ce_flags & (ZEND_ACC_INTERFACE | ZEND_ACC_TRAIT)) > ZEND_ACC_EXPLICIT_ABSTRACT_CLASS); + } +} + +/** + * End of namespace + */ +} diff --git a/zend/file.cpp b/zend/file.cpp new file mode 100644 index 0000000..43c2d7a --- /dev/null +++ b/zend/file.cpp @@ -0,0 +1,160 @@ +/** + * File.cpp + * + * Implementation file for the File class + * + * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com> + * @copyright 2014 Copernica BV + */ + +/** + * Dependencies + */ +#include "includes.h" + +/** + * Namespace + */ +namespace Php { + +/** + * Constructor + * + * The constructor receives a filename as parameter. It uses the normal + * PHP include path resolve algorithms to find the location of the file. + * + * @param name the filename + * @param size length of the filename + */ +File::File(const char *name, size_t size) +{ + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + // resolve the path + _path = zend_resolve_path(name, size TSRMLS_CC); + + // the resolve-path function sometimes returns the original pointer, we + // do not want that because we may have to store the pathname in this object + if (_path != name) return; + + // make a full copy of the pathname + _path = estrndup(name, size); +} + +/** + * Destructor + */ +File::~File() +{ + // clean up path name + if (_path) efree(_path); + + // clean up opcodes + if (_opcodes) delete _opcodes; +} + +/** + * Compile the file + * @return bool + */ +bool File::compile() +{ + // never works if the path is invalid + if (!_path) return false; + + // is the file already compiled? + if (_opcodes) return _opcodes->valid(); + + // we are going to open the file + zend_file_handle fileHandle; + + // open the file + if (zend_stream_open(_path, &fileHandle TSRMLS_CC) == FAILURE) return false; + + // make sure the path name is stored in the handle + if (!fileHandle.opened_path) fileHandle.opened_path = estrdup(_path); + + // we need temporary compiler options + CompilerOptions options(ZEND_COMPILE_DEFAULT); + + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + // create the opcodes + _opcodes = new Opcodes(zend_compile_file(&fileHandle, ZEND_INCLUDE TSRMLS_CC)); + + // close the file handle + zend_destroy_file_handle(&fileHandle TSRMLS_CC); + + // done + return _opcodes->valid(); +} + +/** + * Does the file exist? + * @return boolean + */ +bool File::exists() +{ + // it is of course not valid if the path could not be resolved + if (!_path) return false; + + // if we have valid opcodes, we're sure that it exists + if (_opcodes && _opcodes->valid()) return true; + + // retrieve stats + struct stat buf; + return stat(_path, &buf) == 0; +} + +/** + * Is this a valid file? + * @return boolean + */ +bool File::valid() +{ + // check if file is compilable + return compile(); +} + +/** + * Execute the file + * @return Value + */ +Value File::execute() +{ + // do we already have the opcodes? + if (_opcodes) return _opcodes->execute(); + + // try compiling the file + if (!compile()) return nullptr; + + // add the entry to the list of included files + zend_hash_add_empty_element(&EG(included_files), _path, ::strlen(_path) + 1); + + // execute the opcodes + return _opcodes->execute(); +} + +/** + * Execute a file only once + * @return Value + */ +Value File::once() +{ + // skip if the path is invalid + if (!_path) return nullptr; + + // check if this file was already included + if (zend_hash_exists(&EG(included_files), _path, ::strlen(_path) + 1)) return nullptr; + + // execute the file + return execute(); +} + +/** + * End of namespace + */ +} + diff --git a/zend/hashiterator.h b/zend/hashiterator.h index b1f409f..36c3d6c 100644 --- a/zend/hashiterator.h +++ b/zend/hashiterator.h @@ -29,7 +29,7 @@ public: * @param first Should it start on the first position? * @param tsrm_ls */ - HashIterator(HashTable *hashtable, bool first) : _table(hashtable) + HashIterator(HashTable *hashtable, bool first, bool is_array = false) : _table(hashtable), _is_array(is_array) { // reset the hash pointer to the internal position if (hashtable && first) @@ -56,7 +56,7 @@ public: * @param tsrm_ls */ HashIterator(const HashIterator &that TSRMLS_DC) : - _table(that._table), _position(that._position) + _table(that._table), _position(that._position), _is_array(that._is_array) { // read current position read(); @@ -166,7 +166,13 @@ private: * @var HashPosition */ Bucket *_position = nullptr; - + + /** + * Is a hash interator in array + * @var bool + */ + bool _is_array = false; + /** * The current key and value * @var std::pair @@ -207,7 +213,7 @@ private: // numeric keys are the easiest ones if (type == HASH_KEY_IS_LONG) key = (int64_t)num_key; - else key = string_key; + else key = std::string(string_key, str_len - 1); #endif @@ -223,7 +229,7 @@ private: // if the key is private (it starts with a null character) we should return // false to report that the object is not in a completely valid state - return !_current.first.isString() || _current.first.rawValue()[0]; + return _is_array || !_current.first.isString() || _current.first.rawValue()[0]; } /** diff --git a/zend/includes.h b/zend/includes.h index 63b435e..d6f2062 100644 --- a/zend/includes.h +++ b/zend/includes.h @@ -20,6 +20,7 @@ #include <list> #include <exception> #include <type_traits> +#include <functional> // for debug #include <iostream> @@ -79,6 +80,9 @@ #include "../include/namespace.h" #include "../include/extension.h" #include "../include/call.h" +#include "../include/opcodes.h" +#include "../include/script.h" +#include "../include/file.h" /** * Common header files for internal use only @@ -112,6 +116,7 @@ #include "objectimpl.h" #include "parametersimpl.h" #include "extensionimpl.h" +#include "compileroptions.h" #ifndef ZVAL_COPY_VALUE #define ZVAL_COPY_VALUE(z, v) \ diff --git a/zend/object.cpp b/zend/object.cpp index dc548cc..22431fe 100644 --- a/zend/object.cpp +++ b/zend/object.cpp @@ -17,7 +17,7 @@ namespace Php { * @param name Name of the class to instantiate * @param base Implementation of the class */ -Object::Object(const char *name, Base *base) +Object::Object(const char *name, Base *base) : Value() { // does the object already have a handle? if (base->implementation()) @@ -29,7 +29,7 @@ Object::Object(const char *name, Base *base) { // we need the tsrm_ls variable TSRMLS_FETCH(); - + // 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 (we use the FatalError class @@ -38,24 +38,48 @@ Object::Object(const char *name, Base *base) // 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) - new ObjectImpl(entry, base TSRMLS_CC); - + // member in the base object), this is a self-destructing object that + // will be destructed when the last reference to it has been removed, + // we already set the reference to zero + new ObjectImpl(entry, base, 0 TSRMLS_CC); + // now we can store it operator=(Value(base)); - + // install the object handlers Z_OBJVAL_P(_val).handlers = ClassImpl::objectHandlers(entry); } } /** + * Copy constructor is valid if the passed in object is also an object, + * or when it is a string holding a classname + * @param that An other object + */ +Object::Object(const Value &value) : Value() +{ + // when a string is passed in, we are going to make a new instance of the + // passed in string + if (value.isString()) + { + // instantiate the object + if (instantiate(value)) call("__construct"); + } + else + { + // this simply copies the other object + operator=(value); + } +} + +/** * Internal method to instantiate an object - * @param name + * @param name Name of the class to instantiate + * @return bool True if there is a __construct function */ -void Object::instantiate(const char *name) +bool Object::instantiate(const char *name) { // we need the tsrm_ls variable TSRMLS_FETCH(); @@ -79,6 +103,8 @@ void Object::instantiate(const char *name) // @todo is this a memory leak? the base class first initializes a stdClass, // and then we overwrite it with a specific class + // return whether there is a __construct function + return zend_hash_exists(&entry->function_table, "__construct", 12); } /** diff --git a/zend/objectimpl.h b/zend/objectimpl.h index 7f16320..4d20f3c 100644 --- a/zend/objectimpl.h +++ b/zend/objectimpl.h @@ -62,9 +62,10 @@ public: * * @param entry Zend class entry * @param base C++ object that already exists + * @param refcount The initial refcount for the object * @param tsrm_ls Optional threading data */ - ObjectImpl(zend_class_entry *entry, Base *base TSRMLS_DC) + ObjectImpl(zend_class_entry *entry, Base *base, int refcount TSRMLS_DC) { // allocate a mixed object (for some reason this does not have to be deallocated) _mixed = (MixedObject *)emalloc(sizeof(MixedObject)); @@ -98,25 +99,28 @@ public: // when in thread safety mode, the destruct method and free method have // an extra parameter holding thread information - using DestructType = void(zend_object*,unsigned int,void***); - using FreeType = void(zend_object*,void***); + using DestructType = void(*)(zend_object*,unsigned int,void***); + using FreeType = void(*)(zend_object*,void***); #else // not in thread mode: no special parameter for the tsrm_ls variable - using DestructType = void(zend_object*,unsigned int); - using FreeType = void(zend_object*); + using DestructType = void(*)(zend_object*, unsigned int); + using FreeType = void(*)(zend_object*); #endif // store the two destruct methods in temporary vars - DestructType *destructMethod = &ClassImpl::destructObject; - FreeType *freeMethod = &ClassImpl::freeObject; + DestructType destructMethod = &ClassImpl::destructObject; + FreeType freeMethod = &ClassImpl::freeObject; // the destructor and clone handlers are set to NULL. I dont know why, but they do not // seem to be necessary... _handle = zend_objects_store_put(php(), (zend_objects_store_dtor_t)destructMethod, (zend_objects_free_object_storage_t)freeMethod, NULL TSRMLS_CC); + // set the initial refcount (if it is different than one, because one is the default) + if (refcount != 1) EG(objects_store).object_buckets[_handle].bucket.obj.refcount = refcount; + // the object may remember that we are its implementation object base->_impl = this; } diff --git a/zend/opcodes.cpp b/zend/opcodes.cpp new file mode 100644 index 0000000..9e4525a --- /dev/null +++ b/zend/opcodes.cpp @@ -0,0 +1,131 @@ +/** + * Opcodes.cpp + * + * Implementation file for the opcodes class + * + * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com> + * @copyright 2014 Copernica BV + */ + +/** + * Dependencies + */ +#include "includes.h" + +/** + * Set up namespace + */ +namespace Php { + +/** + * Helper class to store and restore the current opcode state + * + * When we're going to execute a set of instructions, we need to store the + * current state of the Zend engine. After the instructions have been processed, + * we can switch back to the original instructions + */ +class ExecuteState +{ +private: + /** + * All the original settings + */ + zend_op_array *_active_op_array; + zval **_return_value_ptr_ptr; + zend_op **_opline_ptr; + int _interactive; + +public: + /** + * Constructor + */ + ExecuteState() + { + // store all the original stuff + _active_op_array = EG(active_op_array); + _return_value_ptr_ptr = EG(return_value_ptr_ptr); + _opline_ptr = EG(opline_ptr); + _interactive = CG(interactive); + } + + /** + * Destructor + */ + virtual ~ExecuteState() + { + // restore all settings + CG(interactive) = _interactive; + EG(no_extensions) = 0; + EG(opline_ptr) = _opline_ptr; + EG(active_op_array) = _active_op_array; + EG(return_value_ptr_ptr) = _return_value_ptr_ptr; + } +}; + +/** + * Destructor + */ +Opcodes::~Opcodes() +{ + // leap out if opcodes were not valid + if (!_opcodes) return; + + // clean up opcodes + destroy_op_array(_opcodes TSRMLS_CC); + efree(_opcodes); +} + +/** + * Execute the opcodes + * @return Value + */ +Value Opcodes::execute() const +{ + // if the script could not be compiled, we return null + if (!_opcodes) return nullptr; + + // pointer that is going to hold the return value of the script + zval *retval_ptr = nullptr; + + // the zend engine is probably already busy processing opcodes, so we store + // the current execute state before we're going to switch the runtime to + // our own set of opcodes + ExecuteState oldstate; + + // old execute state has been saved (and will automatically be restured when + // the oldstate is destructed), so we can now safely overwrite all the settings + EG(return_value_ptr_ptr) = &retval_ptr; + EG(active_op_array) = _opcodes; + EG(no_extensions) = 1; + if (!EG(active_symbol_table)) zend_rebuild_symbol_table(TSRMLS_C); + CG(interactive) = 0; + + // the current exception + zval* oldException = EG(exception); + + // execute the code + zend_execute(_opcodes TSRMLS_CC); + + // 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); + + // we're ready if there is no return value + if (!retval_ptr) return nullptr; + + // wrap the return value + Value result(retval_ptr); + + // destruct the zval (this function will decrement the reference counter, + // and only destruct if there are no other references left) + zval_ptr_dtor(&retval_ptr); + + // copy the pointer into a value object, and return that + return result; +} + +/** + * End of namespace + */ +} + diff --git a/zend/parametersimpl.h b/zend/parametersimpl.h index fd14238..2841c75 100644 --- a/zend/parametersimpl.h +++ b/zend/parametersimpl.h @@ -36,7 +36,7 @@ public: zval **arg = (zval **) (zend_vm_stack_top(TSRMLS_C) - 1 - (argc-i)); // append value - push_back(Value(*arg)); + emplace_back(*arg); } } diff --git a/zend/script.cpp b/zend/script.cpp new file mode 100644 index 0000000..58ee376 --- /dev/null +++ b/zend/script.cpp @@ -0,0 +1,60 @@ +/** + * Script.cpp + * + * Implementation file for the script class + * + * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com> + * @copyright 2014 Copernica BV + */ + +/** + * Dependencies + */ +#include "includes.h" + +/** + * Open PHP namespace + */ +namespace Php { + +/** + * Helper function to compile the source code + * @param name name of the script + * @param script actual PHP code + * @param size length of the string + * @return opcodes + */ +zend_op_array *Script::compile(const char *name, const char *phpcode, size_t size) +{ + // Sadly, there is not a simple Zend function to compile a string into opcodes, + // so we basically copy the code that we found in zend_execute_API.c inside + // the zend_eval_stringl() function into this file here. However, the code + // found there is full of zval manipulation, for which we can use the much + // simpler Php::Value object + Php::Value source(phpcode, size); + + // remember the old compiler options, and set new compiler options + CompilerOptions options(ZEND_COMPILE_DEFAULT_FOR_EVAL); + + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + // compile the string + return zend_compile_string(source._val, (char *)name TSRMLS_CC); +} + +/** + * Constructor + * @param name name of the script + * @param script actual PHP code + * @param size length of the string + */ +Script::Script(const char *name, const char *phpcode, size_t size) : _opcodes(compile(name, phpcode, size)) +{ +} + +/** + * End of namespace + */ +} + diff --git a/zend/super.cpp b/zend/super.cpp index c25efeb..a2fa0a9 100644 --- a/zend/super.cpp +++ b/zend/super.cpp @@ -40,7 +40,7 @@ Value Super::operator[](const std::string &key) Value value(PG(http_globals)[_index]); // pass on the call - return value[key]; + return value.get(key); } /** @@ -61,7 +61,7 @@ Value Super::operator[](const char *key) Value value(PG(http_globals)[_index]); // pass on the call - return value[key]; + return value.get(key); } /** diff --git a/zend/value.cpp b/zend/value.cpp index 6890ffd..4a59d97 100644 --- a/zend/value.cpp +++ b/zend/value.cpp @@ -124,10 +124,10 @@ Value::Value(const std::string &value) */ Value::Value(const char *value, int size) { - // allocate the zval + // allocate the zval MAKE_STD_ZVAL(_val); - - // do we have a valid value? + + // is there a value? if (value) { // create a string zval @@ -157,10 +157,8 @@ Value::Value(double value) * @param ref Force this to be a reference */ Value::Value(struct _zval_struct *val, bool ref) +: _val(val) { - // just copy the zval into this object - _val = val; - // if the variable is not already a reference, and it has more than one // variable pointing to it, we should seperate it so that any changes // we're going to make will not change the other variable @@ -169,13 +167,13 @@ Value::Value(struct _zval_struct *val, bool ref) // separate the zval SEPARATE_ZVAL_IF_NOT_REF(&_val); } - + // we see ourselves as reference too Z_ADDREF_P(_val); - + // we're ready if we do not have to force it as a reference if (!ref || Z_ISREF_P(_val)) return; - + // make this a reference Z_SET_ISREF_P(_val); } @@ -188,7 +186,9 @@ Value::Value(const Base *object) { // there are two options: the object was constructed from user space, // and is already linked to a handle, or it was constructed from C++ - // space, and no handle does yet exist, find the implementation object + // space, and no handle does yet exist. But if it was constructed from + // C++ space and not yet wrapped, this Value constructor should not be + // called directly, but first via the derived Php::Object class. auto *impl = object->implementation(); // do we have a handle? @@ -205,14 +205,14 @@ Value::Value(const Base *object) // we have to lookup the object in the object-table zend_object_store_bucket *obj_bucket = &EG(objects_store).object_buckets[impl->handle()]; + // there is one more reference to the object + obj_bucket->bucket.obj.refcount += 1; + // this is copy-pasted from zend_objects.c - and it is necessary too! if (!obj_bucket->bucket.obj.handlers) obj_bucket->bucket.obj.handlers = &std_object_handlers; // store the handlers in the zval too (cast is necessary for php 5.3) Z_OBJ_HT_P(_val) = (zend_object_handlers*)obj_bucket->bucket.obj.handlers; - - // run the copy constructor - zval_copy_ctor(_val); } /** @@ -286,7 +286,7 @@ Value::Value(const Value &that) * Move constructor * @param value */ -Value::Value(Value &&that) : _val(that._val) +Value::Value(Value &&that) noexcept: _val(that._val) { // clear the other object that._val = nullptr; @@ -387,7 +387,7 @@ void Value::attach(struct _hashtable *hashtable) * Retrieve the refcount * @return int */ -int Value::refcount() +int Value::refcount() const { return Z_REFCOUNT_P(_val); } @@ -397,7 +397,7 @@ int Value::refcount() * @param value * @return Value */ -Value &Value::operator=(Value &&value) +Value &Value::operator=(Value &&value) noexcept { // skip self assignment if (this == &value) return *this; @@ -854,191 +854,6 @@ Value Value::operator()() const } /** - * Call the function - if the variable holds a callable thing - * @param p0 The first parameter - * @return Value - */ -Value Value::operator()(Value p0) const -{ - // array of parameters - zval **params[1] = { &p0._val }; - - // call the function - return exec(1, params); -} - -/** - * Call the function - if the variable holds a callable thing - * @param p0 The first parameter - * @param p1 The second parameter - * @return Value - */ -Value Value::operator()(Value p0, Value p1) const -{ - // array of parameters - zval **params[2] = { &p0._val, &p1._val }; - - // call the function - return exec(2, params); -} - -/** - * Call the function - if the variable holds a callable thing - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @return Value - */ -Value Value::operator()(Value p0, Value p1, Value p2) const -{ - // array of parameters - zval **params[3] = { &p0._val, &p1._val, &p2._val }; - - // call the function - return exec(3, params); -} - -/** - * Call the function - if the variable holds a callable thing - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @param p3 The fourth parameter - * @return Value - */ -Value Value::operator()(Value p0, Value p1, Value p2, Value p3) const -{ - // array of parameters - zval **params[4] = { &p0._val, &p1._val, &p2._val, &p3._val }; - - // call the function - return exec(4, params); -} - -/** - * Call the function - if the variable holds a callable thing - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @param p3 The fourth parameter - * @param p4 The fifth parameter - * @return Value - */ -Value Value::operator()(Value p0, Value p1, Value p2, Value p3, Value p4) const -{ - // array of parameters - zval **params[5] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val }; - - // call the function - return exec(5, params); -} - -/** - * Call the function - if the variable holds a callable thing - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @param p3 The fourth parameter - * @param p4 The fifth parameter - * @param p5 The sixth parameter - * @return Value - */ -Value Value::operator()(Value p0, Value p1, Value p2, Value p3, Value p4, Value p5) const -{ - // array of parameters - zval **params[6] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val }; - - // call the function - return exec(6, params); -} - -/** - * Call the function - if the variable holds a callable thing - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @param p3 The fourth parameter - * @param p4 The fifth parameter - * @param p5 The sixth parameter - * @param p6 The seventh parameter - * @return Value - */ -Value Value::operator()(Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6) const -{ - // array of parameters - zval **params[7] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val }; - - // call the function - return exec(7, params); -} - -/** - * Call the function - if the variable holds a callable thing - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @param p3 The fourth parameter - * @param p4 The fifth parameter - * @param p5 The sixth parameter - * @param p6 The seventh parameter - * @param p7 The eighth parameter - * @return Value - */ -Value Value::operator()(Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7) const -{ - // array of parameters - zval **params[8] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val, &p7._val }; - - // call the function - return exec(8, params); -} - -/** - * Call the function - if the variable holds a callable thing - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @param p3 The fourth parameter - * @param p4 The fifth parameter - * @param p5 The sixth parameter - * @param p6 The seventh parameter - * @param p7 The eighth parameter - * @param p8 The ninth parameter - * @return Value - */ -Value Value::operator()(Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7, Value p8) const -{ - // array of parameters - zval **params[9] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val, &p7._val, &p8._val }; - - // call the function - return exec(9, params); -} - -/** - * Call the function - if the variable holds a callable thing - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @param p3 The fourth parameter - * @param p4 The fifth parameter - * @param p5 The sixth parameter - * @param p6 The seventh parameter - * @param p7 The eighth parameter - * @param p8 The ninth parameter - * @param p9 The tenth parameter - * @return Value - */ -Value Value::operator()(Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7, Value p8, Value p9) const -{ - // array of parameters - zval **params[10] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val, &p7._val, &p8._val, &p9._val }; - - // call the function - return exec(10, params); -} - -/** * Call the method - if the variable holds an object with the given method * @param name name of the method to call * @return Value @@ -1050,201 +865,6 @@ Value Value::call(const char *name) } /** - * Call the method - if the variable holds an object with the given method - * @param name name of the method to call - * @param p0 The first parameter - * @return Value - */ -Value Value::call(const char *name, Value p0) -{ - // array of parameters - zval **params[] = { &p0._val }; - - // call with zero parameters - return exec(name, 1, params); -} - -/** - * Call the method - if the variable holds an object with the given method - * @param name name of the method to call - * @param p0 The first parameter - * @param p1 The second parameter - * @return Value - */ -Value Value::call(const char *name, Value p0, Value p1) -{ - // array of parameters - zval **params[] = { &p0._val, &p1._val }; - - // call with zero parameters - return exec(name, 2, params); -} - -/** - * Call the method - if the variable holds an object with the given method - * @param name name of the method to call - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @return Value - */ -Value Value::call(const char *name, Value p0, Value p1, Value p2) -{ - // array of parameters - zval **params[] = { &p0._val, &p1._val, &p2._val }; - - // call with zero parameters - return exec(name, 3, params); -} - -/** - * Call the method - if the variable holds an object with the given method - * @param name name of the method to call - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @param p3 The fourth parameter - * @return Value - */ -Value Value::call(const char *name, Value p0, Value p1, Value p2, Value p3) -{ - // array of parameters - zval **params[] = { &p0._val, &p1._val, &p2._val, &p3._val }; - - // call with zero parameters - return exec(name, 4, params); -} - -/** - * Call the method - if the variable holds an object with the given method - * @param name name of the method to call - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @param p3 The fourth parameter - * @param p4 The fifth parameter - * @return Value - */ -Value Value::call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4) -{ - // array of parameters - zval **params[] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val }; - - // call with zero parameters - return exec(name, 5, params); -} - -/** - * Call the method - if the variable holds an object with the given method - * @param name name of the method to call - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @param p3 The fourth parameter - * @param p4 The fifth parameter - * @param p5 The sixth parameter - * @return Value - */ -Value Value::call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5) -{ - // array of parameters - zval **params[] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val }; - - // call with zero parameters - return exec(name, 6, params); -} - -/** - * Call the method - if the variable holds an object with the given method - * @param name name of the method to call - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @param p3 The fourth parameter - * @param p4 The fifth parameter - * @param p5 The sixth parameter - * @param p6 The seventh parameter - * @return Value - */ -Value Value::call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6) -{ - // array of parameters - zval **params[] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val }; - - // call with zero parameters - return exec(name, 7, params); -} - -/** - * Call the method - if the variable holds an object with the given method - * @param name name of the method to call - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @param p3 The fourth parameter - * @param p4 The fifth parameter - * @param p5 The sixth parameter - * @param p6 The seventh parameter - * @param p7 The eighth parameter - * @return Value - */ -Value Value::call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7) -{ - // array of parameters - zval **params[] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val, &p7._val }; - - // call with zero parameters - return exec(name, 8, params); -} - -/** - * Call the method - if the variable holds an object with the given method - * @param name name of the method to call - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @param p3 The fourth parameter - * @param p4 The fifth parameter - * @param p5 The sixth parameter - * @param p6 The seventh parameter - * @param p7 The eighth parameter - * @param p8 The ninth parameter - * @return Value - */ -Value Value::call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7, Value p8) -{ - // array of parameters - zval **params[] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val, &p7._val, &p8._val }; - - // call with zero parameters - return exec(name, 9, params); -} - -/** - * Call the method - if the variable holds an object with the given method - * @param name name of the method to call - * @param p0 The first parameter - * @param p1 The second parameter - * @param p2 The third parameter - * @param p3 The fourth parameter - * @param p4 The fifth parameter - * @param p5 The sixth parameter - * @param p6 The seventh parameter - * @param p7 The eighth parameter - * @param p8 The ninth parameter - * @param p9 The tenth parameter - * @return Value - */ -Value Value::call(const char *name, Value p0, Value p1, Value p2, Value p3, Value p4, Value p5, Value p6, Value p7, Value p8, Value p9) -{ - // array of parameters - zval **params[] = { &p0._val, &p1._val, &p2._val, &p3._val, &p4._val, &p5._val, &p6._val, &p7._val, &p8._val, &p9._val }; - - // call with zero parameters - return exec(name, 10, params); -} - -/** * Helper function that runs the actual call * @param object The object to call it on * @param method The function or method to call @@ -1409,6 +1029,116 @@ bool Value::isCallable() const } /** + * Retrieve the class entry + * @param allowString + * @return zend_class_entry + */ +zend_class_entry *Value::classEntry(bool allowString) const +{ + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + // is this an object + if (isObject()) + { + // should have a class entry + if (!HAS_CLASS_ENTRY(*_val)) return nullptr; + + // class entry can be easily found + return Z_OBJCE_P(_val); + } + else + { + // the value is not an object, is this allowed? + if (!allowString || !isString()) return nullptr; + + // temporary variable + zend_class_entry **ce; + + // find the class entry + if (zend_lookup_class(Z_STRVAL_P(_val), Z_STRLEN_P(_val), &ce TSRMLS_CC) == FAILURE) return nullptr; + + // found the entry + return *ce; + } +} + +/** + * Check whether this object is an instance of a certain class + * + * If you set the parameter 'allowString' to true, and the Value object + * holds a string, the string will be treated as class name. + * + * @param classname The class of which this should be an instance + * @param size Length of the classname string + * @param allowString Is it allowed for 'this' to be a string + * @return bool + */ +bool Value::instanceOf(const char *classname, size_t size, bool allowString) const +{ + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + // the class-entry of 'this' + zend_class_entry *this_ce = classEntry(allowString); + if (!this_ce) return false; + + // class entry of the parameter + zend_class_entry **ce; + + // now we can look up the actual class + // the signature of zend_lookup_class_ex is slightly different since 5.4 + // TODO The signature of this changed once again as of 5.6! +#if PHP_VERSION_ID >= 50400 + if (zend_lookup_class_ex(classname, size, NULL, 0, &ce TSRMLS_CC) == FAILURE) return false; +#else + if (zend_lookup_class_ex(classname, size, 0, &ce TSRMLS_CC) == FAILURE) return false; +#endif + + // check if this is a subclass + return instanceof_function(this_ce, *ce TSRMLS_CC); +} + +/** + * Check whether this object is derived from a certain class + * + * If you set the parameter 'allowString' to true, and the Value object + * holds a string, the string will be treated as class name. + * + * @param classname The class of which this should be an instance + * @param size Length of the classname string + * @param allowString Is it allowed for 'this' to be a string + * @return bool + */ +bool Value::derivedFrom(const char *classname, size_t size, bool allowString) const +{ + // we need the tsrm_ls variable + TSRMLS_FETCH(); + + // the class-entry of 'this' + zend_class_entry *this_ce = classEntry(allowString); + if (!this_ce) return false; + + // class entry of the parameter + zend_class_entry **ce; + + // now we can look up the actual class + // the signature of zend_lookup_class_ex is slightly different since 5.4 + // TODO The signature of this changed once again as of 5.6! +#if PHP_VERSION_ID >= 50400 + if (zend_lookup_class_ex(classname, size, NULL, 0, &ce TSRMLS_CC) == FAILURE) return false; +#else + if (zend_lookup_class_ex(classname, size, 0, &ce TSRMLS_CC) == FAILURE) return false; +#endif + + // should not be identical, it must be a real derived object + if (this_ce == *ce) return false; + + // check if this is a subclass + return instanceof_function(this_ce, *ce TSRMLS_CC); +} + +/** * Make a clone of the type * @return Value */ @@ -1416,18 +1146,24 @@ Value Value::clone() const { // the zval that will hold the copy zval *copy; - + // allocate memory ALLOC_ZVAL(copy); - + // copy the data INIT_PZVAL_COPY(copy, _val); - + // run the copy constructor to ensure that everything gets copied zval_copy_ctor(copy); - + + // wrap it using the Value(zval*) constructor, this will +1 the refcount!!!! + Value output(copy); + + // -1 the refcount to avoid future leaks + Z_DELREF_P(copy); + // done - return Value(copy); + return output; } /** @@ -1610,10 +1346,10 @@ std::map<std::string,Php::Value> Value::mapValue() const { // result variable std::map<std::string,Php::Value> result; - + // iterate over the object - for (auto &iter : *this) result[iter.first.rawValue()] = iter.second; - + for (auto &iter : *this) result[iter.first.stringValue()] = iter.second; + // done return result; } @@ -1626,9 +1362,9 @@ std::map<std::string,Php::Value> Value::mapValue() const ValueIterator Value::createIterator(bool begin) const { // check type - if (isArray()) return ValueIterator(new HashIterator(Z_ARRVAL_P(_val), begin)); + if (isArray()) return ValueIterator(new HashIterator(Z_ARRVAL_P(_val), begin, true)); - // get access to the hast table + // get access to the hash table if (isObject()) { // we need the TSRMLS_CC variable @@ -1647,7 +1383,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)); } } @@ -1676,6 +1412,20 @@ ValueIterator Value::end() const } /** + * Iterate over key value pairs + * @param callback + */ +void Value::iterate(const std::function<void(const Php::Value &,const Php::Value &)> &callback) const +{ + // iterate over the object + for (const auto &iter : *this) + { + // call the callback + callback(iter.first, iter.second); + } +} + +/** * Does the array contain a certain index? * @param index * @return bool @@ -1761,7 +1511,7 @@ Value Value::get(int index) const */ Value Value::get(const char *key, int size) const { - // must be an array + // must be an array or object if (!isArray() && !isObject()) return Value(); // calculate size @@ -1781,6 +1531,9 @@ Value Value::get(const char *key, int size) const } else { + // key should not start with a null byte + if (size > 0 && key[0] == 0) return Value(); + // we need the tsrm_ls variable TSRMLS_FETCH(); @@ -1847,6 +1600,9 @@ void Value::set(int index, const Value &value) */ void Value::setRaw(const char *key, int size, const Value &value) { + // does not work for empty keys + if (!key || (size > 0 && key[0] == 0)) return; + // is this an object? if (isObject()) { @@ -1858,7 +1614,7 @@ void Value::setRaw(const char *key, int size, const Value &value) // retrieve the class entry auto *entry = zend_get_class_entry(_val TSRMLS_CC); - + // update the property (cast necessary for php 5.3) zend_update_property(entry, _val, (char *)key, size, value._val TSRMLS_CC); } |