Page 1 of 2

Dynamically convert procedural PHP to object-based

Posted: Tue Sep 04, 2007 6:16 am
by georgeoc
Hi,

Slightly odd question this, and I may be going down totally the wrong path. I am building a PHP 'message centre', with modules that retrieve and/or produce messages in proprietary format. For example, one possible chain of events is that the POP3 module will retrieve emails from a mail box, convert them to a generic message format and pass them to a phpBB module to insert into a forum.

(N.B. I will be making a phpBB3 module in due course which I imagine will be easier, but for now it's phpBB2 as that's much more commonly used at this stage).

Because of the way the phpBB code is written (need I mention globals?), there are complications with using the phpBB code to help with inserting posts. Basically, either I include the phpBB function files into the scope of my application, and have to deal with the consequences of function-name collisions, uncontrollable database objects and global variables, or I copy all the functions I need into my own source files, using a more useful OOP approach.

I thought of a third possibility, which would be to parse the phpBB function files, find the functions I need and rewrite them dynamically on the fly, changing global variables to class properties and function calls to method calls.

Perhaps this is stupid, but it does have a few advantages:

- reduces the size of my application as I don't include phpBB files in my source code (and remember, I will eventually have any number of different modules, all of which might well suffer from similar problems)
- Easier to keep my app up to date with the phpBB source code, as long as I make the parser intelligently. The phpBB files will be updated by the end user, so hopefully no changes will need to be made to my code.

and an obvious disadvantage:

- I'll need to run the parser every time my application needs to make a phpBB post. This seems like a ridiculous waste of resources.


Any thoughts? Are there examples available of people parsing and modifying PHP source code from other applications on the fly?



Thanks for any help!

Posted: Tue Sep 04, 2007 6:57 am
by Begby
I have to say, without a doubt, that this is impossible to do.

The reason is that everyone writes code so different that it would be impossible to convert something to OO from procedural dynamically.

Posted: Tue Sep 04, 2007 7:42 am
by Bon Bon
It is not impossible, but it might not be entirely accurate, a lot of algorithm will be needed and a high level of understanding data, memory and PHP will be needed to get this right.

Personally I would not bother.

Posted: Tue Sep 04, 2007 4:32 pm
by Weirdan
Not in PHP itself, but there are PHP -> AST parsers. Having abstract syntax tree for a script you can apply modifications to it and output modified result.

