Введіть змінні кастингу в 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

Найкращий спосіб, який я знайшов, щоб зробити це, щоб передати змінну як float:

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

PHP обрізає кінцеві нулі поплавця, а потім переглядає поплавок у вигляді рядка для конкатенації.


Це -> $ value = $ value. 'TaDa!'; Буде повернути значення $ назад у рядок, перш ніж присвоїти остаточне значення $ value. Насправді не дивно, що якщо ти змушуєш типовий ролик, ти отримуєш акторський склад. Не знаєте, в чому полягає питання, в чому питання?
Кріс

"# 3. PHP кидає виняток !! <--- Це мало б сенс." Насправді це взагалі не мало б сенсу. Це навіть не проблема в Java, JavaScript або будь-якій іншій мові C-синтаксису, яку я знаю. Хто з їх розуму бачив би це як бажану поведінку? Ви хочете скрізь робити (string)касти ?
Ніколь

@Renesis: ти мене неправильно розумієш. Я мав на увазі те, що виняток буде викинуто лише в тому випадку, якщо користувач введев змінну-тип. Звичайна поведінка (де PHP проводить кастинг для вас), звичайно, не стане винятком. Я намагаюся сказати, що визначений користувачем тип кастингу є суперечливим , але якби виключити виняток, це раптом має сенс.
Стефан

Якщо ви говорите, що $intval.'bar'викидає виняток, я все ще не згоден. Це не є винятком на будь-якій мові. (Усі мови, які я знаю, виконуються або в автоматичному режимі, або в а .toString()). Якщо ви говорите, що $intval = $stringvalкидає виняток, ви говорите про сильно набрану мову. Я не хотів звучати грубо, тож, вибачте, якщо це зробив. Я просто думаю, що це суперечить тому, до чого звик кожен розробник, і набагато, набагато менш зручно.
Ніколь

@Stephen - я опублікував відповідь після деякого розслідування. Дійсно цікаві результати - я вважав, що 2 випадки точно означать призначення кастингу, але PHP навіть більш дивний, ніж я думав.
Ніколь

Відповіді:


32

У мові із слабким типом типові кастинги є для усунення неоднозначності в операціях набору тексту, коли в іншому випадку компілятор / інтерпретатор використовує порядок чи інші правила, щоб припустити, яку операцію використовувати.

Зазвичай я б сказав, що PHP слідує цій схемі, але із випадків, які я перевіряв, PHP поводився контр-інтуїтивно у кожному.

Ось такі випадки, що використовують JavaScript як мову порівняння.

Зміст струн

Очевидно, це не проблема в PHP, оскільки є окремі оператори конкатенації рядків ( .) та додавання ( +).

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" (тобто шістнадцяткове подання цілого числа). У деяких багатобайтових кодуваннях (не Unicode lucklily) такий рядок може бути використаний для розбиття запиту (оскільки він оцінюється 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 () - ed "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;
}

0

Він також може бути використаний як швидкий і брудний метод, щоб переконатися, що ненадійні дані щось не зламають, наприклад, якщо використовується віддалений сервіс, який має перевірку лайно і повинен приймати лише номери.

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

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

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


1
За винятком того, що він не зламається. Навіть якби він $_POST['amount']містив рядок сміття, php оцінив би, що він не перевищує нуля. Якби він містив рядок, який представляв додатне число, він би оцінював істину.
Стівен

1
Не зовсім правда. Враховуйте, що сума $ передається сторонній службі всередині умовного, який повинен отримати номер. Якби хтось передавав $ _POST ['sum'] = "100 бобінів", видалення (float) все одно дозволило б умовно пройти, але $ $ не було б числом.
Gruffputs

-2

Одне "використання" змінних PHP для повторного кастингу на ходу, яке я часто бачу у використанні, - це під час отримання даних із зовнішніх джерел (введення користувача або бази даних). Це дозволяє кодерам (зауважте, що я не сказав розробникам) ігнорувати (або навіть не вивчати) різні типи даних, доступні з різних джерел.

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

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


2
Я не бачу, як ця відповідь додає значення дискусії. Те, що PHP дозволяє інженеру (або програмісту, або кодеру, що у вас є) виконувати математичні операції над рядками, вже досить ясно в питанні.
Стівен

Дякую, Стівен. Можливо, я використав занадто багато слів, щоб сказати: "PHP дозволяє людям, які не знають, що таке тип даних, створювати програми, які роблять те, що вони очікують в ідеальних умовах".
dotancohen
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.