DEV Community

david duymelinck
david duymelinck

Posted on • Edited on

PHP method overloading

This is a rant about an article on freeCodeCamp named How Method Overloading Works in PHP.

It is more an emotional rant about creating readable and safe code rather than one where I angerly wave my fist at a cloud.

My freeCodeCamp article takeaways

Many languages have method overloading because it is a part of polymophism. And PHP provides it by using the __call method.

Zubair provides the code, addresses some use cases, so overall it is a good article.

The code

I'm biased because I find the __call method a garbage magic function. I only use it if there is no other way.

What i don't like about the code:

  • mixed functionality, identifying the function overload and the actual code
  • no consistent return
  • no long term vision (what if you have ten or twenty methods you want to overload?)

You can argue it is an example to demonstrate overloading, and you are right.
Ravavyr mentioned in the comments that the code should have the same output, and I agree. Method overloading should only handle the input, it should not have an effect on the return of the method. The change in the output is only to demonstrate there is an input difference.

I just want to cry when I see it, so lets continue to make me happy.

Fixing the code

class SampleClass
{
    private function add2($numbers) {
        return array_sum($numbers);
    }

    private function add3($numbers) {
        return array_sum($numbers) > 10 ? 10 : array_sum($numbers);
    }

    public function __call($functionName, $arguments)
    {
        $count = count($arguments);
        $methodName = $functionName . $count;

        return method_exists(__CLASS__, $methodName) ? $this->{$methodName}($arguments) : NULL;
    }
}

# The sample execution works
$sampleObject = new SampleClass;
echo $sampleObject->add(12, 12) . PHP_EOL; // Outputs 24 
echo $sampleObject->add(12, 2, 6) . PHP_EOL; // Outputs 10
Enter fullscreen mode Exit fullscreen mode

To clean up the __call method I used the arguments to create method names, and call the methods if they exist.
So now the only function of the __call method is to find methods in the class.

Nowhere in polymorphism it is mentioned that you can't use private functions to achieve overloading.

Another thing that makes me cry is not validating arguments. array_sum is a very forgiving function, but what if the code can't be so forgiving?

Preventing errors

class Numbers {
    private array $collection = [];

    public function __construct(...$nums) {
        $this->collection = array_filter($nums, fn($num) => is_numeric($num));
    }

    public function toArray() {
        return $this->collection;
    }

}

class SampleClass
{
    private function addOverload(Numbers $numbers) {
        $arr = $numbers->toArray();

        return match(count($arr)) {
            2 => array_sum($arr),
            3 => array_sum($arr) > 10 ? 10 : array_sum($arr)
        };
    }

    private function callMethod($functionName, $arguments) {
        $count = count($arguments);
        $methodName = $functionName . $count;

        if(method_exists(__CLASS__, $methodName) === FALSE) {
            return NULL;
        }

        return $this->{$methodName}($arguments);
    }

    public function __call($functionName, $arguments)
    {
        return match($functionName) {
            'add' => $this->addOverload($arguments[0]),
            'default' => $this->callMethod($functionName, $arguments)
        };
    }
}

$sampleObject = new SampleClass;
$twentyfour = new Numbers(12, 12);
$ten = new Numbers(12, 2, 6);
echo $sampleObject->add($twentyfour) . PHP_EOL; // Outputs 24 
echo $sampleObject->add($ten) . PHP_EOL; // outputs 10
Enter fullscreen mode Exit fullscreen mode

I added a Numbers class to allow only numeric input. This change made me refactor the SampleClass. So there is now an addOverload method that switches the code based on the count.

When I look at the code I have now, there are a lot of lines that are just waste. I love the environment, so I'm going to reduce the waste.

Only in this case

# copy Numbers from previous example

class SampleClass2
{
    public static function add(Numbers $numbers) {
        $arr = $numbers->toArray();

        return match(count($arr)) {
            2 => array_sum($arr),
            3 => array_sum($arr) > 10 ? 10 : array_sum($arr)
        };
    }
}

$twentyfour = new Numbers(12, 12);
$ten = new Numbers(12, 2, 6);
echo SampleClass2::add($twentyfour) . PHP_EOL; // Outputs 24 
echo SampleClass2::add($ten) . PHP_EOL; // outputs 10
Enter fullscreen mode Exit fullscreen mode

Now I am a happy camper. Bye bye __call method, no overloading because the code deviation is contained by the add method.

Conclusion

It is good to know the concept of method overloading, and use it when it is the best solution for the problem you are facing.

Just try to figure out if the code you write makes others and yourself happy in the future.

Top comments (5)

Collapse
 
ravavyr profile image
Ravavyr

Great explanation and breakdown. Now tell me why the hell ANYONE uses overloading.

Instead of just using Add() and Addbutdifferent()

Reusing the same method to do different things is absolutely counterintuitive to the KISS principle.

Collapse
 
xwero profile image
david duymelinck

Method overloading is not meant to do different things based on the arguments, that is a problem with the example that was provided.

Method overloading is needed when you have a static typed language, because the compiler expects the argument to be only one type. Most static typed languages now have generics, which you can abuse, i'm still on the fence if it is abuse, to allow multiple types for the same argument.

In PHP we have composite types so we don't need method overloading even when strict mode is on.

One of my takeaways about the concept of method overloading is that you have to be aware of the types of input an argument can take, and what is the consequence for the rest of the code.

Thank you for your comment. I updated the article so that others don't focus on the valid point you made.

Collapse
 
ravavyr profile image
Ravavyr

So the issue you're having is more about the types being passed as arguments. I get what you're saying now.

Now, I don't get why people seem so hung up on having strict typing. Sure, i understand the benefits of it, but at the same time feel it's so over-worried-about.
Like, if i'm writing a function, i define what will happen within that function.

So if i want it to only process strings i can easily say "if(is_numeric($var)) exit} or whatever to block numbers that aren't strings, or vice versa, or whatever type you want to filter out.
As long as i write the code to meet the requirements and to throw valid errors when the "wrong/bad" data is passed into the function, I don't need strict typing.
It's usually one more if statement and yet people seem to act like it's the end of the world when they don't even write good logic in the rest of their functions.

As far as the terminology, I've been coding for 16+ years and i can pretty much build anything on the internet, but when it comes to terminology i draw blanks. I mean, i learned PHP by copying old text-based rpgs code and hacking away at it until it worked right, and then piled on a decade of experience building and debugging things. The lack of a formal education on it hasn't hurt my career any so i never bothered with all the big words :)

Anyway, my 2 cents.
Thanks for clarifying the issue.

Collapse
 
giulio profile image
Giulio "Joshi"

I can't say how much I do agree with your counter-examples, specially on PHP language having historical flexible types and variadic arguments in its core.

The example using add() method doesn't work on their favor, being quite simple and ...basically needs no overloading at all.

Consider this:

class SampleClass
{
    function add(int ...$numbers): int
    {
        return array_sum($numbers);
    }
}
Enter fullscreen mode Exit fullscreen mode

we have backward compatibility (no need to refactor the usages) and works with 0 or more arguments.

In my experience, I would say that "magic methods" are convenient in cases of refactoring of interface from earlier versions.
Like when you need to limit how many many breaking changes a new version introduces.

The rest is probably possible to handle using the Union Type feature, and write the type handling part within the "overloaded" function.

Collapse
 
rozhnev profile image
Slava Rozhnev

Simple explanation for complex concept. PHP code tested here