One of these parsers is phpc (though it's written in C++). Another would be a parser used by PHPAspect internally.

As a clever trick I may suggest you to implement a custom stream wrapper using stream_wrapper_register which would rewrite the source code on the fly (before it gets to PHP interpreter)

Posted: Tue Sep 04, 2007 5:54 pm
by georgeoc
Weirdan wrote:As a clever trick I may suggest you to implement a custom stream wrapper using stream_wrapper_register which would rewrite the source code on the fly (before it gets to PHP interpreter)
Wow - this is a whole new concept for me! How exciting!

I understand the very basic example given here, but I'm not quite sure how I would use such a class to do what you suggest. Could you just get me started along the right lines?

I guess I should pass the path to the phpBB file in the stream URL:

Code: Select all

fopen('myfunction://path_to_phpBB_file.php');
But then when do I read the file contents and do the parsing (in the stream_open() or the stream_read() method?) and, when I am finished, how do I pass the new PHP code to the interpreter ready to be used?

Sorry, basic questions but I hope you can give me a few pointers!

Posted: Tue Sep 04, 2007 6:35 pm
by georgeoc
OK, I'm getting somewhere. Is this the right way to start:

Code: Select all

require_once 'myfunction://path_to_phpBB_file.php';

I didn't realise I could use so many of the PHP file functions - I thought I was limited to fopen(), etc.

Posted: Wed Sep 05, 2007 1:08 am
by s.dot
You can use file_get_contents() (not making an http request) to get the raw php code of the phpbb functions you want, then unset() the rest from memory, and assign them to your method scope. Since you would be retreiving the PHP code as a string, use of eval() would be needed (although subject to files being edited and security risks).

Posted: Thu Sep 06, 2007 4:47 pm
by Weirdan
not making an http request
He doesn't make any http requests. 'something://' is a way to specify you're requesting resource using 'something' stream wrapper. http just happens to be one of these wrappers (more are available, including zip://, phar://, ftp://, string://) and custom wrappers (implemented either in extensions or by userland php code) may be registering, giving you even more flexibility over what are you getting from functions and language constructs operating on streams.

Posted: Thu Sep 06, 2007 4:52 pm
by georgeoc
@Weirdan - for you information, your suggestion is working absolutely PERFECTLY. Thanks so much for introducing me to a new PHP concept!

Posted: Fri Sep 07, 2007 3:11 am
by Weirdan
Glad it worked for you... originally this idea is from this paper by Sebastian Bergmann and Günter Kniesel on GAP (Generic Aspects for Php)

Posted: Tue Sep 11, 2007 2:50 pm
by jason
georgeoc: Would you mind sharing the final result of this with us? I'd be interested to see what you came up with.

Posted: Thu Sep 13, 2007 7:49 pm
by georgeoc
Sorry - been away for a few days and missed your reply.

The solution inspired by Weirdan's 'clever trick' seems to be working beautifully. In short, what I do is this:
  • I use a helper class called m2f_external_interface
  • Create a instance of the class in the var $external. The constructor sets up a stream handler using stream_wrapper_register ( string $protocol, string $classname ), where $protocol is an arbitrary string (not important what you use, just needs to be unique for each instance of $external) and $classname is the class you will use to parse and transform the PHP files.
  • Using setters, pass in an array of the functions I want to parse, any constants that are required by the code and any global variables (phpBB uses these in almost every function!)
  • Then run $external->load('/path/to/file') for every file I need to parse. Here's where the magic happens. I form the arrays for required functions, constants and globals, and the filepath to parse, into a query string using http_build_query, then initiate the parser like this: include($protocol . '://spacer?' . $query). I found I needed to add the spacer because of complications with the way parse_url() works with UNIX and Windows URIs.
  • Calling include() using the magic protocol will initiate the parser defined in $classname. See the comprehensive template here for a guide to creating your own parser.
  • In the parser I define a prefix $prefix to be added to all function and class names, as well as global variables.
  • After parsing the URI I can get the arrays of function names, etc into local scope.
  • foreach ($query['globals'] as $key => $var) $GLOBALS[$prefix][$key] = $var;
  • In short, I read the file into memory, get the functions I need into a buffer (I use token_get_all() - see the manual page for hints on getting code blocks from tokens), add the prefix to all function and class names (declarations and usage), find examples of global $var; and replace corresponding instances of $var with $GLOBALS[$prefix]['var'] , and replace constants with the runtime values from the constants array.
  • A new PHP file is built up from these function and class declarations, and is passed back to the stream handler using the stream_read() method.
  • I now have all the phpBB functions defined with a custom prefix. Within those functions, any globals, constants and further function calls will also be under my control. I even pass a mock db object in as a global (that's the phpBB technique), trapping all db calls phpBB makes and using my own db object to handle them. So I can guarantee that any errors in the phpBB functions will be handled gracefully by my parser.
  • The next step will be to cache the results of the parse in a static file, which can be included on subsequent requests to prevent all the overheads.

Great fun! I just got all my tests to pass this evening, and I can make a post to phpBB using function calls like the following:

Code: Select all

$this->_external->call_func('user_notification', array($mode, $post_data, $subject, $forum_id, $topic_id, $post_id, $notify_user));
...where call_func() simply returns:

Code: Select all

call_user_func_array($this->_prefix . $function, $args)

Posted: Fri Sep 14, 2007 4:51 am
by stereofrog
Care to explain why

Code: Select all

include "blah://something";
is any better than

Code: Select all

include_blah("something");
in other words, why do you need a stream wrapper here?

Posted: Fri Sep 14, 2007 5:20 am
by georgeoc
The alternative would be to transform the code from the files and send it to the PHP interpreter using eval(), right? Well, I admit I hadn't considered that, but I am very happy with my solution which I consider quite elegant. I've learnt a whole new section of the PHP manual, which must count for something!

If there's another method you have in mind, I'd be interested to hear that too!


"If eval() is the answer, you're almost certainly asking the wrong question." -- Rasmus Lerdorf

Posted: Fri Sep 14, 2007 7:48 am
by stereofrog
Well, internally include() is just a combination of file_get_contents + eval, so eval might be"wrong", but this is what you use every time you're using include and require.

But I agree, what we learn on the way is sometimes more important than what we achieve.

"The movement is everything, the goal is nothing" -- someone. ;)