Переменные приведения типов в PHP, какова практическая причина для этого?

45

PHP, как большинство из нас знает, имеет слабую типизацию . Для тех, кто этого не делает, PHP.net говорит:

PHP не требует (или не поддерживает) явного определения типа в объявлении переменной; тип переменной определяется контекстом, в котором используется переменная.

Нравится вам это или нет, PHP на лету перебрасывает переменные. Итак, следующий код действителен:

$var = "10";
$value = 10 + $var;
var_dump($value); // int(20)

PHP также позволяет явно приводить переменную, например так:

$var = "10";
$value = 10 + $var;
$value = (string)$value;
var_dump($value); // string(2) "20"

Это все круто ... но, для моей жизни, я не могу придумать практическую причину для этого.

У меня нет проблем со строгой типизацией на языках, которые ее поддерживают, например на Java. Это хорошо, и я полностью понимаю это. Кроме того, я знаю - и полностью понимаю полезность - хинтинга типов в параметрах функций.

Проблема, связанная с приведением типов, объясняется приведенной выше цитатой. Если PHP может менять типы по своему желанию , он может делать это даже после принудительного приведения типа; и он может делать это на лету, когда вам нужен определенный тип операции. Это делает следующее действительным:

$var = "10";
$value = (int)$var;
$value = $value . ' TaDa!';
var_dump($value); // string(8) "10 TaDa!"

Так какой смысл?


Возьмите этот теоретический пример мира, в котором приведение пользовательских типов имеет смысл в PHP :

  1. Вы заставляете переменную приведения $fooкак int(int)$foo.
  2. Вы пытаетесь сохранить строковое значение в переменной $foo.
  3. PHP выбрасывает исключение !! ← Это имело бы смысл. Внезапно причина для пользовательского приведения типов существует!

Тот факт, что PHP будет переключать вещи по мере необходимости, делает точку приведения пользовательских типов неопределенной. Например, следующие два примера кода эквивалентны:

// example 1
$foo = 0;
$foo = (string)$foo;
$foo = '# of Reasons for the programmer to type cast $foo as a string: ' . $foo;

// example 2
$foo = 0;
$foo = (int)$foo;
$foo = '# of Reasons for the programmer to type cast $foo as a string: ' . $foo;

Спустя год после того, как первоначально задали этот вопрос, угадайте, кто обнаружил, что использует приведение типов в практической среде? С уважением.

Требовалось отобразить денежные значения на веб-сайте для меню ресторана. Дизайн сайта требовал обрезки концевых нулей, чтобы дисплей выглядел примерно так:

Menu Item 1 .............. $ 4
Menu Item 2 .............. $ 7.5
Menu Item 3 .............. $ 3

Лучший способ сделать это - привести переменную в число с плавающей точкой:

$price = '7.50'; // a string from the database layer.
echo 'Menu Item 2 .............. $ ' . (float)$price;

PHP обрезает конечные нули с плавающей точкой, а затем преобразует число с плавающей точкой в ​​конкатенацию.

Стивен
источник
Это -> $ значение = $ значение. «ТаДа!»; Преобразует $ value обратно в строку перед выполнением присваивания окончательному значению $ value. Неудивительно, что если вы принудительно применяете приведение типа, вы получаете приведение типа. Не уверен, какой смысл спрашивать, в чем смысл?
Крис
"# 3. PHP выдает исключение !! <--- Это имело бы смысл." На самом деле это не имеет никакого смысла. Это даже не проблема в Java, JavaScript или любом другом языке C-синтаксиса, о котором я знаю. Кто в здравом уме будет считать это желательным поведением? Вы хотите, чтобы повсюду были (string)забросы ?
Николь
@Renesis: вы меня неправильно поняли. Я имел в виду, что исключение будет выдано, только если пользователь произвел приведение типа к переменной. Нормальное поведение (где PHP выполняет приведение за вас), конечно, не вызывает исключения. Я пытаюсь сказать, что пользовательское приведение типов является спорным , но если возникнет исключение, это внезапно будет иметь смысл.
Стивен
Если вы говорите, $intval.'bar'бросает исключение, я все еще не согласен. Это не исключение на любом языке. (Все языки, которые я знаю, выполняют либо автоматическое приведение, либо а .toString()). Если вы говорите, что $intval = $stringvalвыбрасывает исключение, то вы говорите о строго типизированном языке. Я не хотел показаться грубым, так что извините, если я сделал. Я просто думаю, что это идет вразрез с тем, к чему привык каждый разработчик, и намного, намного менее удобно.
Николь
@ Стефан - я отправил ответ после некоторого расследования. Действительно интересные результаты - я думал, что 2 случая наверняка покажут цель приведения, но PHP даже более странен, чем я думал.
Николь

