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

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

Related

Is there a compelling reason to use PHP's operator === in comparison operations, over ==?

Say I have this code:
$str = '5';
$int = 5;
For comparison, is there any reason to use something like this (with conversion):
if ($int === intval($str)) //...
or do I just use native PHP facilities?
if ($int == $str) //...
To me, == looks simpler, perhaps at the expense of having PHP do the extra work for me.
Using '==' tends to lead to subtle bugs - eg if two strings look like numbers, PHP does not compare them as strings, which can give unexpected results - the most common/scary example is:
<?php
$actual_password = '240610708';
$provided_password = 'QNKCDZO';
// These would presumably be stored in your database
$stored_password_md5 = md5($actual_password); //0e462097431906509019562988736854;
$stored_password_hash = password_hash($actual_password, PASSWORD_DEFAULT);
$computed_password_md5 = md5($provided_password); //0e830400451993494058024219903391
var_dump($stored_password_md5 == $computed_password_md5); // bool(true) - BAD! NO!
var_dump($stored_password_md5 === $computed_password_md5); // bool(false) - Better, but still no. Vulnerable to timing attacks
var_dump(hash_equals($stored_password_md5, $computed_password_md5)); // bool(false) getting somewhere
var_dump(password_verify($provided_password, $stored_password_hash)); // bool(false) best
While in your specific example, this problem doesn't occur, the possible problems lead to a lot of people recommending to /always/ use ===, so you don't have to remember when == is safe and when it isn't.
Depends on what you are trying to do. Some functions might return false or 0 or a positive integer, like strpos(). 0 means the string was found at position 0 so == false would not work as === false.
In your scenario it is fine to use == as this is common when getting values from a DB or $_POST and $_GET, they will always be strings.
Thanks to the comment from Fred Emmott: Be careful, the following return true:
var_dump('0xa' == '10'); // 0xa hex 10 in decimal
var_dump('10pigs' == 10); // pigs truncated
See String conversion to numbers
The == operator just checks to see if the left and right values are equal. But, the === operator (note the extra “=”) actually checks to see if the left and right values are equal, and also checks to see if they are of the same variable type (like whether they are both booleans, ints, etc.).

Using Eval to perform if comparison of strings in PHP

I have some code that looks like this.
eval('$ruleTrue = '."{$value} {$operator} {$value2};");
I am pulling mostly numeric values from a database and comparing them with other numeric values. The operator comes from a database as well. Possible operators are <,>,==.
Well when comparing ints and floats this works perfectly. BUT when comparing strings it breaks. For instance..
WORKS:
5 > 4
$ruleTrue = true
Doesn't Work Right:
John-Adams == Alice
$ruleTrue = true <--- WHY? Because John is not == to Alice.
For some reason my $ruleTrue variable is being returned as true when comparing strings.
You're trying to evaluate this code:
$ruleTrue = John == Alice;
John and Alice aren't strings, they're undefined constants. You want to put quotes around them. But be careful, because if your users are able to edit those fields in your database, they could find a way of unquoting the strings and executing their own php code, which could be disastrous. Eval is very unsecure that way, and you probably shouldn't be using it.
The expression John-Adams == Alice parses somewhat like (John - Adams) == Alice. The left side is trying to subtract two strings (undefined constants are considered 'barewords', and are equal to a stringification of their names; John === 'John', for example), and in order to make sense of such an odd operation, PHP turns both strings into numbers. As integers, both strings have a value of 0, so the left side equates to 0.
An int.
Now, when PHP wants to compare with ==, it wants to coerce both sides into the same type. In this case, it's converting to int. Alice also converts to 0. Both sides being 0, they're "obviously" equal.
In order to prevent this from happening, you should probably put quotes around your values. You might also consider using the strict equals operator (===), unless you really want that type coercion magic.
Alternatively, if you have a known set of operators, you can eliminate eval and make this safer and more robust all around, by making a comparison function that has sub-functions for the operators. Like so:
function compare($value1, $op, $value2) {
static $known_ops = array(
'==' => function($a, $b) { return $a == $b; },
'!=' => function($a, $b) { return $a != $b; },
...
# you can even make up your own operators. For example, Perl's 'eq':
'eq' => function($a, $b) { return "$a" === "$b"; }
...
);
$func = $known_ops[$op];
return $func($value1, $value2);
}
...
$ruleTrue = compare($value, $operator, $value2);
Now you don't have to worry about your values. You do have to worry about $operator, but that's only an issue if you let a user input it without you validating it. In which case, you may want to throw an exception or something if $op wasn't in $known_ops, cause if you leave PHP to handle it, you'll likely get a fatal error when it tries to call null.
Make sure, if the value are strings, they are surrounded with "quotes"

Weird PHP String Integer Comparison and Conversion

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.

Why is my inequality test failing?

