PHP 5.4 and Function Overloading

How it was

Not long ago, I encountered a problem on the current project. There was a need to mock some built-in functions to increase test coverage. Fumocker copes with such tasks very well and my choice was made in favor of it. I wrote tests and ran them locally. All passed. Nice! Task was done and I had been in a good mood until I put the project on travis-ci. The build was marked as broken for PHP 5.4, but it lit green under 5.3.

This led me to think that between 5.4 and 5.3 was the difference in function overloading. And I started to dig…

I had seen no mention of this behavior in the descriptions of new releases. So at first I went to ask Google. But Google could not answer me. All I could find was about method overloading and nothing else. This prompted me to write several experimental cases.

It would be better to reproduce the strange behavior in a sandbox. For this, I created a dummy class where the method used the built-in function range:

MyClass.phplink
1
2
3
4
5
6
7
8
9
10
11
<?php

namespace MyNamespace;

class MyClass
{
    public function makeMeRange()
    {
        return range(1,3);
    }
}

and a separate file with overridden function in the same namespace:

MyNamespaceFunctions.phplink
1
2
3
4
5
6
7
8
<?php

namespace MyNamespace;

function range($low, $high, $step = null)
{
    return 'Overridden';
}

Then I wondered what happens when we include the class and the function before an instantiation of MyClass:

main.php
1
2
3
4
5
6
7
8
9
<?php

include_once("MyClass.php");
include_once("MyNamespaceFunctions.php");

use MyNamespace\MyClass;

$my_obj = new MyClass();
echo $my_obj->makeMeRange();

The behavior was the same for both versions:

1
2
$ php54 main.php
Overridden
1
2
$ php53 main.php
Overridden

Nothing odd. But what happens if we include overridden function before first instantiation of MyClass?

main.php
1
2
3
4
5
6
7
8
9
10
11
12
<?php

include_once("MyClass.php");

use MyNamespace\MyClass;

$my_obj = new MyClass();

include_once("MyNamespaceFunctions.php");

$other_obj = new MyClass();
echo $my_obj->makeMeRange();

Still no difference:

1
2
$ php54 main.php
Overridden
1
2
$ php53 main.php
Overridden

There was one last chance to catch the two-faced behavior. I tried to make a function call before function overloading:

main.php
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

include_once("MyClass.php");

use MyNamespace\MyClass;

$my_obj = new MyClass();
$my_obj->makeMeRange();

include_once("MyNamespaceFunctions.php");

$other_obj = new MyClass();
echo $other_obj->makeMeRange();

Aha! Gotcha!

1
2
3
$ php54 main.php
PHP Notice:  Array to string conversion in /Volumes/Projects/php-experiments/
1
2
$ php53 main.php
Overridden

And the last option (I promise):

main.php
1
2
3
4
5
6
7
8
9
10
11
12
<?php

include_once("MyClass.php");

use MyNamespace\MyClass;

$my_obj = new MyClass();
$my_obj->makeMeRange();

include_once("MyNamespaceFunctions.php");

echo $my_obj->makeMeRange();

confirmed the function overloading difference between 5.4 and 5.3:

1
2
3
$ php54 main.php
PHP Notice:  Array to string conversion in /Volumes/Projects/php-experiments/
1
2
$ php53 main.php
Overridden

Summary: It turns out that php 5.3 redefines a function for all subsequent calls, when 5.4 uses only one version of the function that was called for the first time.

Except for the problem of the difference, this article raises another question of “freshness documentation.” Yes, we still have a poor incomplete documentation, which we use at our own risk. Should be ashamed!

P.S.: I created a repository to test the described behavior. If you want to check it out, then simply clone the repository and run the test

P.S.S: There is an opened ticket #63201. Your participation is welcome!

Comments