summaryrefslogtreecommitdiff
path: root/zend/extensionimpl.cpp
blob: d313498866621d9c41670d7bd622b1e8f3985156 (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
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
 */
}