diff options
Diffstat (limited to 'documentation')
-rw-r--r-- | documentation/dynamic-loading.html | 253 |
1 files changed, 253 insertions, 0 deletions
diff --git a/documentation/dynamic-loading.html b/documentation/dynamic-loading.html new file mode 100644 index 0000000..12ae965 --- /dev/null +++ b/documentation/dynamic-loading.html @@ -0,0 +1,253 @@ +<h1>Dynamic loading</h1> +<p> + Users who switch from PHP to C++ often ask whether it is more difficult + to manage a system if you use C++ code instead of PHP code. We must be honest + here: working with PHP is easier than working with C++. To activate a PHP + script, you for example don't need root access, and you can simply copy the script + to the web server. Deploying a native C++ extension requires more work than that: + you need to stop the webserver first, compile the extension, install it, and + then restart the web server. +</p> +<p> + On top of that, when an extension is deployed it is immediately active for + all websites that are hosted on the webserver. A deployed PHP script only + changes the behavior of a single website, but a deployed C++ extension affects + all sites. It is not really possible to activate an extension for only specific sites, + or to test a new version of an extension for just a single website, as the + extensions are shared by all PHP processes. If you really want to use + different extensions for different sites, you need multiple servers that all + have their own configuration. +</p> +<p> + Or you can use dynamic loading. +</p> +<p> + PHP has a builtin <a href="http://php.net/dl">dl()</a> + function that you can use to load extensions on the fly. This allows you + to call the 'dl("myextension.so")' function from a PHP script to load an + extension, so that an extension only works for a specific site. This + builtin 'dl()' function has some restrictions because of security + considerations (it would otherwise allow users to run arbitrary native code), + but if you're the only one who is in charge of a system, or when a server is not + shared by multiple organizations, you can use PHP-CPP to create a function + similar to 'dl()' that does not have this restriction. +</p> +<h2>Why is dl() restricted?</h2> +<p> + The dl() function is restricted because of security issues. When you use + dl(), you can only load extensions that are stored in the system + wide extensions directory, and not for loading extensions that users have + placed in other locations. A call to dl("/home/user/myextension.so") will + therefore fail, because "/home/user" is not the official extensions + directory. Why this restriction? +</p> +<p> + To understand this, one must first realize that in a normal PHP installation + PHP scripts are edited by users who do not have root access. On shared hosting + environments different users all run their own website on the same system. In + such a setup, it is an absolute no-go if one user can write a script or program + that has access to the data of others. With an unrestricted dl() function + however, exactly this would be possible. An unrestricted dl() call would + allow PHP programmers to write a native extension, store that in their home + directory or in the /tmp directory, and have it loaded by the webserver process. + They could then execute arbitrary code, and possibly + install loggers or other malicious code inside other people's websites. By + only allowing extensions from the system wide extensions directory to be loaded, + PHP ensures every dynamically loaded extension must at least have been installed + by the system administrator. +</p> +<p> + When you write your own extensions however - either directly on top of the Zend + API, or by using the PHP-CPP library - you already are in a position to write and execute + arbitrary code anyway. No need for security checks here. From you C/C++ code + you can do whatever you want. Would it not be cool if you could dynamically + load an extension based on the requirements of a site? One website needs + to be stable, and loads the well tested version 1.0 of your extension, while + a second website is more experimental, and loads version 2.0. You could be + running two versions of your extension on the same machine. +</p> +<h2>Thin loader extension</h2> +<p> + Imagine you're writing your own extension "MyExtension" that has many different + classes and functions, and for which you plan to bring out new releases all the time. + You do not want to deploy a new release in a "big bang" style, but you want to roll out + new versions slowly, one customer or one website at a time. How would you do this? +</p> +<p> + You start by developing a thin loader extension: the ExtensionLoader + extension. This extension has only one function: enable_my_extension() - which + takes the version number of your actual extension, and dynamically loads that version + of your extension. This is a simple extension (it only has one function) + that you install globally, and that you will probably never have to update. +</p> +<p> +<pre class="language-c++"><code> +/** + * Function to load an extension by its version number + * + * It takes one argument: the version number of your extension, + * and returns a boolean to indicate whether the extension was + * correctly loaded. + * + * @param params Vector of parameters + * @return boolean + */ +Php::Value enable_my_extension(Php::Parameters &params) +{ + // get version number + int version = params[0]; + + // construct pathname to your extension (this is for example + // /path/to/MyExtension.so.1 or /path/to/MyExtension.so.2) + std::string path = "/path/to/MyExtension.so." + std::to_string(version); + + // load the extension + return Php::dl(path); +} + +/** + * 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("ExtensionLoader", "1.0"); + + // the extension has one method + myExtension.add("enable_my_extension", enable_my_extension, { + Php::ByVal("version", Php::Type::Numeric) + }); + + // return the extension + return myExtension; + } +} +</code></pre> +</p> +<p> + The above code holds the full source code of the ExtensionLoader extension. + You can install this extension on your system, by copy'ing it to the global + php extensions directory and updating the php.ini files. +</p> +<p> + After you've installed this thin loader extension, you can write your actual + big extension full with classes and functions, and compile this extension + into *.so files: the first version you compile into MyExtension.so.1, + and later versions into MyExtension.so.2, MyExtension.so.3, and so on. For every new + release you introduce a new version number, and you copy these shared objects + to the /path/to directory (the same path hardcoded in the 'loader' extension + showed above). And although this is not the official PHP extensions directory, + such extensions can nevertheless be loaded byt the enable_my_extension() + function. +</p> +<p> + You do not have to copy the extensions into the PHP extension directory, nor + do you have to update the php.ini configuration. To activate an extension, + you simply need to call the enable_my_extension() function that was introduced + but he thin loader: +</p> +<p> +<pre class="language-php"><code> +<?php +// enable version 2 of the extension (this will load MyExtension.so.2) +if (!enable_my_extension(2)) die("Version 2 of extension is missing"); + +// from now on we can use classes and functions from version 2 of the extension +$object = new ClassFromMyExtension(); +$object->methodFromMyExtension(); + +// you get the idea... + +?> +</code></pre> +</p> +<p> + The thin loader that we showed above is still pretty secure. It is not possible + to run arbitrary code, or to open arbitrary *.so files. The worst thing that can + happen is that someone opens an extension with a wrong version number - which is + not disastrous at all. +</p> +<p> + But if you really trust the users on your system, you can easily adjust the thin + loader extension to allow other types of parameters too. In the most open + scenario, you could even write a function that allows users to literally + open every possible shared object file: +</p> +<p> +<pre class="language-c++"><code> +/** + * Function to load every possible extension by pathname + * + * It takes one argument: the filename of the PHP extension, and 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. 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 &params) +{ + // get extension name + std::string pathname = params[0]; + + // load the extension + return Php::dl(pathname); +} + +/** + * 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) + }); + + // return the extension + return myExtension; + } +} +</code></pre> +</p> +<p> + The above code would allow PHP scripts to dynamically load PHP extensions, + no matter where they are stored on the system: +</p> +<p> +<pre class="language-php"><code> +<?php +// load the C++ extension stored in the same directory as this file +if (!dl_unrestricted(__DIR__.'/MyExtension.so')) die("Extension could not be loaded"); + +// from now on we can use classes and functions from the extension +$object = new ClassFromMyExtension(); +$object->methodFromMyExtension(); + +?> +</code></pre> +</p> +<p> + The dl_unrestricted() function is an awesome function, but be careful here: + if you're the administrator of a shared hosting platform, you definitely + do not want to install it! +</p> |