Weird PHP String Integer Comparison and Conversion - php

I was working on some data parsing code while I came across the following.
$line = "100 something is amazingly cool";
$key = 100;
var_dump($line == $key);
Well most of us would expect the dump to produce a false, but to my surprise the dump was a true!
I do understand that in PHP there is type conversion like that:
$x = 5 + "10 is a cool number"; // as documented on PHP manual
var_dump($x); // int(15) as documented.
But why does a comparison like how I mentioned in the first example converts my string to integer instead of converting the integer to string.
I do understand that you can do a === strict-comparison to my example, but I just want to know:
Is there any part of the PHP documentation mentioning on this behaviour?
Can anyone give an explanation why is happening in PHP?
How can programmers prevent such problem?

If I recal correcly PHP 'casts' the two variables to lowest possible type.
They call it type juggling.
try: var_dump("something" == 0);
for example, that'll give you true . . had that bite me once before.
More info: http://php.net/manual/en/language.operators.comparison.php

I know this is already answered and accepted, but I wanted to add something that may help others who find this via search.
I had this same problem when I was comparing a post array vs. keys in a PHP array where in my post array, I had an extra string value.
$_POST["bar"] = array("other");
$foo = array(array("name"=>"foobar"));
foreach($foo as $key=>$data){
$foo[$key]["bar"]="0";
foreach($_POST["bar"] as $bar){
if($bar==$key){
$foo[$key]["bar"]="1";
}
}
}
From this you would think that at the end $foo[0]["bar"] would be equal to "0" but what was happening is that when $key = int 0 was loosely compared against $bar = string "other" the result was true to fix this, I strictly compared, but then needed to convert the $key = int 0 into a $key = string "0" for when the POST array was defined as array("other","0"); The following worked:
$_POST["bar"] = array("other");
$foo = array(array("name"=>"foobar"));
foreach($foo as $key=>$data){
$foo[$key]["bar"]="0";
foreach($_POST["bar"] as $bar){
if($bar==="$key"){
$foo[$key]["bar"]="1";
}
}
}
The result was $foo[0]["bar"]="1" if "0" was in the POST bar array and $foo[0]["bar"]="0" if "0" was not in the POST bar array.
Remember that when comparing variables that your variables may not being compared as you think due to PHP's loose variable typing.

Related

Why empty('0') = true; empty('00') = false;

I eval $var using
if(empty($_GET['var'])){
...
}
I take TRUE from
https://myweb.com/?var=0
I take FALSE from
https://myweb.com/?var=00
The empty pseudo-function shares its logic with casting to boolean - if something is equivalent to "false", it is considered "empty".
The list of values which are considered "empty" is intended to be helpful, but is occasionally confusing, because there isn't really one perfect answer. Starting off with integers, it seems reasonable that 0 is "empty", but for instance 1 is not. Because user input almost always comes in the form of strings (particularly on the web, where PHP is most at home), it's also useful for the string "0" to behave the same as the integer 0.
On the face of it, "00" should also be equivalent to 0, and therefore "empty", but now things start getting messy: if you convert the string "hello" to an integer, that is also 0, so is "hello" also empty? That wouldn't be very useful.
The truth is, casts such as this can only really work one of two ways:
Throw an error on any conversion which is not 100% unambiguous.
Pick a set of compromises which is mostly useful, but not entirely consistent.
PHP picked the second route, and the difference between empty("0") and empty("00") is one of the side effects of the particular compromise chosen. Other languages which took a similar route (e.g. Perl, JavaScript) have different compromises, with different surprising outcomes.
See also my answer to a similar question here.
Because 0 gives empty in PHP if you check without type, but I think 00 type-cast to a string value and thus it's not empty.
You can strict check with type via the operator ===
I think it's probably because you are receiving the 00 as string value in your code. Because 0 and 00 are both considered as empty by the empty function in PHP. Try executing the code below in one of your PHP's to understand better. You can even check out the sandbox link.
<?php
$a = 0;
$b = 00;
$c = '00';
if(empty($a)) echo 'empty a';
if(empty($b)) echo 'empty b';
if(empty($c)) echo 'empty c';
?>
Check the documentation of empty() function, there are a list of values that are considered as empty by it.
The empty() function checks whether a variable is empty or not.
This function returns false if the variable exists and is not empty,
otherwise it returns true.
The following values evaluates to empty:
0
0.0
"0"
""
NULL
FALSE
array()
Additionally, when you are dealing with numeric check, you should always typecast the value to integer and then compare if it's 0 or not. This would be the ideal approach.
<?php
$a = 0;
$b = '0';
$c = '00';
$d = 1;
$e = '1';
if(!intval($a)) echo 'empty a ';
if(!intval($b)) echo 'empty b ';
if(!intval($c)) echo 'empty c ';
if(!intval($d)) echo 'empty d ';
if(!intval($e)) echo 'empty e ';
?>

