summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEmiel Bruijntjes <emiel.bruijntjes@copernica.com>2015-04-13 08:54:55 +0200
committerEmiel Bruijntjes <emiel.bruijntjes@copernica.com>2015-04-13 08:54:55 +0200
commit6dcadfb26de2ce9117f58986d543c521143df1c2 (patch)
tree92dd8efd6de0a4d9d837908caa4232ccd59bff80
parent6328204053b26585cc0a590815f912fb8b153f41 (diff)
parentfaa7df504380295296e3349e9360732d750554c3 (diff)
Merge branch 'master' of https://github.com/CopernicaMarketingSoftware/PHP-CPP
-rw-r--r--Examples/DlUnrestricted/dlunrestricted.cpp5
-rw-r--r--documentation/dynamic-loading.html102
-rw-r--r--include/call.h6
-rw-r--r--zend/eval.cpp9
-rw-r--r--zend/executestate.h18
-rw-r--r--zend/hiddenpointer.h10
-rw-r--r--zend/module.cpp30
-rw-r--r--zend/module.h78
-rw-r--r--zend/opcodes.h2
9 files changed, 247 insertions, 13 deletions
diff --git a/Examples/DlUnrestricted/dlunrestricted.cpp b/Examples/DlUnrestricted/dlunrestricted.cpp
index bdc7cae..a7ec7ed 100644
--- a/Examples/DlUnrestricted/dlunrestricted.cpp
+++ b/Examples/DlUnrestricted/dlunrestricted.cpp
@@ -29,8 +29,11 @@ Php::Value dl_unrestricted(Php::Parameters &params)
// get extension name
std::string pathname = params[0];
+ // should it be opened persistently?
+ bool persistent = params.size() > 1 ? params[1].boolValue() : false;
+
// load the extension
- return Php::dl(pathname);
+ return Php::dl(pathname, persistent);
}
/**
diff --git a/documentation/dynamic-loading.html b/documentation/dynamic-loading.html
index 12ae965..f57a997 100644
--- a/documentation/dynamic-loading.html
+++ b/documentation/dynamic-loading.html
@@ -251,3 +251,105 @@ $object->methodFromMyExtension();
if you're the administrator of a shared hosting platform, you definitely
do not want to install it!
</p>
+<h2 id="persistent">Persistent extensions</h2>
+<p>
+ A dynamically loaded extension is automatically unloaded at the end of the
+ page view. If a subsequent pageview also dynamically loads the same extension,
+ it will start with a completely new and fresh environment. If you want to
+ write an extension that uses static data or static resources (think of a persistent
+ database connection, or a worker thread that processes tasks) this may not
+ always be the desired behavior. You want to keep the database connection active,
+ or the thread running, also after the extension is unloaded.
+</p>
+<p>
+ To overcome this, the Php::dl() function comes with a second boolean parameter
+ that you can use to specify whether you want to load the extension persistently,
+ or only for that specific pageview.
+</p>
+<p>
+ Notice that if you set this parameter to true, the only thing that persists is
+ the data <i>in</i> an extension. In subsequent pageviews you will still have to
+ load the extension to activate the functions and classes in it, even if you had
+ already loaded the extension persistently in an earlier pageview. But because
+ the extension was already loaded before, the static data in it (like the database
+ connection or the thread) is preserved.
+</p>
+<p>
+ The dl_unrestricted() function that we demonstrated above can be modified to include
+ this persistent parameter:
+</p>
+<p>
+<pre class="language-c++"><code>
+/**
+ * Function to load every possible extension by pathname
+ *
+ * It takes two arguments: the filename of the PHP extension, and a boolean to
+ * specify whether the extension data should be kept in memory. It returns a
+ * boolean to indicate whether the extension was correctly loaded.
+ *
+ * This function goes further than the original PHP dl() fuction, because
+ * it does not check whether the passed in extension object is stored in the
+ * right directory, and because it allows persistent loading of extensions.
+ * Literally every possible extension, also local ones created by end users,
+ * can be loaded.
+ *
+ * @param params Vector of parameters
+ * @return boolean
+ */
+Php::Value dl_unrestricted(Php::Parameters &amp;params)
+{
+ // get extension name
+ std::string pathname = params[0];
+
+ // persistent setting
+ bool persistent = params.size() > 1 ? params[1].boolValue() : false;
+
+ // load the extension
+ return Php::dl(pathname, persistent);
+}
+
+/**
+ * Switch to C context to ensure that the get_module() function
+ * is callable by C programs (which the Zend engine is)
+ */
+extern "C" {
+ /**
+ * Startup function that is called by the Zend engine
+ * to retrieve all information about the extension
+ * @return void*
+ */
+ PHPCPP_EXPORT void *get_module() {
+ // create static instance of the extension object
+ static Php::Extension myExtension("load_extension", "1.0");
+
+ // the extension has one method
+ myExtension.add("dl_unrestricted", dl_unrestricted, {
+ Php::ByVal("pathname", Php::Type::String),
+ Php::ByVal("persistent", Php::Type::Bool, false)
+ });
+
+ // return the extension
+ return myExtension;
+ }
+}
+</code></pre>
+</p>
+<p>
+ And this extension allows us to dynamically load extensions, while preserving
+ persistent data <i>inside</i> the extension:
+</p>
+<p>
+<pre class="language-php"><code>
+&lt;?php
+// load the C++ extension stored in the same directory as this file, the
+// extension is persistently loaded, so it may use persistent data like
+// database connections and so on.
+if (!dl_unrestricted(__DIR__.'/MyExtension.so', true)) die("Extension could not be loaded");
+
+// from now on we can use classes and functions from the extension
+$object = new ClassFromMyExtension();
+$object->methodFromMyExtension();
+
+?&gt;
+</code></pre>
+</p>
diff --git a/include/call.h b/include/call.h
index 36574ce..094c7d2 100644
--- a/include/call.h
+++ b/include/call.h
@@ -27,9 +27,9 @@ extern PHPCPP_EXPORT bool define(const std::string &name, const Value &value
extern PHPCPP_EXPORT bool defined(const char *constant);
extern PHPCPP_EXPORT bool defined(const char *constant, size_t size);
extern PHPCPP_EXPORT bool defined(const std::string &constant);
-extern PHPCPP_EXPORT bool dl(const char *filename);
-inline PHPCPP_EXPORT bool dl(const std::string &filename) { return dl(filename.c_str()); }
-inline PHPCPP_EXPORT bool dl(const Value &filename) { return dl(filename.rawValue()); }
+extern PHPCPP_EXPORT bool dl(const char *filename, bool persistent = false);
+inline PHPCPP_EXPORT bool dl(const std::string &filename, bool persistent = false) { return dl(filename.c_str(), persistent); }
+inline PHPCPP_EXPORT bool dl(const Value &filename, bool persistent = false) { return dl(filename.rawValue(), persistent); }
extern PHPCPP_EXPORT Value eval(const char *phpCode);
inline PHPCPP_EXPORT Value eval(const std::string &phpCode) { return eval(phpCode.c_str()); }
extern PHPCPP_EXPORT Value include(const char *filename);
diff --git a/zend/eval.cpp b/zend/eval.cpp
index 7ab4957..6d043b1 100644
--- a/zend/eval.cpp
+++ b/zend/eval.cpp
@@ -93,12 +93,17 @@ Value require_once(const char *filename)
/**
* Implementation of the dl() function - activate a different PHP extension
+ *
+ * If you open an extension persistently, the static variables inside it are
+ * kept open for as long as apache runs.
+ *
* @param filename
+ * @param persistent
*/
-bool dl(const char *filename)
+bool dl(const char *filename, bool persistent)
{
// create the module
- Module module(filename);
+ Module module(filename, persistent);
// start the module
return module.start();
diff --git a/zend/executestate.h b/zend/executestate.h
index 8e33b16..d71a252 100644
--- a/zend/executestate.h
+++ b/zend/executestate.h
@@ -26,11 +26,18 @@ class ExecuteState
private:
/**
* All the original settings
+ * @var mixed
*/
zend_op_array *_active_op_array;
zval **_return_value_ptr_ptr;
zend_op **_opline_ptr;
int _interactive;
+
+ /**
+ * The new value for 'no-extensions'
+ * @var int
+ */
+ int _no_extensions;
#ifdef ZTS
/**
@@ -42,15 +49,22 @@ private:
public:
/**
+ * No trivial constructor
+ */
+ ExecuteState() = delete;
+
+ /**
* Constructor
+ * @param no_extensions
*/
- ExecuteState(TSRMLS_D)
+ ExecuteState(int no_extensions TSRMLS_DC)
{
// 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);
+ _no_extensions = no_extensions;
#ifdef ZTS
// copy tsrm_ls param
@@ -65,7 +79,7 @@ public:
{
// restore all settings
CG(interactive) = _interactive;
- EG(no_extensions) = 0;
+ EG(no_extensions) = _no_extensions;
EG(opline_ptr) = _opline_ptr;
EG(active_op_array) = _active_op_array;
EG(return_value_ptr_ptr) = _return_value_ptr_ptr;
diff --git a/zend/hiddenpointer.h b/zend/hiddenpointer.h
index d2636e3..147886e 100644
--- a/zend/hiddenpointer.h
+++ b/zend/hiddenpointer.h
@@ -38,8 +38,14 @@ public:
// copy pointer into the buffer
memcpy(buffer, &pointer, sizeof(Type *));
- // copy the name into the buffer
- memcpy(buffer + sizeof(Type *), text, size + 1);
+ // copy the name into the buffer, making it lower case at the same time
+ // (php function names are case insensitive, and must even be lowercase
+ // because all the lookups are done in lowercase)
+ for (int i = 0; i < size + 1; i++)
+ {
+ // convert char the lower case
+ buffer[sizeof(Type *) + i] = tolower(text[i]);
+ }
// store in member
_buffer = buffer;
diff --git a/zend/module.cpp b/zend/module.cpp
new file mode 100644
index 0000000..46d03d8
--- /dev/null
+++ b/zend/module.cpp
@@ -0,0 +1,30 @@
+/**
+ * Module.cpp
+ *
+ * Module implementation file
+ *
+ * @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
+ * @copyright 2015 Copernica BV
+ */
+
+/**
+ * Dependencies
+ */
+#include "includes.h"
+
+/**
+ * Set up namespace
+ */
+namespace Php {
+
+/**
+ * The persistent handles
+ * @var Module::Persistent
+ */
+Module::Persistent Module::_persistent;
+
+/**
+ * End of namespace
+ */
+}
+
diff --git a/zend/module.h b/zend/module.h
index bb36810..4431355 100644
--- a/zend/module.h
+++ b/zend/module.h
@@ -43,7 +43,7 @@ private:
* @var zend_module_entry
*/
zend_module_entry *_entry = nullptr;
-
+
#ifdef ZTS
/**
* When in thread safety mode, we also keep track of the TSRM_LS var
@@ -52,12 +52,82 @@ private:
void ***tsrm_ls;
#endif
+ /**
+ * Internal helper class with persistent modules
+ */
+ class Persistent
+ {
+ private:
+ /**
+ * The set of handles
+ * @var std::set
+ */
+ std::set<void*> _handles;
+
+ public:
+ /**
+ * Constructor
+ */
+ Persistent() {}
+
+ /**
+ * Destructor
+ */
+ virtual ~Persistent()
+ {
+ // remove all handles
+ while (!_handles.empty())
+ {
+ // get first handle
+ auto iter = _handles.begin();
+
+ // remove the handle
+ DL_UNLOAD(*iter);
+
+ // remove from set
+ _handles.erase(iter);
+ }
+ }
+
+ /**
+ * Check whether a handle is already persistently opened
+ * @param handle
+ * @return bool
+ */
+ bool contains(void *handle) const
+ {
+ return _handles.find(handle) != _handles.end();
+ }
+
+ /**
+ * Add a library persistently
+ * @param module
+ */
+ void add(const char *module)
+ {
+ // insert the handle
+ _handles.insert(DL_LOAD(module));
+ }
+ };
+
+ /**
+ * All persistent modules
+ * @var Persistent
+ */
+ static Persistent _persistent;
+
public:
/**
* Constructor
+ *
+ * A module can be loaded persistently. This means that the variables in
+ * the module will keep in scope for as long as Apache runs, even though
+ * the extension is not active in other page views
+ *
* @param module Name of the module
+ * @param persistent Should it be loaded persistently
*/
- Module(const char *module)
+ Module(const char *module, bool persistent)
{
#ifdef ZTS
// fetch multi-threading thing
@@ -76,6 +146,10 @@ public:
// handle should be valid
if (!_handle) return;
+ // if we have to open it persistently, we open it for a second time so that
+ // the refcounter always stays 1 or higher
+ if (persistent && !_persistent.contains(_handle)) _persistent.add(module);
+
// we have to call the get_module() function
Symbol<zend_module_entry*()> get_module(_handle, "get_module");
diff --git a/zend/opcodes.h b/zend/opcodes.h
index 7e6d1ec..4e78083 100644
--- a/zend/opcodes.h
+++ b/zend/opcodes.h
@@ -74,7 +74,7 @@ public:
// 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(TSRMLS_C);
+ ExecuteState execState(0 TSRMLS_CC);
// 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