PHP4 + PHP5 compatible overloading

While I don’t necessarily enjoy PHP, it’s not such a terrible language. At least for version 5. Lately I’ve been working on a project called ‘migrations’ which is essentially a PHP utility to generate / track / apply changes to a database in a similar fashion to the migrations system in Ruby on Rails. Of course, it’s got more magic than migrations (only because we know we’re going to use subversion) and it’s got to work with PHP4.

Let’s get back to the subject though. When writing any type of class that needs to be polymorphic or ‘dynamic’ in a sense that method calls can be made when a pre-defined method doesn’t exist, then ‘overloading’ is the magic PHP provides to satisfy your needs. As a note, polymorphism is simply the ability of a method to take varying parameters or by which a different method will be executed based on the types and/or quantity of methods required. For example, you could have a different method defined for function($array) than function($string). PHP doesn’t care about types and so doesn’t support polymorphism per se but by using overloading you can essentially accomplish the same thing. In my case however, I’m simply wanting to allow execution of non-existent methods against on object. The purpose is that I want to be able to log each method call and it’s result in order to track what’s going on. You’ll quickly see what I mean in the examples.

PHP5 Basic Example:

class dynamicClass {
    function __call($method, $arguments) {
        trigger_error('Call to undefined method ' . __CLASS__ . '::' . $method . '()', E_USER_ERROR);

Now have a class in PHP5 with overloading enabled. Without trigger_error, PHP will always think that you’re calling a valid method! In PHP5, there is no way to tell the interpreter the called method is invalid and you are responsible for triggering an error.

PHP4 Basic Example:

class dynamicClass {
    function __call($method, $arguments, &$return) {
        if($method == __CLASS__)
            return true;

        trigger_error('Call to undefined method ' . __CLASS__ . '::' . $method . '()', E_USER_ERROR);

In PHP4, overloading was experimental for awhile and is now not well documented because it changed so much in PHP5. The first two lines of the __call method exist because the class doesn’t have a constructor defined but we don’t want to return false or null – if we do – PHP will throw a warning that we made a call to an undefined method, as it tries to call the non-existent constructor. Without overloading PHP4 ignores non-existent constructors, however, since we’ve overloaded and the constructor doesn’t already exist – PHP4 hits __call. This is different from PHP5 wherein PHP5 will not try to make an overload call for any constructor. This is likely because in PHP5 constructors or pseudo-static methods (they are not called within the context of an object even though the constructor has access to the object – this is why you get to use the $this variable even in a static context – if you couldn’t PHP5 constructors wouldn’t work ;) )

So between these two examples we can see some obvious limitations.

  1. don’t use overloading for anything regarding constructors
  2. php4 uses &$return declaration for return, php5 uses return()
  3. php4 is the exception, not php5; php5 autoload will work just fine
  4. in php4, you’re class is limited to being declared from within an eval() block

This makes for complications if you want to create a class with overloading which will work both in PHP4 and PHP5. There is the safe way, not-so-safe way, and the somewhat-safe way that’s a little slower! First, I’ll show you the not-so-safe way which I’ll be using simply because this is a utility and not something I expect someone to modify and secondly because integration tests tell me if/when something breaks. If you don’t write tests I would highly recommend the ‘safer’ way.

File – dynamicOverloaded.class.php:

class dynamicOverloaded {
    function __call($method, $arguments /*PHP4, &$return*/) {
        /*PHP4if($method == __CLASS__) return true;*/

        // You're overloading logic

        $return = $resultFromYourMagic;
        return ($return ? $return : true);

You maybe asking yourself now “wtf” or “how is that supposed to work?” and you would be right – it’s not going to work yet. In order to make this work, you can’t require() or include(), or otherwise execute this file. Unless of course, the version of PHP is >= 5, in either case, the following code will do the trick.

The not-so-safe way:

if(substr(PHP_VERSION, 0, strrpos(PHP_VERSION, '.')) < 5)
    eval('?>' . preg_replace('|/\*PHP4(.*?)\*/|', '\\1', file_get_contents('path/to/dynamicOverloaded.class.php')) );

As with any php script, you have to include it. But in this case we’re eval()’ing the contents of the file instead of including it when using PHP4. Before eval we’re stripping /*PHP4 */ comments and leaving the code within it intact. The result? It’s like pre-compile macros for PHP. This is called the not-so-safe way primarily due to all of the limitations and bugs from eval().

But what is the safe method, and what if I’m doing something eval() doesn’t like?

In this case, you want to change your code to check for the version of PHP – create one class called yourClass.class4.php and yourclass.class.php; if using PHP4… you get the idea. Make your require() statements dynamic. The class4.php script will have the same code but you’ll remove the /*PHP4 */ comments before you save the file (leave the code between the comment markers in place). As you can see the real trick is to have your classes declared in separate files. The problem with this and the reason it’s not called “safe” is because you’re going to have to keep your changes to the class synchronized across both files at all times! While annoying, you can be happy know that you’re not eval()’ing code or executing temporary files.

But what if I don’t want to have to keep changes synchronized between class files and I don’t mind it being a tiny bit slow?

Before requiring the class file so you’ll want to name the base class yourClass.classX.php with the /*PHP4 */ blocks of code in it – before performing require() you literally ‘compile’ the class by running the preg_replace and saving the result to a temp file on disk; then require() the tmp file and delete the tmp file. Now obviously there can be some risks associated with executing a file which is written to disk by the executing process (esp if it’s a web server) but you can do a few things such as making the file name random to mitigate risks. Whatever you do, be sure the temporary location is relatively safe (consult chmod man page) and that above all the generated script is deleted after it’s executed.

PHP4 + PHP5 compatible overloading