PHP Why does explicit typecast + 1 work, but not with an increment operator

Firstly, let's say for the sake of argument, the reasons why I want to do this is arbitrary and not solution specific.
I would like to explicitly cast a variable, regardless of input, and increment it after typecasting, not with the original variable. For example, not like this:
$num = "47 downvotes";
(int) ++$num;
I am looking for something similar to this psuedo-coded version, or a variation thereof:
$num = "value";
++((int) $num);
For PHP being loose, I was really hoping this to work, but I can't use the Pre-increment operator without creating another variable first.
$num = "value";
$cast = (int) $num;
echo ++$cast;
While testing, I found that PHP is loose enough for it to work by adding a digit however:
$num = "47 dogs";
echo ((int) $num) + 1;
I also understand my first example, isn't wrong, but again, for arbitrary reasons, I need to make sure it has been casted prior to incrementing/decrementing.
So the question is, why is PHP loose enough for the latter to compile?
If you could provide resources or links to any reputable reading material I would appreciate that as well.
With explicit typecasting you have to assign the result to a variable. In your examples, you are trying to increment variables when they are strings, which fails or doesn't produce the result you expect in combination with typecasting.
Look at your original example:
<?php
$num = "47 downvotes";
echo $num . PHP_EOL;
echo ++$num;
The result of incrementing a string isn't what you expect it to be:
47 downvotes
47 downvotet
So your original supposition is that PHP doesn't work when in fact it does.
$num = "47 downvotes";
echo (int) ++$num . PHP_EOL;
$num2 = "47";
echo (int) ++$num2;
Output:
47
48
The process of typecasting is inherently complicated, and has all sorts of behavior that can produce unexpected results, and just isn't the catchall dependable "fix your input" that will let you find the numeric portion of any string available to you in a single line of code, but that doesn't mean that PHP is flawed.

php variables, strings and arrays: associative array returns first letter of string

I understand that, with a sting assigned to a variable, individual characters can be expressed by using the variable as an indexed array, but why does the code below, using an associative array, not just die with missing required? Why does 'isset' not throw FALSE on an array key that definitely doesn't exist?
unset($a);
$a = 'TESTSTRING';
if(!isset($a['anystring'])){
die('MISSING REQUIRED');
}else{
var_dump($a['anystring']);
}
The above example will output:
string(1) "T"
EDIT:
As indicated by Jelle Keiser, this is probably the safer thing to do:
if(!array_key_exists('required',$_POST)){
die('MISSING REQUIRED');
}else{
echo $_POST['required'];
}
What PHP is doing is using your string as a numeric index. In this case, 'anystring' is the equivalent of 0. You can test this by doing
<?php
echo (int)'anystring';
// 0
var_dump('anystring' == 0);
// bool(true)
PHP does a lot of type juggling, which can lead to "interesting" results.
$a is a string not an associative array.
If you want to access it that way you have to do something like this.
$a['anystring'] = 'TESTSTRING';
You need to use array_key_exists() to test if a key exists
The working of isset is correct in your case.
Because $a is a string, the index-operator will give you the specified char in the string at the declared position. (like a "Char-Array")
A small example:
$a = 'TESTSTRING';
echo $a[0]; // Output: T
echo $a[1]; // Output: E
// ...
This will output the first and the second character at index 0 and 1 of the string.
And because the index-operator always expects an integer value on strings. The given value will be automatically casted to an integer. You can see this, when you cast the string to an integer, like this:
echo (int) 'TESTSTRING'; // Output: 0
For char-access on strings, also see the PHP-Manual.
Try enabling PHP to show all errors by using error_reporting(E_ALL);
This should give you a warning saying you are using an illegal offset. PHP therefore automatically assumes you are looking for the first element in the array or letter in this case.
it works as expected for... it returned false... but when I force it to return true ... itz throws an error saying illegal offset somekind.... but still output the first string.... as anystring casted as int equals to 0.. check the version of php you are using bro... I used notepad++ to create the php file... no special ide...

How come ('-' == 0) === true?