Ответы:

32

В языке со слабой типизацией существует приведение типов для устранения неоднозначности в типизированных операциях, когда в противном случае компилятор / интерпретатор использовал бы порядок или другие правила, чтобы предположить, какую операцию использовать.

Обычно я бы сказал, что PHP следует этому шаблону, но в случаях, которые я проверял, PHP вел себя нелогично в каждом.

Вот те случаи, использующие JavaScript в качестве языка сравнения.

Конкатенация строк

Очевидно, что это не проблема в PHP, потому что есть отдельные операторы concatenation ( .) и extension ( +).

JavaScript
var a = 5;
var b = "10"
var incorrect = a + b; // "510"
var correct = a + Number(b); // 15

Сравнение строк

Часто в компьютерных системах «5» больше, чем «10», потому что он не интерпретируется как число. Это не так в PHP, который, даже если оба являются строками, понимает, что они являются числами, и устраняет необходимость в приведении):

JavaScript
console.log("5" > "10" ? "true" : "false"); // true
PHP
echo "5" > "10" ? "true" : "false";  // false!

Набор функций подписи

В PHP реализована простая проверка типов для сигнатур функций, но, к сожалению, она настолько несовершенна, что, вероятно, ее редко можно использовать.

Я думал, что могу делать что-то не так, но комментарий к документам подтверждает, что встроенные типы, отличные от массива, не могут использоваться в сигнатурах функций PHP - хотя сообщение об ошибке вводит в заблуждение.

PHP
function testprint(string $a) {
    echo $a;
}

$test = 5;
testprint((string)5); // "Catchable fatal error: Argument 1 passed to testprint()
                      //  must be an instance of string, string given" WTF?

И в отличие от любого другого языка, который я знаю, даже если вы используете тип, который он понимает, null больше не может быть передан этому аргументу ( must be an instance of array, null given). Как глупо.

Булева интерпретация

[ Редактировать ]: это новый. Я подумал о другом случае, и снова логика полностью противоположна JavaScript.

JavaScript
console.log("0" ? "true" : "false"); // True, as expected. Non-empty string.
PHP
echo "0" ? "true" : "false"; // False! This one probably causes a lot of bugs.

Итак, в заключение, единственный полезный случай, о котором я могу думать, это ... (барабанная дробь)

Тип усечения

Другими словами, когда у вас есть значение одного типа (скажем, строка), и вы хотите интерпретировать его как другой тип (int), и вы хотите, чтобы оно стало одним из допустимых значений в этом типе:

$val = "test";
$val2 = "10";
$intval = (int)$val; // 0
$intval2 = (int)$val2; // 10
$boolval = (bool)$intval // false
$boolval2 = (bool)$intval2 // true
$props = (array)$myobject // associative array of $myobject's properties

Я не могу понять, что такое апкастинг (для типа, который включает в себя больше значений) действительно когда-либо получит вас.

Поэтому, хотя я не согласен с предложенным вами использованием типизации (вы, по сути, предлагаете статическую типизацию , но с неоднозначностью, заключающейся в том , что только при принудительном приведении к типу это приведет к ошибке - что приведет к путанице), я думаю, что это хорошо вопрос, потому что , видимо , литье имеет очень мало пользы в PHP.

Николь
источник
Хорошо, а как насчет E_NOTICEтогда? :)
Стивен
@Stephen E_NOTICEможет быть в порядке, но для меня неоднозначное состояние касается - как бы вы узнали, взглянув на один бит кода, если бы переменная находилась в этом состоянии (будучи приведенной в другом месте)? Также я нашел другое условие и добавил его в свой ответ.
Николь
1
Что касается булевой оценки, в PHP-документации четко указывается, что считается ложным при вычислении в булеву оценку, и как пустая строка, так и строка «0» считаются ложными. Так что даже когда это кажется странным, это нормальное и ожидаемое поведение.
Яцек Прусия
добавить немного путаницы: echo "010" == 010 и echo "0x10" == 0x10;-)
vartec
1
Обратите внимание, что начиная с PHP 7 , примечания к этому ответу о хинтинге скалярного типа являются неточными.
Джон В.
15

