PHP
Тип жонглирования и нерегулярные проблемы сравнения
Поиск…
Что такое Тип Жонглирование?
PHP - это свободно типизированный язык. Это означает, что по умолчанию он не требует, чтобы операнды в выражении имели одинаковые (или совместимые) типы. Например, вы можете добавить число в строку и ожидать, что оно будет работать.
var_dump ("This is example number " . 1);
Выход будет:
string (24) «Это пример номер 1»
PHP выполняет это путем автоматического литья несовместимых типов переменных в типы, которые позволяют выполнить запрошенную операцию. В приведенном выше случае он будет отличать целочисленный литерал 1 в строку, что означает, что он может быть объединен с предыдущим строковым литералом. Это называется жонглированием типа. Это очень мощная функция PHP, но это также функция, которая может привести вас к большому вытягиванию волос, если вы не знаете об этом и можете даже привести к проблемам безопасности.
Рассмотрим следующее:
if (1 == $variable) {
// do something
}
Цель состоит в том, что программист проверяет, что переменная имеет значение 1. Но что произойдет, если переменная $ имеет значение «1 с половиной» вместо? Ответ может вас удивить.
$variable = "1 and a half";
var_dump (1 == $variable);
Результат:
BOOL (истина)
Почему это произошло? Это связано с тем, что PHP понял, что строка «1 с половиной» не является целым числом, но это должно быть для того, чтобы сравнить ее с целым числом 1. Вместо того, чтобы терпеть неудачу, PHP инициирует жонглирование типа и пытается преобразовать переменную в целое число. Он делает это, беря все символы в начале строки, которую можно отличить до целого и отбросить. Он останавливается, как только он сталкивается с символом, который нельзя рассматривать как число. Поэтому «1 с половиной» передается целому числу 1.
Конечно, это очень надуманный пример, но он служит для демонстрации проблемы. Следующие несколько примеров будут охватывать некоторые случаи, когда я столкнулся с ошибками, вызванными манипуляцией типа, которая произошла в реальном программном обеспечении.
Чтение из файла
При чтении из файла мы хотим узнать, когда мы достигли конца этого файла. Зная, что fgets()
возвращает false в конце файла, мы можем использовать это как условие для цикла. Однако, если данные, возвращаемые из последнего чтения, являются тем, что оценивается как логическое значение false
, это может привести к преждевременному завершению цикла чтения файла.
$handle = fopen ("/path/to/my/file", "r");
if ($handle === false) {
throw new Exception ("Failed to open file for reading");
}
while ($data = fgets($handle)) {
echo ("Current file line is $data\n");
}
fclose ($handle);
Если файл читается содержит пустую строку, то в while
цикл будет завершен в этой точке, так как пустая строка вычисляется как логическое значение false
.
Вместо этого мы можем явно проверить логическое false
значение, используя строгие операторы равенства :
while (($data = fgets($handle)) !== false) {
echo ("Current file line is $data\n");
}
Обратите внимание, что это надуманный пример; в реальной жизни мы использовали бы следующий цикл:
while (!feof($handle)) {
$data = fgets($handle);
echo ("Current file line is $data\n");
}
Или замените все это на:
$filedata = file("/path/to/my/file");
foreach ($filedata as $data) {
echo ("Current file line is $data\n");
}
Переключить сюрпризы
Операторы switch используют нестрогое сравнение для определения совпадений. Это может привести к некоторым неприятным неожиданностям . Например, рассмотрим следующее утверждение:
switch ($name) {
case 'input 1':
$mode = 'output_1';
break;
case 'input 2':
$mode = 'output_2';
break;
default:
$mode = 'unknown';
break;
}
Это очень простой оператор и работает как ожидалось, когда $name
является строкой, но может вызвать проблемы в противном случае. Например, если $name
является целым числом 0
, тогда во время сравнения будет выполняться манипуляция типа. Тем не менее, это буквальное значение в выражении case, которое вызывает жонглирование, а не условие в инструкции switch. Строка "input 1"
преобразуется в целое число 0
которое соответствует входному значению целого числа 0
. Результатом этого является то, что если вы задаете значение целого числа 0
, первый случай всегда выполняется.
Есть несколько решений этой проблемы:
Явное литье
Значение может быть типаж в строку перед сравнением:
switch ((string)$name) {
...
}
Или также можно использовать функцию, известную для возврата строки:
switch (strval($name)) {
...
}
Оба этих метода гарантируют, что значение имеет тот же тип, что и значение в операторах case
.
Избегайте switch
Использование оператора if
даст нам контроль над тем, как выполняется сравнение, что позволяет нам использовать строгие операторы сравнения :
if ($name === "input 1") {
$mode = "output_1";
} elseif ($name === "input 2") {
$mode = "output_2";
} else {
$mode = "unknown";
}
Строгая типизация
Начиная с PHP 7.0, некоторые из вредоносных эффектов жонглирования типа могут быть смягчены при строгой типизации . Включив этот оператор declare
в качестве первой строки файла, PHP будет принудительно вводить объявления типов параметров и декларации типа возвращаемого типа, TypeError
исключение TypeError
.
declare(strict_types=1);
Например, этот код, используя определения типа параметра, будет бросать захватывающее исключение типа TypeError
при запуске:
<?php
declare(strict_types=1);
function sum(int $a, int $b) {
return $a + $b;
}
echo sum("1", 2);
Аналогично, этот код использует декларацию типа возврата; он также генерирует исключение, если он пытается вернуть что-либо, кроме целого:
<?php
declare(strict_types=1);
function returner($a): int {
return $a;
}
returner("this is a string");