diff options
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | include/class.h | 5 | ||||
-rw-r--r-- | include/extension.h | 129 | ||||
-rw-r--r-- | include/namespace.h | 155 | ||||
-rw-r--r-- | src/callable.h | 12 | ||||
-rw-r--r-- | src/extension.cpp | 275 | ||||
-rw-r--r-- | src/extensionimpl.cpp | 300 | ||||
-rw-r--r-- | src/extensionimpl.h | 213 | ||||
-rw-r--r-- | src/function.h | 2 | ||||
-rw-r--r-- | src/includes.h | 1 | ||||
-rw-r--r-- | src/namespace.cpp | 79 |
11 files changed, 694 insertions, 481 deletions
@@ -184,8 +184,8 @@ install: ${MKDIR} ${INSTALL_HEADERS}/phpcpp ${CP} phpcpp.h ${INSTALL_HEADERS} ${CP} include/*.h ${INSTALL_HEADERS}/phpcpp - ${CP} ${PHP_LIBRARY} ${INSTALL_LIB} - ${CP} ${HHVM_LIBRARY} ${INSTALL_LIB} + if [ -e ${PHP_LIBRARY} ]; then ${CP} ${PHP_LIBRARY} ${INSTALL_LIB}; fi + if [ -e ${HHVM_LIBRARY} ]; then ${CP} ${HHVM_LIBRARY} ${INSTALL_LIB}; fi ${CONFIG_UTILITY} > ${INSTALL_HEADERS}/phpcpp/config.h test: diff --git a/include/class.h b/include/class.h index 7b9a928..e0316df 100644 --- a/include/class.h +++ b/include/class.h @@ -16,11 +16,6 @@ */ /** - * Zend/SPL interfaces that we support - */ -extern struct _zend_class_entry *zend_ce_arrayaccess; - -/** * Set up namespace */ namespace Php { diff --git a/include/extension.h b/include/extension.h index f9361b3..84789d3 100644 --- a/include/extension.h +++ b/include/extension.h @@ -6,7 +6,7 @@ * apache process starts - and will be used for all subsequent requests that * are handled by Apache. * - * For some environments (for example CLI scripts and FastCGI calls) only one + * For some environments (for example CLI scripts and CGI calls) only one * request is handled by an extension instance, but for others (when PHP runs * as module in a webserver) many requests are handled by the same extension * instance. @@ -16,11 +16,6 @@ */ /** - * Structures referenced in this class - */ -struct _zend_module_entry; - -/** * Set up namespace */ namespace Php { @@ -28,7 +23,7 @@ namespace Php { /** * Forward declaration */ -class Extension; +class ExtensionImpl; /** * Signature of a callback @@ -46,7 +41,7 @@ public: * @param name Extension name * @param version Extension version string */ - Extension(const char *name = NULL, const char *version = NULL); + Extension(const char *name, const char *version = "1.0"); /** * No copy'ing and no moving @@ -67,13 +62,10 @@ public: * is handled. You can register this callback if you want to be notified * when the engine is ready, for example to initialize certain things. * - * @param callback + * @param callback Function to be called + * @return Extension Same object to allow chaining */ - void onStartup(const Callback &callback) - { - // copy callback - _onStartup = callback; - } + Extension &onStartup(const Callback &callback); /** * Register a function to be called when the PHP engine is going to stop @@ -81,13 +73,10 @@ public: * The callback will be called right before the process is going to stop. * You can register a function if you want to clean up certain things. * - * @param callback + * @param callback Function to be called + * @return Extension Same object to allow chaining */ - void onShutdown(const Callback &callback) - { - // copy callback - _onShutdown = callback; - } + Extension &onShutdown(const Callback &callback); /** * Register a callback that is called at the beginning of each pageview/request @@ -97,13 +86,10 @@ public: * multiple requests after each other, and you may want to set back certain * global variables to their initial variables in front of each request * - * @param callback + * @param callback Function to be called + * @return Extension Same object to allow chaining */ - void onRequest(const Callback &callback) - { - // copy callback - _onRequest = callback; - } + Extension &onRequest(const Callback &callback); /** * Register a callback that is called to cleanup things after a pageview/request @@ -113,105 +99,38 @@ public: * This method is called onIdle because the extension is idle in between * requests. * - * @param callback + * @param callback Function to be called + * @return Extension Same object to allow chaining */ - void onIdle(const Callback &callback) - { - // copy callback - _onIdle = callback; - } + Extension &onIdle(const Callback &callback); /** - * Retrieve the module entry + * Retrieve the module pointer * * This is the memory address that should be exported by the get_module() * function. * - * @return _zend_module_entry + * @return void* */ - _zend_module_entry *module(); + void *module(); /** * Cast to a module entry - * @return _zend_module_entry* + * + * @return void* */ - operator _zend_module_entry * () + operator void * () { return module(); } private: /** - * 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. + * The implementation object * - * @var zend_module_entry - */ - _zend_module_entry *_entry; - - /** - * Callback that is called after the engine is initialized and before the - * pageviews are going to be handled - * @var Callback - */ - Callback _onStartup; - - /** - * Callback that is called in front of each request - * @var Callback - */ - Callback _onRequest; - - /** - * Callback that is called right after each request - * @var Callback - */ - Callback _onIdle; - - /** - * Callback that is called right before the engine is closing down - * @var Callback - */ - Callback _onShutdown; - - /** - * Function that is called when the extension initializes - * @param type Module type - * @param number Module number - * @param tsrm_ls - * @return int 0 on success - */ - static int onStartup(int type, int module_number TSRMLS_DC); - - /** - * Function that is called when the extension is about to be stopped - * @param type Module type - * @param number Module number - * @param tsrm_ls - * @return int - */ - static int onShutdown(int type, int module_number TSRMLS_DC); - - /** - * Function that is called when a request starts - * @param type Module type - * @param number Module number - * @param tsrm_ls - * @return int 0 on success - */ - static int onRequest(int type, int module_number TSRMLS_DC); - - /** - * Function that is called when a request is ended - * @param type Module type - * @param number Module number - * @param tsrm_ls - * @return int 0 on success + * @var ExtensionImpl */ - static int onIdle(int type, int module_number TSRMLS_DC); + ExtensionImpl *_impl; }; /** diff --git a/include/namespace.h b/include/namespace.h index de73924..cd871d4 100644 --- a/include/namespace.h +++ b/include/namespace.h @@ -23,32 +23,37 @@ class Function; */ class Namespace { -public: +protected: /** - * Constructor - * @param name Name of the namespace + * Name of the namespace + * @var string */ - Namespace(const char *name) : _name(name) {} + std::string _name; + + /** + * Functions defined in the namespace + * @var list + */ + std::list<std::shared_ptr<Function>> _functions; + + /** + * Classes defined in the namespace + * @var list + */ + std::list<std::shared_ptr<ClassBase>> _classes; /** - * Copy constructor - * @param ns Namespace to copy + * Namespaces defined inside the namespace + * @var list */ - Namespace(const Namespace &ns) : - _name(ns._name), - _functions(ns._functions), - _classes(ns._classes), - _namespaces(ns._namespaces) {} - + std::list<std::shared_ptr<Namespace>> _namespaces; + +public: /** - * Move constructor - * @param ns + * Constructor + * @param name Name of the namespace */ - Namespace(Namespace &&ns) : - _name(std::move(ns._name)), - _functions(std::move(ns._functions)), - _classes(std::move(ns._classes)), - _namespaces(std::move(ns._namespaces)) {} + Namespace(const char *name) : _name(name) {} /** * Destructor @@ -56,7 +61,7 @@ public: virtual ~Namespace() {} /** - * Add a native function directly to the extension + * Add a native function directly to the namespace * @param name Name of the function * @param function The function to add * @param arguments Optional argument specification @@ -68,131 +73,91 @@ public: Namespace &add(const char *name, const native_callback_3 &function, const Arguments &arguments = {}); /** - * Add a native class to the extension by moving it + * Add a native class to the namespace by moving it * @param type The class implementation * @return Namespace Same object to allow chaining */ template<typename T> Namespace &add(Class<T> &&type) { - // make a copy of the object - auto *copy = new Class<T>(std::move(type)); - - // and add it to the list of classes - _classes.push_back(std::unique_ptr<ClassBase>(copy)); + // make a copy of the object, and add it to the list of classes + _classes.push_back(std::unique_ptr<ClassBase>(new Class<T>(std::move(type)))); // allow chaining return *this; } /** - * Add a native class to the extension by copying it + * Add a native class to the namespace by copying it * @param type The class implementation - * @param Namespace Same object to allow chaining + * @return Namespace Same object to allow chaining */ template<typename T> Namespace &add(const Class<T> &type) { - // make a copy of the object - auto *copy = new Class<T>(std::move(type)); - // and add it to the list of classes - _classes.push_back(std::unique_ptr<ClassBase>(copy)); + _classes.push_back(std::unique_ptr<ClassBase>(new Class<T>(type))); // allow chaining return *this; } /** - * Add an interface to the extension by moving it + * Add an interface to the namespace by moving it * @param interface The interface properties + * @return Namespace Same object to allow chaining */ Namespace &add(Interface &&interface) { - // make a copy of the object - auto *copy = new Interface(std::move(interface)); - - // and add it to the list of classes - _classes.push_back(std::unique_ptr<ClassBase>(copy)); + // make a copy and add it to the list of classes + _classes.push_back(std::unique_ptr<ClassBase>(new Interface(std::move(interface)))); // allow chaining return *this; } /** - * Add an interface to the extension by copying it + * Add an interface to the namespace by copying it * @param interface The interface properties + * @return Namespace Same object to allow chaining */ Namespace &add(const Interface &interface) { - // make a copy of the object - auto *copy = new Interface(interface); - - // and add it to the list of classes - _classes.push_back(std::unique_ptr<ClassBase>(copy)); + // make a copy and add it to the list of classes + _classes.push_back(std::unique_ptr<ClassBase>(new Interface(interface))); // allow chaining return *this; } /** - * Add a namespace to the extension by moving it + * Add a namespace to the namespace by moving it * @param ns The namespace + * @return Namespace Same object to allow chaining */ Namespace &add(Namespace &&ns) { - // make a copy of the object - auto *copy = new Namespace(std::move(ns)); - // add it to the list of namespaces - _namespaces.push_back(std::unique_ptr<Namespace>(copy)); + _namespaces.push_back(std::unique_ptr<Namespace>(new Namespace(std::move(ns)))); // allow chaining return *this; } /** - * Add a namespace to the extension by copying it + * Add a namespace to the namespace by copying it * @param ns The namespace + * @return Namespace Same object to allow chaining */ Namespace &add(const Namespace &ns) { - // make a copy of the object - auto *copy = new Namespace(std::move(ns)); - - // add it to the list of namespaces - _namespaces.push_back(std::unique_ptr<Namespace>(copy)); + // make a copy and add it to the list of namespaces + _namespaces.push_back(std::unique_ptr<Namespace>(new Namespace(ns))); // allow chaining return *this; } - -protected: - /** - * Name of the namespace - * @var string - */ - std::string _name; - - /** - * Functions defined by the extension - * @var list - */ - std::list<std::shared_ptr<Function>> _functions; - - /** - * Classes defined by the extension - * @var list - */ - std::list<std::shared_ptr<ClassBase>> _classes; - - /** - * Namespaces defined by the extension - * @var list - */ - std::list<std::shared_ptr<Namespace>> _namespaces; - /** * The total number of functions * @return size_t @@ -208,21 +173,27 @@ protected: // done return result; } - + /** - * Initialize all functions in this namespace - * @param ns Namespace prefix - * @param entries The array to be filled - * @return int Number of functions that were initialized + * Apply a callback to each registered function + * + * The callback will be called with the name of the namespace, and + * a reference to the registered function. + * + * @param callback */ - size_t initialize(const std::string &ns, struct _zend_function_entry entries[]); - + void apply(const std::function<void(const std::string &ns, Function &func)> &callback); + /** - * Initialize the namespace after it was registered - * @param parent Parent namespace - * @param tsrm_ls + * Apply a callback to each registered class + * + * The callback will be called with the name of the namespace, and + * a reference to the registered class. + * + * @param callback */ - void initialize(const std::string &parent TSRMLS_DC); + void apply(const std::function<void(const std::string &ns, ClassBase &clss)> &callback); + }; /** diff --git a/src/callable.h b/src/callable.h index aef5649..65ce719 100644 --- a/src/callable.h +++ b/src/callable.h @@ -9,12 +9,6 @@ */ /** - * Forward definitions - */ -struct _zend_function_entry; -struct _zend_arg_info; - -/** * Set up namespace */ namespace Php { @@ -99,7 +93,7 @@ public: * @param classname Optional class name * @param flags Access flags */ - void initialize(struct _zend_function_entry *entry, const char *classname = nullptr, int flags = 0) const; + void initialize(zend_function_entry *entry, const char *classname = nullptr, int flags = 0) const; /** * Fill function info @@ -107,7 +101,7 @@ public: * @param ns Active namespace * @param classname Optional class name */ - void initialize(struct _zend_arg_info *info, const char *classname = nullptr) const; + void initialize(zend_arg_info *info, const char *classname = nullptr) const; protected: @@ -139,7 +133,7 @@ protected: * The arguments * @var zend_arg_info[] */ - struct _zend_arg_info *_argv = nullptr; + zend_arg_info *_argv = nullptr; /** * Private helper method to fill an argument object diff --git a/src/extension.cpp b/src/extension.cpp index 949efa9..9685b32 100644 --- a/src/extension.cpp +++ b/src/extension.cpp @@ -2,7 +2,7 @@ * Extension.cpp * * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com> - * @copyright 2013 Copernica BV + * @copyright 2013, 2014 Copernica BV */ #include "includes.h" @@ -12,265 +12,88 @@ namespace Php { /** - * If this extension is compiled for a PHP version with multi - * threading support, we need an additional header file + * Constructor that defines a number of functions right away + * @param name Extension name + * @param version Extension version string */ -#ifdef ZTS -#include "TSRM.h" -#endif - -/** - * We're almost there, we now need to declare an instance of the - * structure defined above (if building for a single thread) or some - * sort of impossible to understand magic pointer-to-a-pointer (for - * multi-threading builds). We make this a static variable because - * this already is bad enough. - */ -ZEND_DECLARE_MODULE_GLOBALS(phpcpp) - -/** - * Function that must be defined to initialize the "globals" - * We do not have to initialize anything, but PHP needs to call this - * method (crazy) - * @param globals - */ -static void init_globals(zend_phpcpp_globals *globals) {} - -/** - * The *startup() and *shutdown() callback functions are passed a module_number - * variable. However, there does not seem to be a decent API call in Zend to - * get back the original module_entry linked to this number. So we have to - * look up entries in a hash table to find the right module entry. To make things - * even worse, the records in this hash table are copies of the original - * zend_module_entry structure, so we can also not hide the C++ extension - * object pointer in the entry that we created ourselves. - * - * We have an ugly solution, we keep track of a map of all C++ extension names - * and their associated extension object, and a map of all module number and - * the linked extension object. - * - * @var map - */ -static std::map<std::string,Extension*> name2extension; -static std::map<int,Extension*> number2extension; - -/** - * Handler function that is used in combination with zend_hash_apply() - * - * This function is called when we need to find an extension object based on - * an extension number. We loop through the list of all registered modules, and - * for each module we check if we know the extension based on the name - * - * @param _zend_module_entry - */ -static int match_module(_zend_module_entry *entry) -{ - // check if there is an extension with this name - auto iter = name2extension.find(entry->name); - if (iter == name2extension.end()) return ZEND_HASH_APPLY_KEEP; - - // we have the extension, store in combination with the number - number2extension[entry->module_number] = iter->second; - - // done - return ZEND_HASH_APPLY_KEEP; -} - -/** - * Find an extension based on the module number - * @param number - * @param tsrm_ls - * @return Extension* - */ -static Extension *find(int number TSRMLS_DC) -{ - // do we already have an extension with this number? - auto iter = number2extension.find(number); - if (iter != number2extension.end()) return iter->second; - - // no, not yet, loop through all modules - zend_hash_apply(&module_registry, (apply_func_t)match_module TSRMLS_CC); - - // find again - iter = number2extension.find(number); - if (iter == number2extension.end()) return nullptr; - - // found! - return iter->second; -} - -/** - * Function that is called when the extension initializes - * @param type Module type - * @param number Module number - * @param tsrm_ls - * @return int 0 on success - */ -int Extension::onStartup(int type, int module_number TSRMLS_DC) -{ - // initialize and allocate the "global" variables - ZEND_INIT_MODULE_GLOBALS(phpcpp, init_globals, NULL); +Extension::Extension(const char *name, const char *version) : + Namespace(""), _impl(new ExtensionImpl(this, name, version)) {} - // get the extension - Extension *extension = find(module_number TSRMLS_CC); - - // initialize namespace - extension->initialize("" TSRMLS_CC); - - // is the callback registered? - if (extension->_onStartup) extension->_onStartup(); - - // done - return BOOL2SUCCESS(true); -} - /** - * Function that is called when the extension is about to be stopped - * @param type Module type - * @param number Module number - * @param tsrm_ls - * @return int + * Destructor */ -int Extension::onShutdown(int type, int module_number TSRMLS_DC) +Extension::~Extension() { - // get the extension - Extension *extension = find(module_number TSRMLS_CC); - - // is the callback registered? - if (extension->_onShutdown) extension->_onShutdown(); - - // done - return BOOL2SUCCESS(true); + // get rid of the implementation object + delete _impl; } /** - * Function that is called when a request starts - * @param type Module type - * @param number Module number - * @param tsrm_ls - * @return int 0 on success + * Register a function to be called when the PHP engine is ready + * @param callback + * @return Extension */ -int Extension::onRequest(int type, int module_number TSRMLS_DC) +Extension &Extension::onStartup(const Callback &callback) { - // get the extension - Extension *extension = find(module_number TSRMLS_CC); - - // is the callback registered? - if (extension->_onRequest) extension->_onRequest(); + // pass on to the implementation + _impl->onStartup(callback); - // done - return BOOL2SUCCESS(true); + // allow chaining + return *this; } /** - * Function that is called when a request is ended - * @param type Module type - * @param number Module number - * @param tsrm_ls - * @return int 0 on success + * Register a function to be called when the PHP engine is going to stop + * @param callback + * @return Extension */ -int Extension::onIdle(int type, int module_number TSRMLS_DC) +Extension &Extension::onShutdown(const Callback &callback) { - // get the extension - Extension *extension = find(module_number TSRMLS_CC); + // pass on to the implementation + _impl->onShutdown(callback); - // is the callback registered? - if (extension->_onIdle) extension->_onIdle(); - - // done - return BOOL2SUCCESS(true); + // allow chaining + return *this; } /** - * Constructor - * @param name Name of the extension - * @param version Version number - * @param start Request start callback - * @param stop Request stop callback + * Register a callback that is called at the beginning of each pageview/request + * @param callback */ -Extension::Extension(const char *name, const char *version) : Namespace("") +Extension &Extension::onRequest(const Callback &callback) { - // keep extension pointer based on the name - name2extension[name] = this; - - // allocate memory (we allocate this on the heap so that the size of the - // entry does not have to be defined in the .h file. We pay a performance - // price for this, but we pay this price becuase the design goal of the - // PHP-C++ library is to have an interface that is as simple as possible - _entry = new _zend_module_entry; + // pass on to the implementation + _impl->onRequest(callback); - // assign all members (apart from the globals) - _entry->size = sizeof(zend_module_entry); // size of the data - _entry->zend_api = ZEND_MODULE_API_NO; // api number - _entry->zend_debug = ZEND_DEBUG; // debug mode enabled? - _entry->zts = USING_ZTS; // is thread safety enabled? - _entry->ini_entry = NULL; // the php.ini record - _entry->deps = NULL; // dependencies on other modules - _entry->name = name; // extension name - _entry->functions = NULL; // functions supported by this module (none for now) - _entry->module_startup_func = &Extension::onStartup; // startup function for the whole extension - _entry->module_shutdown_func = &Extension::onShutdown; // shutdown function for the whole extension - _entry->request_startup_func = &Extension::onRequest; // startup function per request - _entry->request_shutdown_func = &Extension::onIdle; // shutdown function per request - _entry->info_func = NULL; // information for retrieving info - _entry->version = version; // version string - _entry->globals_size = 0; // size of the global variables - _entry->globals_ctor = NULL; // constructor for global variables - _entry->globals_dtor = NULL; // destructor for global variables - _entry->post_deactivate_func = NULL; // unknown function - _entry->module_started = 0; // module is not yet started - _entry->type = 0; // temporary or persistent module, will be filled by Zend engine - _entry->handle = NULL; // dlopen() handle, will be filled by Zend engine - _entry->module_number = 0; // module number will be filled in by Zend engine - _entry->build_id = (char *)ZEND_MODULE_BUILD_ID; // check if extension and zend engine are compatible - - // things that only need to be initialized -#ifdef ZTS - _entry->globals_id_ptr = NULL; -#else - _entry->globals_ptr = NULL; -#endif - + // allow chaining + return *this; } /** - * Destructor + * Register a callback that is called to cleanup things after a pageview/request + * @param callback */ -Extension::~Extension() +Extension &Extension::onIdle(const Callback &callback) { - // deallocate functions - if (_entry->functions) delete[] _entry->functions; + // pass on to the implementation + _impl->onIdle(callback); - // deallocate entry - delete _entry; + // allow chaining + return *this; } /** - * Retrieve the module entry - * @return zend_module_entry + * Retrieve the module pointer + * + * This is the memory address that should be exported by the get_module() + * function. + * + * @return void* */ -zend_module_entry *Extension::module() +void *Extension::module() { - // check if functions we're already defined - if (_entry->functions || _functions.size() == 0) return _entry; - - // allocate memory for the functions - zend_function_entry *entries = new zend_function_entry[functions() + 1]; - - // initialize the entries - int count = Namespace::initialize("", entries); - - // last entry should be set to all zeros - zend_function_entry *last = &entries[count]; - - // all should be set to zero - memset(last, 0, sizeof(zend_function_entry)); - - // store functions in entry object - _entry->functions = entries; - - // return the entry - return _entry; + // pass on to the implementation + return _impl->module(); } /** diff --git a/src/extensionimpl.cpp b/src/extensionimpl.cpp new file mode 100644 index 0000000..0197757 --- /dev/null +++ b/src/extensionimpl.cpp @@ -0,0 +1,300 @@ +/** + * Extension.cpp + * + * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com> + * @copyright 2013 Copernica BV + */ +#include "includes.h" + +/** + * Set up namespace + */ +namespace Php { + +/** + * If this extension is compiled for a PHP version with multi + * threading support, we need an additional header file + */ +#ifdef ZTS +#include "TSRM.h" +#endif + +/** + * We're almost there, we now need to declare an instance of the + * structure defined above (if building for a single thread) or some + * sort of impossible to understand magic pointer-to-a-pointer (for + * multi-threading builds). We make this a static variable because + * this already is bad enough. + */ +ZEND_DECLARE_MODULE_GLOBALS(phpcpp) + +/** + * Function that must be defined to initialize the "globals" + * We do not have to initialize anything, but PHP needs to call this + * method (crazy) + * @param globals + */ +static void init_globals(zend_phpcpp_globals *globals) {} + +/** + * The *startup() and *shutdown() callback functions are passed a module_number + * variable. However, there does not seem to be a decent API call in Zend to + * get back the original module_entry linked to this number. So we have to + * look up entries in a hash table to find the right module entry. To make things + * even worse, the records in this hash table are copies of the original + * zend_module_entry structure, so we can also not hide the C++ extension + * object pointer in the entry that we created ourselves. + * + * We have an ugly solution, we keep track of a map of all C++ extension names + * and their associated extension object, and a map of all module number and + * the linked extension object. + * + * @var map + */ +static std::map<std::string,ExtensionImpl*> name2extension; +static std::map<int,ExtensionImpl*> number2extension; + +/** + * Handler function that is used in combination with zend_hash_apply() + * + * This function is called when we need to find an extension object based on + * an extension number. We loop through the list of all registered modules, and + * for each module we check if we know the extension based on the name + * + * @param zend_module_entry + */ +static int match_module(zend_module_entry *entry) +{ + // check if there is an extension with this name + auto iter = name2extension.find(entry->name); + if (iter == name2extension.end()) return ZEND_HASH_APPLY_KEEP; + + // we have the extension, store in combination with the number + number2extension[entry->module_number] = iter->second; + + // done + return ZEND_HASH_APPLY_KEEP; +} + +/** + * Find an extension based on the module number + * @param number + * @param tsrm_ls + * @return Extension* + */ +static ExtensionImpl *find(int number TSRMLS_DC) +{ + // do we already have an extension with this number? + auto iter = number2extension.find(number); + if (iter != number2extension.end()) return iter->second; + + // no, not yet, loop through all modules + zend_hash_apply(&module_registry, (apply_func_t)match_module TSRMLS_CC); + + // find again + iter = number2extension.find(number); + if (iter == number2extension.end()) return nullptr; + + // found! + return iter->second; +} + +/** + * Function that is called when the extension initializes + * @param type Module type + * @param number Module number + * @param tsrm_ls + * @return int 0 on success + */ +int ExtensionImpl::onStartup(int type, int module_number TSRMLS_DC) +{ + // initialize and allocate the "global" variables + ZEND_INIT_MODULE_GLOBALS(phpcpp, init_globals, NULL); + + // get the extension + auto *extension = find(module_number TSRMLS_CC); + + // initialize the extension + extension->initialize(TSRMLS_CC); + + // is the callback registered? + if (extension->_onStartup) extension->_onStartup(); + + // done + return BOOL2SUCCESS(true); +} + +/** + * Function that is called when the extension is about to be stopped + * @param type Module type + * @param number Module number + * @param tsrm_ls + * @return int + */ +int ExtensionImpl::onShutdown(int type, int module_number TSRMLS_DC) +{ + // get the extension + auto *extension = find(module_number TSRMLS_CC); + + // is the callback registered? + if (extension->_onShutdown) extension->_onShutdown(); + + // done + return BOOL2SUCCESS(true); +} + +/** + * Function that is called when a request starts + * @param type Module type + * @param number Module number + * @param tsrm_ls + * @return int 0 on success + */ +int ExtensionImpl::onRequest(int type, int module_number TSRMLS_DC) +{ + // get the extension + auto *extension = find(module_number TSRMLS_CC); + + // is the callback registered? + if (extension->_onRequest) extension->_onRequest(); + + // done + return BOOL2SUCCESS(true); +} + +/** + * Function that is called when a request is ended + * @param type Module type + * @param number Module number + * @param tsrm_ls + * @return int 0 on success + */ +int ExtensionImpl::onIdle(int type, int module_number TSRMLS_DC) +{ + // get the extension + auto *extension = find(module_number TSRMLS_CC); + + // is the callback registered? + if (extension->_onIdle) extension->_onIdle(); + + // done + return BOOL2SUCCESS(true); +} + +/** + * Constructor + * @param data Pointer to the extension object created by the extension programmer + * @param name Name of the extension + * @param version Version number + */ +ExtensionImpl::ExtensionImpl(Extension *data, const char *name, const char *version) : _data(data) +{ + // keep extension pointer based on the name + name2extension[name] = this; + + // assign all members (apart from the globals) + _entry.size = sizeof(zend_module_entry); // size of the data + _entry.zend_api = ZEND_MODULE_API_NO; // api number + _entry.zend_debug = ZEND_DEBUG; // debug mode enabled? + _entry.zts = USING_ZTS; // is thread safety enabled? + _entry.ini_entry = NULL; // the php.ini record + _entry.deps = NULL; // dependencies on other modules + _entry.name = name; // extension name + _entry.functions = NULL; // functions supported by this module (none for now) + _entry.module_startup_func = &ExtensionImpl::onStartup; // startup function for the whole extension + _entry.module_shutdown_func = &ExtensionImpl::onShutdown; // shutdown function for the whole extension + _entry.request_startup_func = &ExtensionImpl::onRequest; // startup function per request + _entry.request_shutdown_func = &ExtensionImpl::onIdle; // shutdown function per request + _entry.info_func = NULL; // information for retrieving info + _entry.version = version; // version string + _entry.globals_size = 0; // size of the global variables + _entry.globals_ctor = NULL; // constructor for global variables + _entry.globals_dtor = NULL; // destructor for global variables + _entry.post_deactivate_func = NULL; // unknown function + _entry.module_started = 0; // module is not yet started + _entry.type = 0; // temporary or persistent module, will be filled by Zend engine + _entry.handle = NULL; // dlopen() handle, will be filled by Zend engine + _entry.module_number = 0; // module number will be filled in by Zend engine + _entry.build_id = (char *)ZEND_MODULE_BUILD_ID; // check if extension and zend engine are compatible + + // things that only need to be initialized +#ifdef ZTS + _entry.globals_id_ptr = NULL; +#else + _entry.globals_ptr = NULL; +#endif + +} + +/** + * Destructor + */ +ExtensionImpl::~ExtensionImpl() +{ + // deallocate functions + if (_entry.functions) delete[] _entry.functions; +} + +/** + * Retrieve the module entry + * @return zend_module_entry + */ +zend_module_entry *ExtensionImpl::module() +{ + // check if functions we're already defined + if (_entry.functions) return &_entry; + + // the number of functions + int count = _data->functions(); + + // skip if there are no functions + if (count == 0) return &_entry; + + // allocate memory for the functions + zend_function_entry *entries = new zend_function_entry[count + 1]; + + // index being processed + int i = 0; + + // apply a function to each function + _data->apply([&i, entries](const std::string &prefix, Function &function) { + + // initialize the function + function.initialize(prefix, &entries[i]); + + // move on to the next iteration + i++; + }); + + // last entry should be set to all zeros + zend_function_entry *last = &entries[count]; + + // all should be set to zero + memset(last, 0, sizeof(zend_function_entry)); + + // store functions in entry object + _entry.functions = entries; + + // return the entry + return &_entry; +} + +/** + * Initialize the extension after it was started + * @param tsrm_ls + */ +void ExtensionImpl::initialize(TSRMLS_D) +{ + // we need to register each class, find out all classes + _data->apply([TSRMLS_C](const std::string &prefix, ClassBase &c) { + + // forward to implementation class + c.implementation()->initialize(&c, prefix TSRMLS_C); + }); +} + +/** + * End of namespace + */ +} + diff --git a/src/extensionimpl.h b/src/extensionimpl.h new file mode 100644 index 0000000..8c9ae58 --- /dev/null +++ b/src/extensionimpl.h @@ -0,0 +1,213 @@ +/** + * ExtensionImpl.h + * + * Extension implementation for the Zend engine. + * + * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com> + * @copyright 2013, 2014 Copernica BV + */ + +/** + * Set up namespace + */ +namespace Php { + +/** + * Class definition + */ +class ExtensionImpl +{ +protected: + /** + * Pointer to the extension object that is filled by the extension programmer + * @var Extension + */ + Extension *_data; + + /** + * 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; + + /** + * Callback that is called after the engine is initialized and before the + * pageviews are going to be handled + * @var Callback + */ + Callback _onStartup; + + /** + * Callback that is called in front of each request + * @var Callback + */ + Callback _onRequest; + + /** + * Callback that is called right after each request + * @var Callback + */ + Callback _onIdle; + + /** + * Callback that is called right before the engine is closing down + * @var Callback + */ + Callback _onShutdown; + +public: + /** + * Constructor + * @param data Extension object created by the extension programmer + * @param name Name of the extension + * @param version Version number + */ + ExtensionImpl(Extension *data, const char *name, const char *version); + + /** + * No copy'ing and no moving + */ + ExtensionImpl(const ExtensionImpl &extension) = delete; + ExtensionImpl(ExtensionImpl &&extension) = delete; + + /** + * Destructor + */ + virtual ~ExtensionImpl(); + + /** + * Register a function to be called when the PHP engine is ready + * + * The callback will be called after all extensions are loaded, and all + * functions and classes are available, but before the first pageview/request + * is handled. You can register this callback if you want to be notified + * when the engine is ready, for example to initialize certain things. + * + * @param callback + */ + void onStartup(const Callback &callback) + { + // copy callback + _onStartup = callback; + } + + /** + * Register a function to be called when the PHP engine is going to stop + * + * The callback will be called right before the process is going to stop. + * You can register a function if you want to clean up certain things. + * + * @param callback + */ + void onShutdown(const Callback &callback) + { + // copy callback + _onShutdown = callback; + } + + /** + * Register a callback that is called at the beginning of each pageview/request + * + * You can register a callback if you want to initialize certain things + * at the beginning of each request. Remember that the extension can handle + * multiple requests after each other, and you may want to set back certain + * global variables to their initial variables in front of each request + * + * @param callback + */ + void onRequest(const Callback &callback) + { + // copy callback + _onRequest = callback; + } + + /** + * Register a callback that is called to cleanup things after a pageview/request + * + * The callback will be called after _each_ request, so that you can clean up + * certain things and make your extension ready to handle the next request. + * This method is called onIdle because the extension is idle in between + * requests. + * + * @param callback + */ + void onIdle(const Callback &callback) + { + // copy callback + _onIdle = callback; + } + + /** + * Retrieve the module entry + * + * This is the memory address that should be exported by the get_module() + * function. + * + * @return _zend_module_entry + */ + zend_module_entry *module(); + + /** + * Cast to a module entry + * @return _zend_module_entry* + */ + operator zend_module_entry * () + { + return module(); + } + +private: + /** + * Initialize the namespace after it was registered + * @param tsrm_ls + */ + void initialize(TSRMLS_DC); + + /** + * Function that is called when the extension initializes + * @param type Module type + * @param number Module number + * @param tsrm_ls + * @return int 0 on success + */ + static int onStartup(int type, int module_number TSRMLS_DC); + + /** + * Function that is called when the extension is about to be stopped + * @param type Module type + * @param number Module number + * @param tsrm_ls + * @return int + */ + static int onShutdown(int type, int module_number TSRMLS_DC); + + /** + * Function that is called when a request starts + * @param type Module type + * @param number Module number + * @param tsrm_ls + * @return int 0 on success + */ + static int onRequest(int type, int module_number TSRMLS_DC); + + /** + * Function that is called when a request is ended + * @param type Module type + * @param number Module number + * @param tsrm_ls + * @return int 0 on success + */ + static int onIdle(int type, int module_number TSRMLS_DC); +}; + +/** + * End of namespace + */ +} + + diff --git a/src/function.h b/src/function.h index fd60944..4c34ac7 100644 --- a/src/function.h +++ b/src/function.h @@ -67,7 +67,7 @@ public: * @param prefix Active namespace prefix * @param entry Entry to be filled */ - void initialize(const std::string &prefix, struct _zend_function_entry *entry) + void initialize(const std::string &prefix, zend_function_entry *entry) { // if there is a namespace prefix, we should adjust the name if (prefix.size()) _ptr = HiddenPointer<Callable>(this, prefix+"\\"+(const char *)_ptr); diff --git a/src/includes.h b/src/includes.h index f725ae1..48df910 100644 --- a/src/includes.h +++ b/src/includes.h @@ -100,6 +100,7 @@ #include "iteratorimpl.h" #include "streambuf.h" #include "classimpl.h" +#include "extensionimpl.h" #ifndef ZVAL_COPY_VALUE #define ZVAL_COPY_VALUE(z, v) \ diff --git a/src/namespace.cpp b/src/namespace.cpp index d462684..bea31a1 100644 --- a/src/namespace.cpp +++ b/src/namespace.cpp @@ -78,58 +78,55 @@ Namespace &Namespace::add(const char *name, const native_callback_3 &function, c } /** - * Initialize all functions in this namespace - * @param parent Namespace prefix of the parent - * @param entries The array to be filled - * @return int Number of functions that were initialized + * Apply a callback to each registered function + * + * The callback will be called with the name of the namespace, and + * a reference to the registered function. + * + * @param callback */ -size_t Namespace::initialize(const std::string &parent, struct _zend_function_entry entries[]) +void Namespace::apply(const std::function<void(const std::string &ns, Function &func)> &callback) { - // keep iterator counter - int count = 0; - - // the namespace to use - std::string prefix = parent.size() ? parent + "\\" + _name : _name; - - // loop through the functions - for (auto &function : _functions) - { - // retrieve entry - zend_function_entry *entry = &entries[count++]; - - // let the function fill the entry - function->initialize(prefix, entry); - } + // loop through the functions, and apply the callback + for (auto &function : _functions) callback(_name, *function); - // loop through the namespace - for (auto &ns : _namespaces) - { - // let the namespace initialize - count += ns->initialize(prefix, &entries[count]); - } - - // done - return count; + // loop through the other namespaces + for (auto &ns : _namespaces) ns->apply([this, callback](const std::string &ns, Function &func) { + + // if this is the root namespace, we don't have to change the prefix + if (_name.size() == 0) return callback(ns, func); + + // construct a new prefix + // @todo this could be slightly inefficient + return callback(_name + "\\" + ns, func); + }); } /** - * Initialize the namespace after it was registered - * @param parent Parent namespace - * @param tsrm_ls + * Apply a callback to each registered class + * + * The callback will be called with the name of the namespace, and + * a reference to the registered class. + * + * @param callback */ -void Namespace::initialize(const std::string &parent TSRMLS_DC) +void Namespace::apply(const std::function<void(const std::string &ns, ClassBase &clss)> &callback) { - // the namespace to use - std::string prefix = parent.size() ? parent + "\\" + _name : _name; + // loop through the classes, and apply the callback + for (auto &c : _classes) callback(_name, *c); - // loop through the classes in this namespace - for (auto &c : _classes) c->implementation()->initialize(c.get(), prefix TSRMLS_CC); - - // and loop through the other namespaces - for (auto &n : _namespaces) n->initialize(prefix TSRMLS_CC); + // loop through the other namespaces + for (auto &ns : _namespaces) ns->apply([this, callback](const std::string &ns, ClassBase &clss) { + + // if this is the root namespace, we don't have to change the prefix + if (_name.size() == 0) return callback(ns, clss); + + // construct a new prefix + // @todo this could be slightly inefficient + return callback(_name + "\\" + ns, clss); + }); } - /** * End namespace */ |