Как это было
Не так давно сталкнулся с одной проблемой, возникшей при переезде на php 5.4. Задача состояла в тестировании функционала, который использовал родные функции. К слову, Fumocker отлично справляется с этой задачей, позволяя в тестах переопределять встроенные функции. Я написал пачку тестов и запустил их локально. Все тесты прошли успешно. Отлично! Задача была сделана и я был в полном счастье, пока не добавил проект в travis-ci. И? Сборка была сломана под php 5.4, когда под 5.3 всё светилось зелёным.
Именно этот факт навел меня на мысль, что между 5.3 и 5.4 должна быть разница в перегрузке функций.
Скажу честно, я никогда прежде не находил информации об этом различии в описании релизов php 5.4. Поэтому в первую очередь пошел пытать Гугл. Но Гугл не смог дать внятного ответа. Все что я нашел — касалось перегрузки методов, не более. Это и cподвигло меня начать эксперементировать с кодом.
Ну что, приступим?
Первым желанием возникло создать песочницу для воспроизведения ситуации. Для этого я написал простейший класс, который использовал встроенную функцию range:
<?php
// MyClass.php
namespace MyNamespace;
class MyClass
{
public function makeMeRange()
{
return range(1,3);
}
}
и отдельный файл с переопределением этой функции в одноименной области видимости:
<?php
// MyNamespaceFunctions.php
namespace MyNamespace;
function range($low, $high, $step = null)
{
return 'Overridden';
}
Затем мне стало интересно, что произойдет, если мы подключим файлы с определением класса и функции перед созданием первого объекта MyClass:
<?php
// main.php
include_once("MyClass.php");
include_once("MyNamespaceFunctions.php");
use MyNamespaceMyClass;
$my_obj = new MyClass();
echo $my_obj->makeMeRange();
В этом случае поведение одинаковое для двух версий:
$ php54 main.php
Overridden
$ php53 main.php
Overridden
Но что должно случиться, если подключить файл с функцией после создания первого экземпляра MyClass?
<?php
include_once("MyClass.php");
use MyNamespaceMyClass;
$my_obj = new MyClass();
include_once("MyNamespaceFunctions.php");
$other_obj = new MyClass();
echo $my_obj->makeMeRange();
По-прежнему никакой разницы:
$ php54 main.php
Overridden
$ php53 main.php
Overridden
Остается последний шанс найти причину двуликости поведения — попробовать вызвать функцию перед включением файла с переопределением этой же функции:
<?php
include_once("MyClass.php");
use MyNamespaceMyClass;
$my_obj = new MyClass();
$my_obj->makeMeRange();
include_once("MyNamespaceFunctions.php");
$other_obj = new MyClass();
echo $other_obj->makeMeRange();
Ага! Попалась!
$ php54 main.php
PHP Notice: Array to string conversion in /Volumes/Projects/php-experiments/
…
$ php53 main.php
Overridden
И последний-препоследний эксперимент (обещаю):
<?php
include_once("MyClass.php");
use MyNamespaceMyClass;
$my_obj = new MyClass();
$my_obj->makeMeRange();
include_once("MyNamespaceFunctions.php");
echo $my_obj->makeMeRange();
подтверждает разницу в перегрузке функции между 5.3 и 5.4:
$ php54 main.php
PHP Notice: Array to string conversion in /Volumes/Projects/php-experiments/
…
$ php53 main.php
Overridden
В итоге
Получается, что php 5.3 дает переопределить функцию в любой момент выполнения скрипта, если функция не была переопределена до этого. Когда в это же время php 5.4 будет использовать лишь ту версию функции, какая была впервые использована где-либо в коде.
Кроме описанной проблемы, эта статья подымает давний вопрос — «акутальность документации». Да, мы до сих пор вынуждены использовать неполноценную документацию на свой страх и риск. По-моему, это позор на наши головы.
P.S.: Я создал репозиторий для тестирования описанного поведения. На случай если захотите самостоятельно проверить просто склонируйте репозиторий и запустите тесты с помощью phpunit
P.S.S: А еще есть открытый тиктет #63201 на bugs.php.net. Любое участие в данном вопросе приветствуется!
Автор: vatson