C, PHP, VB, .NET

Дневникът на Филип Петров


* Сравнения и типове данни в PHP

Публикувано на 22 март 2010 в раздел ОСУП.

PHP е един много популярен език за програмиране в днешно време. Може би това главно се дължи на липсата на строго типизиране на данните и автоматичното им превръщане от един тип в друг при нужда. Програмистите изглежда са мързеливи хора и за това PHP им харесва. Това обаче може да ви доведе до сериозни главоболия и купове непредвидени грешки. Нека демонстрираме с един примерен код:

$var1 = 'blablabla';
$var2 = 0;
if ($var1==true && $var2==false && $var1==$var2){
    echo ('WTF');
}

Логически погледнато условието "$var1==true && $var2==false && $var1==$var2" би трябвало винаги да върне false. Да, но не - пробвайте и на екрана ще видите едно WTF! Ето какво се получава:

  1. "$var1==true" връща true, защото променливата $var1 не е празна;
  2. "$var2==false" връща true, защото стойността на $var2 е 0, което отговаря на стойността на константата false;
  3. "$var1==$var2" магически също връща true, защото низът 'blablabla' автоматично се превръща в цяло число, което очевидно е 0, а 0 си е равно на 0.

Уплашихте ли се? За щастие конкретно за този проблем има лек и той е операторът "===", който означава "идентично равно по стойност на". За разлика от "==" (равно по стойност на), операторът ще търси освен равни стойности и еднакви типове данни.

Силно препоръчвам от гледна точка на сигурността на вашия код да използвате именно оператор "===", особено когато сравняваме данни подадени от потребител. В противен случай даваме възможно поле за изява на объркана програмна логика, която може да доведе до напълно неподозирани пробиви в сигурността.

Други типични PHP проблеми могат да възникнат при неинициализирани променливи. Какво мислите ще се случи, ако използвате променлива, която никога преди това не сте я инициализирали? Отговор - няма проблем, просто ще бъде хвърлен един "Undefined variable" notice ако пазите разширен лог файл (в повечето случаи дори няма да забележите). Променливата ще бъде инициализирана със стойността по подразбиране (в зависимост от контекста ще бъде "", 0, false или null). Затова винаги инициализирайте променливите си - дори те да трябва да са със стойности по подразбиране! Всъщност напук на първосигналната логика - този "спестен" ред код (от пропускането на дефиниране на променлива) всъщност забавя бързодействието на програмата, вместо да помага.

Накрая ще спомена за още един типичен PHP проблем, който заблуждава програмистите - проверката за това дали променлива има някаква стойност (функция isset()) и дали променливата е празна (функция empty()). Когато използвате isset вие проверявате просто дали променливата е инициализирана. Например една променлива $string="" е празна, но все пак е дефинирана. Функцията empty() връща булева стойност и проверява за:

  1. "" или ''- празен низ;
  2. 0 - цяло число 0;
  3. "0" или '0' - нула като низ;
  4. null - празна стойност;
  5. false - булева стойност "неистина";
  6. array() - празен масив;
  7. var $var - променлива в class без стойност.

Особени проблеми с empty() поражда точка 3. Например отговор "0" на въпроса "колко пари получавате като заплата на месец" би бил логически валиден ако сте безработен, но ако използвате функцията "empty()" ще получите true, т.е. функцията приема, че получения низ/число е празен. Ето един практичен пример - търсим дали дадена дума се среща в текст и използваме позицията й:

$string  = "Philip Petrov";
$var = strpos($string, "Philip");
if(empty($var)){ // грешна проверка - ще върне true
    echo 'The word "Philip" is not found in the text!';
}

Функцията strpos() връща число (на коя позиция се среща търсения низ в дадения) или false в случай, че търсения низ не е намерен. Тъй като както казахме empty() ще върне true при стойност на променливата"false" и ще върне true при подадено цяло число. Да, но забравихме за цялото число 0, нали? Затова и сравнението е неправилно, защото се получава частен случай. Това поведение на функцията empty() всъщност е напълно логично ако се върнете в първата част на статията - всъщност empty($var) е отрицанието на (boolean)$var (превръщането на променливата $var в булев тип),т.е. същия проблем се получава често при хората, които не използват функции, а разчитат директно на превръщане на типовете, като например условия като "if($var)...". Затова много внимавайте коя функция къде се използва и следвайте добре програмната логика. Най-общо казано - просто не ползвайте empty(), а си пишете условията сами. Иначе примерът от по-горе трябва да се пренапише така:

$string  = "Philip Petrov";
$var = strpos($string, "Philip");
if($var===false){ // правилна проверка
    echo 'The word "Philip" is not found in the text!';
}

Описаният по-горе проблем може да се формулира по-простичко - PHP третира числото 0 като false, що се отнася до булеви сравнения. Затова много се пазете от него. Много функции като strpos могат да върнат 0, което да се интерпретира като false и това да обърка програмната ви логика.

Колкото до isset() - добре е да го използваме когато не сме сигурни какво става с данните "над нас". Например една проверка "if($var<6)..." ще върне true, ако променливата $var не е инициализирана (тя ще се инициализира със стойност по подразбиране 0, а условието 0<6 е истина). Когато работим в екип не е изключено да получим подобен "подарък" от колега. Затова когато програмиране в "несигурна среда" използвайте винаги условието в комбинация с isset(), т.е. от примера "if(isset($var) && $var<6)...". Но за да ви уплашим още повече от езика за програмиране PHP ще трябва да споменем, че променливи инициализирани със стойност "null" се третират от isset() като "неинициализирани". Например следният код ще изпише на екрана "not initialized":

$var = null;
if (isset($var)) echo "initialized";
else echo "not initialized";

С други думи точното определение на isset() е "проверява дали променливата е инициализирана и има стойност (т.е. не е null)". Помнете и тази подробност и се пазете от грешки. Съществува функция is_null(), която за нещастие връща "true" при подадена неинициализирана променлива. Изобщо PHP и в момента страда от липсата на истинска is_defined() функция. Затова спазвайте един съвет от мен - нека докато разработвате вашите скриптове винаги имате една команда "error_reporting( E_ALL );" в началото на всеки PHP файл. Когато свършите работа и изчистите всички възможни грешки я махнете.

В заключение: PHP ни дава лесна възможност за промяна на типовете данни. По ирония на съдбата именно това "удобство" ни задължава много по-стриктно да следим сами типовете данни в PHP отколкото го правим в другите езици. С други думи вместо да ни спести, това "удобство" всъщност ни донася допълнитeлна работа. Тук засегнахме само малка част от проблемите, които може да очаквате "под път и над път".

 



Добави коментар

Адресът на електронната поща няма да се публикува


*