I was working here with some exploded string walks:
array_walk($data, function(&$value)
{
// Transform numerics into appropriate type numbers
if (is_numeric($value))
{
$value = substr_count($value, '.') == 1 ? (float) $value : (int) $value;
}
// Transform dashes into nulls
if ($value == '-')
{
$value = null;
}
});
to transform values into their appropriate types, and some special character handling.
Where I stumbled upon an interesting, huh, bug?
The Bug
I was amazed, that each entry, that had it's initial value as string(1) '0' ended up being a null.
At first, I thought that the problem relies in (float) and (int) typecasts, though, after debugging:
var_dump((float) '0', (int) '0');
I saw that's not the case, getting the expected result:
float(0)
int(0)
It took me a while, to attempt to debug the, what at the moment appeared to be an obvious, weak type check, but, once I did, I was shocked:
var_dump('-' == 0);
The above expression appears to be:
bool(true)
Now, while writing, I thought I should debug some more, so:
var_dump( '=' == 0 );
var_dump( 'What a lovely nonsense?' == 0 );
var_dump( 0 == 'DAFUQ?' ); // maybe it's because of order? It's PHP, world of miracles, you know...
And, every expression listed above is bool(true).
Okay, maybe that's because internally, mystically PHP casts the expression into a (bool)?
var_dump( (bool) '-' == 0 );
No:
bool(false)
So, so, so...
I made a test-case here: http://codepad.org/smiEvsDj
The problem exists in 5.2.5 (codepad), also in 5.4.3 (friend) and also in 5.4.17 (my actual environment).
What is the reason behind this feature / bug / what-the-F-actually-is-this?
You have stumbled upon one of the major complaints that people have about PHP as a language: The fact that the "==" operator is not transitive.
Any string "foo" == TRUE, because the PHP people wanted this to work:
if ($string) {
// do something if $string is set
}
Yet, converting a string to a number (which PHP always tries to do when you use "=="), "foo" == 0!
Of course, TRUE != 0. That is a major pain when dealing with PHP, it's not logical, but it's reality.
It's trying to parse numbers from your strings, not finding any digits and automatically reverting to 0. I think...
e.g. it sees 'What a lovely nonsense?' == 0, sees that you're comparing integers, and tries to convert What a lovely nonsense? to an integer. As there are no numerical digits it defaults to 0, and believes that LHS == RHS so returns true

is_int and GET or POST

Why does is_int always return false in the following situation?
echo $_GET['id']; //3
if(is_int($_GET['id']))
echo 'int'; //not executed
Why does is_int always return false?
Because $_GET["id"] is a string, even if it happens to contain a number.
Your options:
Use the filter extension. filter_input(INPUT_GET, "id", FILTER_VALIDATE_INT) will return an integer typed variable if the variable exists, is not an array, represents an integer and that integer is within the valid bounds. Otherwise it will return false.
Force cast it to integer (int)$_GET["id"] - probably not what you want because you can't properly handle errors (i.e. "id" not being a number)
Use ctype_digit() to make sure the string consists only of numbers, and therefore is an integer - technically, this returns true also with very large numbers that are beyond int's scope, but I doubt this will be a problem. However, note that this method will not recognize negative numbers.
Do not use:
is_numeric() because it will also recognize float values (1.23132)
Because HTTP variables are always either strings, or arrays. And the elements of arrays are always strings or arrays.
You want the is_numeric function, which will return true for "4". Either that, or cast the variable to an int $foo = (int) $_GET['id']...
Checking for integers using is_int($value) will return false for strings.
Casting the value -- is_int((int) $value) -- won't help because strings and floats will result in false positive.
is_numeric($value) will reject non numeric strings, but floats still pass.
But the thing is, a float cast to integer won't equal itself if it's not an integer. So I came up with something like this:
$isInt = (is_numeric($value) && (int) $value == $value);
It works fine for integers and strings ... and some floating numbers.
But unfortunately, this will not work for some float integers.
$number = pow(125, 1/3); // float(5) -- cube root of 125
var_dump((int) $number == $number); // bool(false)
But that's a whole different question.
How i fixed it:
$int_id = (int) $_GET["id"];
if((string)$int_id == $_GET["id"]) {
echo $_GET["id"];
}
It's probably stored as a string in the $_GET, cast it to an int.
Because $_GET is an array of strings.
To check if the get parameter contains an integer you should use is_numeric()
Because $_GET['id'] is a string like other parts of query string. You are not converting it to integer anywhere so is_int return false.
The dirty solution I'm using is this:
$val = trim($_GET['id']);
$cnd = ($val == (int)$val);
echo $cnd ? "It's an int" : "Not an int";
Apart from the obvious (ugly code that hides its workings behind specifics of the php engine), does anybody know cases where this goes wrong?
Prabably best way to check if value from GET or POST is integer is check by preg_match
if( preg_match('/^[0-9]+$/', $_GET['id'] ){
echo "is int";
}
You can possibly try the intval() which can be used to test the value of your var. e.g
If(intval($_GET['ID']==0)
The function will check if the var is integer and return TRUE if not FALSE

Categories