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
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
|
/**
* 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::processStartup(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
return BOOL2SUCCESS(extension->initialize(module_number TSRMLS_CC));
}
/**
* 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::processShutdown(int type, int module_number TSRMLS_DC)
{
// get the extension
auto *extension = find(module_number TSRMLS_CC);
// done
return BOOL2SUCCESS(extension->shutdown(module_number TSRMLS_CC));
}
/**
* 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::processRequest(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::processIdle(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);
}
/**
* Function that is called when the PHP engine initializes with a different PHP-CPP
* version for the libphpcpp.so file than the version the extension was compiled for
* @param type Module type
* @param number Module number
* @param tsrm_ls
* @return int 0 on success
*/
int ExtensionImpl::processMismatch(int type, int module_number TSRMLS_DC)
{
// get the extension
auto *extension = find(module_number TSRMLS_CC);
// report a warning
warning << "Version mismatch between PHP-CPP and extension " << extension->name() << " " << extension->version() << " (recompile needed?)" << std::endl;
// 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
* @param apiversion API version number
*/
ExtensionImpl::ExtensionImpl(Extension *data, const char *name, const char *version, int apiversion) :
ExtensionBase(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, will be filled by Zend engine
_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::processStartup; // startup function for the whole extension
_entry.module_shutdown_func = &ExtensionImpl::processShutdown; // shutdown function for the whole extension
_entry.request_startup_func = &ExtensionImpl::processRequest; // startup function per request
_entry.request_shutdown_func = &ExtensionImpl::processIdle; // 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
// everything is ok if the api versions match
if (apiversion == PHPCPP_API_VERSION) return;
// mismatch between api versions, the extension is invalid, we use a
// different startup function to report to the user
_entry.module_startup_func = &ExtensionImpl::processMismatch;
// the other callback functions are no longer necessary
_entry.module_shutdown_func = nullptr;
_entry.request_startup_func = nullptr;
_entry.request_shutdown_func = nullptr;
}
/**
* Destructor
*/
ExtensionImpl::~ExtensionImpl()
{
// deallocate the php.ini entries
if (_ini) delete[] _ini;
// deallocate functions
if (_entry.functions) delete[] _entry.functions;
}
/**
* The extension name
* @return const char *
*/
const char *ExtensionImpl::name() const
{
// name is stored in the struct
return _entry.name;
}
/**
* The extension version
* @return const char *
*/
const char *ExtensionImpl::version() const
{
// version is stored in the struct
return _entry.version;
}
/**
* Retrieve the module entry
* @return zend_module_entry
*/
zend_module_entry *ExtensionImpl::module()
{
// check if functions were already defined
if (_entry.functions) return &_entry;
// if the 'processMismatch' function is installed, the API version is wrong,
// and nothing should be initialized
if (_entry.module_startup_func == &ExtensionImpl::processMismatch) 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->functions([&i, entries](const std::string &prefix, NativeFunction &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 module_number
* @param tsrm_ls
* @return bool
*/
bool ExtensionImpl::initialize(int module_number TSRMLS_DC)
{
// array contains ini settings
_ini = new zend_ini_entry[_data->iniVariables()+1];
// the entry that we're filling
int i = 0;
// Fill the php.ini entries
_data->iniVariables([this, &i, module_number](Ini &ini) {
// initialize the function
zend_ini_entry *entry = &_ini[i];
// fill the property
ini.fill(entry, module_number);
// move on to the next iteration
i++;
});
// last entry should be set to all zero's
memset(&_ini[i], 0, sizeof(zend_ini_entry));
// register ini entries in Zend core
zend_register_ini_entries(_ini, module_number TSRMLS_CC);
// the constants are registered after the module is ready
_data->constants([module_number TSRMLS_CC](const std::string &prefix, Constant &c) {
// forward to implementation class
c.implementation()->initialize(prefix, module_number TSRMLS_CC);
});
// we also need to register each class, find out all classes
_data->classes([TSRMLS_C](const std::string &prefix, ClassBase &c) {
// forward to implementation class
c.implementation()->initialize(&c, prefix TSRMLS_CC);
});
// initialize the PhpCpp::Functor class
Functor::initialize(TSRMLS_C);
// remember that we're initialized (when you use "apache reload" it is
// possible that the processStartup() method is called more than once)
_locked = true;
// is the callback registered?
if (_onStartup) _onStartup();
// done
return true;
}
/**
* Function that is called when the extension shuts down
* @param module_number
* @param tsrmls
* @return bool
*/
bool ExtensionImpl::shutdown(int module_number TSRMLS_DC)
{
// unregister the ini entries
zend_unregister_ini_entries(module_number TSRMLS_CC);
// destruct the ini entries
if (_ini) delete[] _ini;
// forget the ini entries
_ini = nullptr;
// shutdown the functor class
Functor::shutdown(TSRMLS_C);
// is the callback registered?
if (_onShutdown) _onShutdown();
// done
return true;
}
/**
* End of namespace
*/
}
|