Dynamically convert procedural PHP to object-based

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

georgeoc
Forum Contributor
Posts: 166
Joined: Wed Aug 09, 2006 4:21 pm
Location: London, UK

Dynamically convert procedural PHP to object-based

Post 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!
Begby
Forum Regular
Posts: 575
Joined: Wed Dec 13, 2006 10:28 am

Post 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.
Bon Bon
Forum Commoner
Posts: 66
Joined: Sat Mar 13, 2004 10:21 pm
Location: UK

Post 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.
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Post 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)
georgeoc
Forum Contributor
Posts: 166
Joined: Wed Aug 09, 2006 4:21 pm
Location: London, UK

Post 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!
georgeoc
Forum Contributor
Posts: 166
Joined: Wed Aug 09, 2006 4:21 pm
Location: London, UK

Post 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.
User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Post 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).
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Post 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.
georgeoc
Forum Contributor
Posts: 166
Joined: Wed Aug 09, 2006 4:21 pm
Location: London, UK

Post by georgeoc »

@Weirdan - for you information, your suggestion is working absolutely PERFECTLY. Thanks so much for introducing me to a new PHP concept!
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Post 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)
jason
Site Admin
Posts: 1767
Joined: Thu Apr 18, 2002 3:14 pm
Location: Montreal, CA
Contact:

Post 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.
georgeoc
Forum Contributor
Posts: 166
Joined: Wed Aug 09, 2006 4:21 pm
Location: London, UK

Post 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)
User avatar
stereofrog
Forum Contributor
Posts: 386
Joined: Mon Dec 04, 2006 6:10 am

Post 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?
georgeoc
Forum Contributor
Posts: 166
Joined: Wed Aug 09, 2006 4:21 pm
Location: London, UK

Post 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
User avatar
stereofrog
Forum Contributor
Posts: 386
Joined: Mon Dec 04, 2006 6:10 am

Post 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. ;)
Post Reply