Overloading in PHP


Murray Picton wrote up a blog post today on overloading functions in PHP. Overloading is a useful feature of many languages. Murray gives a nice definition in his post:

Overloading a function is the ability to define a function more than once with a different set of parameters for each one and then when it is called, the version of the function that matches the parameter set will be executed.

so you can define function foo(String $a) and foo(Array $a) and a different version will be called depending on how you call it. But, PHP’s idea of overloading is completely different and not really related.

Hello ladies, look at PHP, now back to Java, now back to PHP. Sadly, PHP’s not Java, but it can look (a bit) like Java if you stop using lady-scented functions and start using method overloading.

I just wanted to get that quote in somehow.

Murray’s solution is to use func_get_args inside your function, and perform some logic to switch execution to a different branch depending on what we find there. Something like:

function foo() {
    $args = func_get_args();
    if(is_string($args)) { /* do some stuff */ }
    else if(is_array($args)) { /* do some stuff */ }
}

This works well enough, but there are a few things I don’t like about it:

  1. You have to put all the code into one function, instead of literally overloading multiple functions
  2. The “which version am I” code and the actual functionality are intermixed inside the function

I thought an OO solution might mitigate against these things, allowing us to write completely separate functions, but which can be called with the same name, a different one being executed depending on the arguments. I’m not certain my solution is an improvement over Murray’s, but it’s a different approach if nothing else:

abstract class Overload {
    
    protected $aliases = Array();
    
    public function __call($called_method, $args) {
        if(array_key_exists($called_method, $this->aliases)) {
            foreach($this->aliases[$called_method] as $class_method) {
                if(call_user_func_array($class_method[1],$args)) {
                    return call_user_func_array(Array($this,$class_method[0]), $args);
                }
            }
            throw new Exception('Incorrect arguments for '.$called_method);
        } else {
            throw new Exception('Call to undefined method '.$called_method);
        }
    }
}

class Calc extends Overload {
    
    public function __construct() {
        $this->aliases['add'] = Array();
        $this->aliases['add'][] = Array('addition_string', function($args) {
            return is_string($args);
        });
        $this->aliases['add'][] = Array('addition_array', function($args) {
            return is_array($args);
        });
        $this->aliases['add'][] = Array('addition_args', function($args) {
            return (func_num_args()>=2);
        });
    }
        
    public function addition_string($args) {
        return $this->addition_array(explode(":",$args));
    }
    
    public function addition_array($args) {
        return array_sum($args);
    }
    
    public function addition_args() {
        return $this->addition_array(func_get_args());
    }
}

$calc = new Calc();

echo $calc->add("3:4:5")."\n"; // 12
echo $calc->add(Array(3,4,5))."\n"; // 12
echo $calc->add(3,4,5)."\n"; // 12

Wait, what?

Hah, yeah but it’s not as complicated as it looks: First of all let’s look at the simple part of our Calc class. We define three methods, addition_args, addition_string and addition_array. These are the methods we want to ‘overload’ into one ‘add’ method, and the really great thing is that they’re just as simple as their non-overloaded counterparts would have been.

Now to actually get them overloaded, in the constructor we populate the ‘aliases’ property of the parent class. This is an associative array of the following form:

$this->aliases[ALIAS][] = Array(METHOD, CALLBACK);

So, we want the alias to be ‘add’, and the method to be one of addition_args, addition_string and addition_array.

$this->aliases['add'][] = Array('addition_array', CALLBACK);
$this->aliases['add'][] = Array('addition_string', CALLBACK);
$this->aliases['add'][] = Array('addition_args', CALLBACK);

And CALLBACK is a function which returns true if the arguments given to it are suitable for the method it relates to.

So addition_string’s callback checks to see if its arguments are a string, and so on. If we wanted to define another alias, we’d just keep adding to the alias array, something like this:

$this->alias['subtract'][] = Array('some_method', function($args) {
    return some_test($args);
});

Be aware that if two callbacks return true for the same types of argument, those which appear earlier in the aliases array will take precedence. If we change addition_args’s callback to accept one or more argument instead of two and put it at the beginning of the aliases array, the callbacks for addition_string and addition_array would never be called and everything would be sent to addition_args instead.

The Overload Class

If all you care about is getting some kind of java-like overloading in PHP, and you don’t mind wrapping your functions in a class, then you can stop here – you don’t need to know how Overload works, just populate the aliases array correctly in the constructor and make your class extend Overload and you’re done. But if you’d like to know just what on earth Overload is doing, read on!

Overload is an abstract class, so it can’t be instantiated by itself – it just provides some functionality which a concrete (normal) class can use if it extends the abstract class.

We make use of PHP’s ‘magic’ __call method, which is called any time an unknown method is called on an object. Quick example:

class Foo {
    public function __call($method, $args) {
        echo 'wow!'. $method.' called!';
    }
    public function bar() {
        echo 'bar called';
    }
}

$foo = new Foo();
$foo->bar(); // "bar called"
$foo->baz(); // "wow! baz called!";

Back to the addition example: if you’re calling a method which does exist in Calc, then Overload does nothing. But if you call a method which doesn’t exist, then Overload’s __call will kick in and take a look at what you’re calling.

The logic in __call is pretty simple. Remember that we populate Overload’s aliases array when Calc is instantiated? Well in __call we just look through that array to see if the name of the method we’re trying to call exists as a key in the array:

if(array_key_exists($called_method, $this->aliases)) {

If it does, that means it’s been defined as an alias, and we’ll loop through all methods for that alias, running the callbacks to determine if we should call that method.

foreach($this->aliases[$called_method] as $class_method) {
    if(call_user_func_array($class_method[1],$args)) {

Once we find a method whose callback says ‘yes! I accept those arguments!’, then we call it:

return call_user_func_array(Array($this,$class_method[0]), $args);

In any other condition, we throw an exception; they’ve either called a method which doesn’t even exist as an alias, or they called an alias with some dodgy arguments and none of the callbacks returned true.

Improvements in the comments or on twitter @user24 please. There’s some good discussion about this post on reddit as well.

Especially interested in improvements to the error throwing.


Related Posts:

, , , , , , , ,

  1. #1 by Del on October 11, 2010 - 12:44 am

    By mapping the caller method name to your aliases array key name you’ve managed to create a very nice solution to Overloading in PHP.

    I really enjoyed reading this!

    Nice work H.

  2. #2 by Thomas on June 7, 2011 - 2:11 pm

    Nice solution indeed.

    A question: Could it be that a typing error has sliped into your code?

    return call_user_func_array(Array($this,$class_method[0]), $args);

    If not, I would be happy to get a tip to understand…

    Thomas

    • #3 by Howard Yeend on August 3, 2011 - 5:39 pm

      not sure which part you’re having trouble with…

      I’m guessing it’s the Array($this, $class_method[0]) part – In PHP you usually send a callback by name, like usort($arr, ‘my_callback’); but if your callback is in an object, you can send the callback as an array of object reference and method name, eg usort($arr, Array($my_object, ‘callback’));

      So that’s what we’re doing – sending call_user_func_array a reference to the method named by $class_method[0] in the $this object.

Comments are closed.