The ever flaky Xdebug is on the fritz at the mo' (normal service will be resumed as soon as possible), so I am reduced to "debug by echo".
echo($path_info['filename'] . ' ' . $licence['issue_timestamp'].'<br>');
if ($path_info['filename'] != $licence['issue_timestamp'])
{
die('They are NOT equal');
$_SESSION['error_messages'][] = 'This licence file has been copied';
return False;
}
else
die('They are equal');
outputs
1319266557_ 1319266557
They are equal
Any idea what I am doing wrong? Is there something special about (trailing) underscores?
The docs state:
$a != $b Not equal TRUE if $a is not equal to $b after type juggling.
And that "type juggling" looks mighty suspicious. Elsewhere on that page, it mentions:
If you compare a number with a string or the comparison involves numerical strings, then each string is converted to a number and the comparison performed numerically. These rules also apply to the switch statement. The type conversion does not take place when the comparison is === or !== as this involves comparing the type as well as the value.
And the example is a dead giveaway as to what's happening in your case:
var_dump(100 == "1e2"); // 100 == 100 -> true
In terms of how strings are converted to integers, that can be seen here. The salient bit is (my bold):
If the string does not contain any of the characters '.', 'e', or 'E' and the numeric value fits into integer type limits (as defined by PHP_INT_MAX), the string will be evaluated as an integer. In all other cases it will be evaluated as a float.
The value is given by the initial portion of the string.
Example: $foo = 1 + "10 Small Pigs"; // $foo is integer (11)
That's why "123_" is equal to "123" (a). Bottom line, use !== since that ensures both the value and the type are the same.
(a) See the online PHP executor:
One of the bone-headed things that PHP does is that == only compares values "after type juggling". What this means is that what most sane people think of as == is really === in PHP. Try
$path_info['filename'] !== $licence['issue_timestamp'].

PHP best way to check whether a string is empty or not

I've seen a lot of php code that does the following to check whether a string is valid by doing:
$str is a string variable.
if (!isset($str) || $str !== '') {
// do something
}
I prefer to just do
if (strlen($str) > 0) {
// something
}
Is there any thing that can go wrong with the second method? Are there any casting issues I should be aware of?
Since PHP will treat a string containing a zero ('0') as empty, it makes the empty() function an unsuitable solution.
Instead, test that the variable is explicitly not equal to an empty string:
$stringvar !== ''
As the OP and Gras Double and others have shown, the variable should also be checked for initialization to avoid a warning or error (depending on settings):
isset($stringvar)
This results in the more acceptable:
if (isset($stringvar) && $stringvar !== '') {
}
PHP has a lot of bad conventions. I originally answered this (over 9 years ago) using the empty() function, as seen below. I've long since abandoned PHP, but since this answer attracts downvotes and comments every few years, I've updated it. Should the OP wish to change the accepted answer, please do so.
Original Answer:
if(empty($stringvar))
{
// do something
}
You could also add trim() to eliminate whitespace if that is to be considered.
Edit:
Note that for a string like '0', this will return true, while strlen() will not.
You need isset() in case $str is possibly undefined:
if (isset($str) && $str !== '') {
// variable set, not empty string
}
Using !empty() would have an important caveat: the string '0' evaluates to false.
Also, sometimes one wants to check, in addition, that $str is not something falsy, like false or null[1]. The previous code doesn't handle this. It's one of the rare situations where loose comparison may be useful:
if (isset($str) && $str != '') {
// variable set, not empty string, not falsy
}
The above method is interesting as it remains concise and doesn't filter out '0'. But make sure to document your code if you use it.
Otherwise you can use this equivalent but more verbose version:
if (isset($str) && (string) $str !== '') {
// variable set, not empty string, not falsy
}
Of course, if you are sure $str is defined, you can omit the isset($str) from the above codes.
Finally, considering that '' == false, '0' == false, but '' != '0', you may have guessed it: PHP comparisons aren't transitive (fun graphs included).
[1] Note that isset() already filters out null.
This will safely check for a string containing only whitespace:
// Determines if the supplied string is an empty string.
// Empty is defined as null or containing only whitespace.
// '0' is NOT an empty string!
function isEmptyString($str) {
return !(isset($str) && (strlen(trim($str)) > 0));
}
What about this:
if( !isset($str[0]) )
echo "str is NULL or an empty string";
I found it on PHP manual in a comment by Antone Roundy
I posted it here, because I did some tests and it seems to work well, but I'm wondering if there is some side effect I'm not considering. Any suggestions in comments here would be appreciated.
According to PHP empty() doc (http://ca1.php.net/empty):
Prior to PHP 5.5, empty() only supports variables; anything else will result in a parse error. In other words, the following will not work: empty(trim($name)). Instead, use trim($name) == false.
This simple old question is still tricky.
strlen($var) works perfectly ONLY if you're absolutely sure the $var is a string.
isset($var) and empty($var) result are based on type of the variable, and could be tricky at some cases (like empty string ""). View the table in this page for more details.
UPDATE
There are actually 2 cases for this question:
Case 1: You're sure that your variable is always going to be a "string":
In this case, just test the length:
if(strlen($str) > 0) {
// do something..
}
Case 2: Your variable may and may not be a "string":
In this case, it depends on what you want to do. For me (most of the time), if it's not a string then I validate it as "false". You can do it this way:
if(is_string($var) && $var !== '') {// true only if it's a string AND is not empty
// do something ...
}
And to make it shorter and in 1 condition instead of 2 (specially useful if you're testing more than 1 string in same if condition), I made it into function:
function isNonEmptyString($var) {
return is_string($var) && $var !== '';
}
// Somewhere else..
// Reducing conditions to half
if(isNonEmptyString($var1) && isNonEmptyString($var2) && isNonEmptyString($var3)) {
// do something
}
If your variable $str is not defined then your strlen() method will throw an exception. That is the whole purpose of using isset() first.
trimming the string will also help if there are string with white spaces.
if (isset($str) && trim($str) !== '') {
// code
}
I think not, because strlen (string lenght) returns the lenght (integer) of your $str variable.
So if the variable is empty i would return 0. Is 0 greater then 0. Don't think so.
But i think the first method might be a little more safer. Because it checks if the variable is init, and if its not empty.

Categories