summaryrefslogtreecommitdiff
path: root/documentation/dynamic-loading.html
blob: f57a997767c59441a87df75807ca478c3eb287e9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
<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 &amp;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>
&lt;?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...

?&gt;
</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 &amp;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>
&lt;?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();

?&gt;
</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>
<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>