Вы смешиваете концепции слабого / сильного и динамического / статического типа.

PHP слаб и динамичен, но ваша проблема в концепции динамического типа. Это означает, что переменные не имеют типа, значения имеют.

«Приведение типов» - это выражение, которое создает новое значение оригинала другого типа; он ничего не делает с переменной (если она задействована).

Единственная ситуация, когда я регулярно печатаю приведенные значения, - это числовые параметры SQL. Вы должны очистить / экранировать любое входное значение, которое вы вставляете в операторы SQL, или (намного лучше) использовать параметризованные запросы. Но если вам нужно какое-то значение, которое ДОЛЖНО быть целым числом, гораздо проще просто привести его.

Рассмотреть возможность:

function get_by_id ($id) {
   $id = (int)$id;
   $q = "SELECT * FROM table WHERE id=$id LIMIT 1";
   ........
}

если бы я пропустил первую строку, $idбыл бы простой вектор для инъекции SQL. Приведение гарантирует, что это безвредное целое число; любая попытка вставить некоторый SQL просто приведет к запросуid=0

Хавьер
источник
Я приму это. Теперь, насколько полезна литье типов?
Стивен
Забавно, что вы вводите SQL-инъекцию Я спорил о SO с кем-то, кто использует эту технику для очистки пользовательского ввода. Но какую проблему решает этот метод, который mysql_real_escape_string($id);еще не решен?
Стивен
короче :-) конечно, для строк я использую параметризованные запросы или (если использую старое расширение mysql) избегаю его.
Хавьер
2
mysql_real_escape_string()имеет уязвимость, когда ничего не делает со строками типа '0x01ABCDEF' (то есть шестнадцатеричное представление целого числа). В некоторых многобайтовых кодировках (к счастью, не в Юникоде) такая строка может использоваться для разбиения запроса (поскольку MySQL оценивает его как нечто, содержащее кавычку). Вот почему ни, mysql_real_escape_string()ни is_int()лучший выбор для работы с целочисленными значениями. Типотипирование есть.
MCHL
Ссылка с некоторыми более подробной информацией: ilia.ws/archives/…
Mchl
4

Я нашел одно применение для приведения типов в PHP:

я разрабатываю приложение для Android, которое делает http-запросы к PHP-сценариям на сервере для получения данных из базы данных. Сценарий хранит данные в виде объекта PHP (или ассоциативного массива) и возвращается в виде объекта JSON в приложение. Без приведения типов я получил бы что-то вроде этого:

{ "user" : { "id" : "1", "name" : "Bob" } }

Но, используя приведение типов PHP (int)к идентификатору пользователя при сохранении объекта PHP, я получаю это возвращаемое в приложение:

{ "user" : { "id" : 1, "name" : "Bob" } }

Затем, когда объект JSON анализируется в приложении, это избавляет меня от необходимости анализировать идентификатор в целое число!

Видите, очень полезно.

Райан
источник
Я не рассматривал форматирование данных для внешних систем со строгой типизацией. +1
Стивен
Это особенно верно, когда речь идет о JSON с внешними системами, такими как Elasticsearch. Значение json_encode () - "5" даст очень разные результаты, чем значение 5.
Йохан Фредрик Варен
3

Одним из примеров являются объекты с методом __tostring: $str = $obj->__toString();против $str = (string) $obj;. Во втором набирается гораздо меньше текста, и дополнительными вещами являются знаки препинания, которые набирают больше времени. Я также думаю, что это более читабельно, хотя другие могут не согласиться.

Другой делает массив из одного элемента: array($item);против (array) $item;. Это поместит любой скалярный тип (целое число, ресурс и т. Д.) В массив.
Альтернативно, если $itemэто объект, его свойства станут ключами к их значениям. Тем не менее, я думаю, что преобразование объекта-> массива немного странно: частные и защищенные свойства являются частью массива и переименовываются. Процитируем документацию PHP : для частных переменных имя класса добавляется перед именем переменной; Защищенные переменные имеют «*» перед именем переменной.

