Simulating require_once within an extension

php.internals

Mo McRoberts

19 years ago
Hi list, Apologies if I'm sending this to the wrong list; I couldn't see another which was more appropriate on the PHP Mailing Lists page. I'm developing a PHP extension for which part of the functionality can be described in a nutshell as: * at request start-up time, build a map of identifiers to path-names, read from a configuration file; * whilst a user script is being processed, a function provided by the extension can be called to add, remove or modify items in the mapping; * a user script can call a function, passing it an identifier in the map, and the extension should simulate require_once being called with the corresponding pathname (with some transformation applied). For example, if the configuration file specified that 'foo' mapped to /www/common/foo, calling the above function with a parameter of 'foo' might simulate require_once('/www/common/foo/script.php') (where the transformation applied in this case is appending 'script.php' to the given pathname). A prototype implementation written in PHP itself works well enough, but obviously there are scoping issues with such an implementation (i.e., any scripts included are included within the scope of the function, not the caller) which I want to avoid through the use of an extension. Obviously, much of this is pretty trivial and straightforward. My problem is the actual simulation of require_once itself. As it's a language intrinsic, there's no simply-exposed API for performing the same action. Digging through the PHP sources, I've come across zend_execute_scripts(), which seems to fit the bill, although there's no documentation and very few examples of it being used outside of the PHP engine itself. From skimming as many bits of the PHP sources that actually use zend_execute_scripts() that I could find, the code I've come up with isn't hugely dissimilar to this: static int do_required(const char *filename TSRMLS_DC) { int r; zend_file_handle zh; if(SUCCESS != (r = zend_stream_open(filename, &zh TSRMLS_CC))) { return r; } if(NULL == zh.opened_path) { zh.opened_path = estrdup(filename); } if(zend_hash_add_empty_element(&EG(included_files), zh.opened_path, strlen(zh.opened_path) + 1) == SUCCESS) { r = zend_execute_scripts(ZEND_REQUIRE_ONCE TSRMLS_CC, NULL, 1, &zh); } zend_stream_close(TSRMLS_CC); return r; } Simple enough, right? Wrong. I'm hoping at this point that somebody who knows the Zend internals pretty well will immediately spot which things I'm not initialising, saving/restoring, or happen to be double-freeing at this point, because I'm at a loss. My symptoms are this: * If calls to this function are nested, and an inner call results in zend_stream_open() failing, I get faults in zend_get_executed_lineno(), suggesting corruption somewhere. * If I save, reset and restore return_value_ptr_ptr, active_op_array, opline_ptr before doing anything, things seem better, but the Warning message reported when the file can't be opened gives the error location as [no active file] on line 0, which is less than ideal. * If I only save/reset/restore around the call to zend_get_executed_lineno() itself, things seem to work until I get as far as installing the extension for my Apache 2.2 module build of PHP: at which point, as soon as there's some nesting things start to go bad (errors reported or not). Removing the final zend_stream_close() call stops Apache from dying, but I strongly suspect that I'm just masking the problem rather than fixing it. So, my questions are: what am I doing wrong, and is there a better way to accomplish the same thing? I considered evaluating a script instead of trying to simulate require_once itself, but that seemed incredibly kludgy. Any help appreciated. Thanks, Mo.

Jochem Maas

19 years ago
I suspect you could implement the desired functionality using http://php.net/manual/en/function.stream-wrapper-register.php although I can't find any info on whether include/require actually work with registered stream wrappers .. maybe one of the devs could confirm/deny whether this is possible. which would allow you to do something like this (assuming it is possible to do): require_once 'momcr://foo/script.php'; Mo McRoberts wrote:

Mo McRoberts

19 years ago
Hi Jochem, On 24-Feb-2007, at 19:09, Jochem Maas wrote:
> I suspect you could implement the desired functionality > using http://php.net/manual/en/function.stream-wrapper-register.php > > although I can't find any info on whether include/require > actually work with registered stream wrappers .. maybe one of the > devs could confirm/deny whether this is possible. > > which would allow you to do something like this (assuming it is > possible to do): > > require_once 'momcr://foo/script.php';
That's a good idea, but unfortunately won't work for me--the ‘require’ step is the first part of the function, which will then go on to do other things (the return value from the function will end up being a class instance—and it will be irrespective of whether the includee had previously been included or not); it's just the case that this is as far as I've got with C implementation of it before running into problems! Mo.

LAUPRETRE François (P)

19 years ago
Sorry, I can't help with your C extension but I can give you a solution I implemented in PHP for the same needs. I did using two mechanisms : - A stream wrapper to allow require_once to reference a virtual path. Not required if you require 'real' files. In PHK, I need it because I implement a virtual file system. - A 'tunneling' function like yours, used through an eval(). I agree it is not very elegant but, unfortunately, there is no 'uplevel' feature in PHP (could be an interesting addition, along with a who_called_me()). This function generally returns a 'require_once <stream-wrapped path>' string, but as we eval()uate the result, it can return any code, even a 'throw Exception', to be thrown from the calling scope. If you want to see more about this example, download the PHK_Creator building kit from http://www.tekwire.net/joomla/download/phk, and search for the 'web_tunnel' function in file PHK.php. Regards François