воскресенье, 20 октября 2019 г.

Неявные преобразования данных в PHP

Маленький пост о том, как поиметь проблем на ровном месте из-за неявного преобразования данных из одного типа в другой.

Допустим, у вас есть такой простенький код:

$a = ['1' => 'v1', '1-2' => 'v2', '1-3' => 'v3'];
$search = '1-2';
foreach ($a as $key => $value) {
    if ($key == $search) {
        echo 'Key found: ' . $key . PHP_EOL;
    }
}


Видим вывод:

Key found: 1
Key found: 1-2


Возникает закономерный вопрос - почему? Мы же ожидали, что выведется только ключ 1-2. Откуда же в выводе взялся ключ 1?

Давайте копнем чуть глубже и модифицируем код проверки:

if ($key == $search) {
    var_dump($key, $search);
    echo PHP_EOL;
}


Видим вывод:

int(1)
string(3) "1-2"

string(3) "1-2"
string(3) "1-2"


Что мы видим? Что ключ первого элемента массива вместо типа строка (string) стал типом целое число (integer).

Это стандартное поведение ключей типа строка, отмеченное в официальном руководстве. Получаем, что на первой итерации цикла мы сравниваем целое число 1 со строкой 1-2. Немного неожиданно.

Казалось бы - строка 1-2 уж никак не может быть равна целому числу 1. Однако, правила сравнения разных типов (также отраженные в официальном руководстве, таблица Сравнение различных типов) сообщают, что при сравнении числа и строки, строка приводится к числу. И по правилам приведения к числу (опять же описанным к официальном руководстве) мы получаем, что строка 1-2 приводится к числу 1. А уж 1 точно равно 1.

Что можно сказать в заключение:

P.S. Интересно, предложат и заапрувят ли когда-нибудь rfc, который сделает оба сравнения (=== и ==) строгими?