Другое использование - преобразование данных GET / POST в соответствующие типы для базы данных. MySQL может справиться с этим сам, но я думаю, что более ANSI-совместимые серверы могут отклонять данные. Причина, по которой я упоминаю только базы данных, заключается в том, что в большинстве других случаев для данных в какой-то момент будет выполняться операция в соответствии с их типом (т. Е. Для int / floats обычно выполняются вычисления и т. Д.).

Алан Пирс
источник
Это отличные примеры того, как работает приведение типов. Тем не менее, я не уверен, что они удовлетворяют потребность . Да, вы можете преобразовать объект в массив, но почему? Я думаю, потому что тогда вы могли бы использовать множество PHP-функций массива в новом массиве, но я не могу понять, как это было бы полезно. Кроме того, PHP обычно создает строковые запросы для отправки в базу данных MySQL, поэтому тип переменной не имеет значения (автоматическое преобразование строки из intили floatбудет происходить при построении запроса). (array) $itemэто аккуратно , но полезно?
Стивен
Я на самом деле согласен. Когда я их набирал, я думал, что подумаю о некоторых видах использования, но я этого не сделал. Для базы данных, если параметры являются частью строки запроса, то вы правы, приведение не имеет смысла. Однако при использовании параметризованных запросов (что всегда является хорошей идеей) можно указывать типы параметров.
Алан Пирс
Ага! Возможно, вы нашли правильную причину с помощью параметризованных запросов.
Стивен
0

Этот скрипт:

$tags = _GET['tags'];
foreach ($tags as $tag) {
    echo 'tag: ', $tag;
}

будет работать нормально для, script.php?tags[]=oneно не удастся script.php?tags=one, потому что _GET['tags']возвращает массив в первом случае, но не во втором. Поскольку сценарий написан так, чтобы он ожидал массив (и у вас меньше контроля над строкой запроса, отправляемой в сценарий), проблему можно решить путем соответствующего приведения результата из _GET:

$tags = (array) _GET['tags'];
foreach ($tags as $tag) {
    echo 'tag: ', $tag;
}
beldaz
источник
0

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

$amount = (float) $_POST['amount'];

if( $amount > 0 ){
    $remoteService->doacalculationwithanumber( $amount );    
}

Очевидно, что это ошибочно и также неявно обрабатывается оператором сравнения в операторе if, но это помогает вам точно знать, что делает ваш код.

Gruffputs
источник
1
За исключением того, что это не сломается. Даже если бы он $_POST['amount']содержал строку мусора, php мог бы оценить, что он не больше нуля. Если бы он содержал строку, которая представляла положительное число, это оценило бы true.
Стивен
1
Не совсем верно. Предположим, что сумма $ передается стороннему сервису внутри условного соглашения, который должен получить номер. Если кто-то передаст $ _POST ['amount'] = "100 катушек", удаление (float) все равно позволит условному условию пройти, но $ amount не будет числом.
Gruffputs
-2

Я часто вижу, как часто используется «PHP» для преобразования переменных на лету при извлечении данных из внешних источников (пользовательский ввод или база данных). Это позволяет кодировщикам (заметьте, что я не говорил разработчикам) игнорировать (или даже не изучать) различные типы данных, доступные из разных источников.

Один кодер (обратите внимание, что я не сказал «разработчик»), чей код я унаследовал и до сих пор поддерживаю, похоже, не знает, что существует разница между строкой "20", возвращаемой в $_GETсупер-переменной, и операцией с целыми числами,20 + 20 когда она добавляет ее в значение в базе данных. Ей только повезло, что PHP использует .для конкатенации строк, а не +как любой другой язык, потому что я видел, как ее код «добавляет» две строки (a varcahrиз MySQL и значение из $_GET) и получает int.

Это практический пример? Только в том смысле, что это позволяет кодировщикам не знать, с какими типами данных они работают. Я лично ненавижу это.

dotancohen
источник
2
Я не понимаю, как этот ответ повышает ценность обсуждения. Тот факт, что PHP позволяет инженеру (или программисту, или программисту, что у вас есть) выполнять математические операции над строками, уже совершенно ясен в этом вопросе.
Стивен
Спасибо, Стивен. Возможно, я использовал слишком много слов, чтобы сказать: «PHP позволяет людям, которые не знают, что такое тип данных, создавать приложения, которые делают то, что они ожидают, в идеальных условиях».
dotancohen