I have this code:
$r = do_something($data);
if ($r == 1)
{
echo "it is 1";
}
switch ($r)
{
case "a":
print "a";
break;
case "b":
print "b";
break;
default:
print "default";
}
With this code the output should be:
it is 1
default
but the surprise is that the output is:
it is 1
a
How is this possible?
edit: after some test i see that:
$r === true.
so the new question is: when the var is true how work the switch?
PHP's "type juggling" rules are rather tricky, and occasionally unintuitive and even controversial. There's a great big table in the manual showing what happens when you make comparisons of various sorts, but to explain your particular case:
You are working with three different types: $r is currently true, which is a boolean; the if statement tests against 1, which is an integer; and the switch statement tests against "a" and "b", which are strings.
When you compare a boolean to an integer, PHP first converts the integer to a boolean, using the rule that 0 converts to false, and anything else converts to true. This has the effect in your case of $r == 1 evaluating to true, but $r == 42 would also evaluate to true.
When you compare a boolean to a string, a similar thing happens, but here the string gets converted according to this rule: an empty string is false, anything else is true. So $r == "a" also evaluates to true in your example, which is why that branch of the switch statement is executed.
To get the result you were hoping for, you need to force the type conversion to happen in a different way. There are a few ways to do this, but the simplest in your case is to cast $r to the same type as what you're comparing it against:
(int)$r will give you the integer 1 for a value of true, and 0 for false, so if ( (int)$r == 1 ) will give the same result, but be clearer that $r wasn't actually an integer at that point in the code.
more importantly, (string)$r will give you the string "1", so switch( (string)$r ) won't have to do any "type juggling" to compare against strings like "a" and "b", and you won't get any surprises there.
I think your do_something($data) function returns 1 if it does what it suppose to do. I tried this code and it works. Here it is.
function do_something($data){
return 1;
}
$r= do_something('abc');
if ($r == 1)
{
echo "it is 1<br />";
}
switch ($r)
{
case "a" : print "a"; break;
case "b" : print "b"; break;
default : print "default";
}
OUTPUT:
it is 1
default
Related
Expect you have following situation:
$myVar = null;
switch($myVar) {
case is_int($myVar):
echo "i am an int";
break;
case null:
echo "i am null";
break;
default:
echo "failure";
}
The result is "i am an int". But I expected "i am null" as a result.
Is this a bug in php (my version: 5.5.11) or natural behaviour?
switch($myVar) means test the value of $myvar against each case expression until a match is found.
is_int($myVar) returns a Boolean false when $myvar is null.
Loose comparison of null against Boolean false (null will be converted to a Boolean false for the comparison) returns a true, so the case is accepted
You can get round this by changing the order of your case statements to test the null case first, but
Moral: It's never a good idea to use expressions in case statements
Why dont you use gettype() to identify the data type of the variable.
$myvar = null;
echo gettype($myvar)."\n";
$myvar = 1;
echo gettype($myvar)."\n";
$myvar = "a";
echo gettype($myvar);
DEMO
I have the following PHP (the server is running version 5.3.x) in a script which is giving me a result that I am having trouble understanding. The general idea of this code is that I have a "normal mode", and two maintenance modes. In the first maintenance mode, data is only evaluated and can be viewed by an admin but is not stored to the database. If I set $maintenance_mode_enabled = 2;, then the same "preview" output should be displayed but only SOME specific updates to the database should be processed. The reason I added the ==2 comparison is because I found the need for a third option after I had setup the true/false for the basic default maintenance mode. At any rate, I noticed 18 records on my last maintenance_mode_enabled = true; run that were partially updated during the process, just as though I had set maintenance_mode_enabled = 2;.
$maintenance_mode_enabled = true;
if ($maintenance_mode_enabled){
echo "Case 0\n";
}
if (!$maintenance_mode_enabled){
echo "Case 1\n";
}
if ($maintenance_mode_enabled == 2){
echo "Case 2\n";
}
The output I get is:
Case 0
Case 2
From what I understood, true (being boolean) is definitely not equal to 3. I am familiar with some oddities when comparing false, NULL and 0, but this problem with integers and TRUE is entirely new to me.
Any ideas as to why this isn't working? I realize that I can just as easily change $maintenance_mode_enabled to an integer instead of a bolean by default, and set it as either 0, 1 or 2 to get the desired results, but I really want to understand WHY this seems to defy logic.
The reason this happens is because you're comparing a boolean to an integer. As with many languages, at the core of the comparison function it's casting the second part of your comparison to a boolean. Any non-NULL, non-zero, non-empty or non-false value, in this case 2 is "true."
As the previous answer mentions I would change the code to use strict comparison. I would also change from three separate if-statements to one if-elseif statement:
if ($maintenance_mode_enabled === true) {
// catches only true not > 0
echo "Case 0\n";
} elseif ($maintenance_mode_enabled === false) {
// catches only true not = 0
echo "Case 1\n";
} elseif ((int)$maintenance_mode_enabled === 2) {
echo "Case 2\n";
}
I recommend this change because maintenance mode can only have one value.
EDIT
I didn't realize true and 2 could coexist. You could do:
if ($maintenance_mode_enabled) {
echo "Case 0\n";
if (2 === (int)$maintenance_mode_enabled) {
echo "Case 2\n";
}
} else {
echo "Case 1\n";
}
Use the === operator for true otherwise all non 0 / null / false will be true.
Use the === operator for false otherwise all equal to 0 / false / null will show as "false"
The following will output Case 0
<?php
$maintenance_mode_enabled = true;
if ($maintenance_mode_enabled === true){
// catches only true not just > 0
echo "Case 0\n";
}
elseif (!$maintenance_mode_enabled === false){
// catches only false not including = 0
echo "Case 1\n";
}
elseif ($maintenance_mode_enabled == 2){
echo "Case 2\n";
}
?>
Oh, NOW I get it. It seems the problem here is that when I do the loose (==) comparison of a boolean with an integer, the type casting is converting the integer into a boolean, thus resulting in 2 being equal to true - since both are being tested as booleans. The solution is to use strict (===) comparison, so that both must be of the same type... i.e.: 2 (integer), is not exactly the same as true, since true is is of a different type - boolean.
I had the following in my code:
$mixed = array();
$mixed[0] = "It's a zero!";
$mixed['word'] = "It's a word!";
foreach ($mixed as $key => $value) {
if ($key == 'word') {
echo $value.'<br />';
}
}
The above would for some reason print both "It's a zero!" and "It's a word!". I was expecting it to print only "It's a word!". Why is that?? I feel like I am missing something important. When I was using === in the if statement, it worked as expected, in other words it printed only "It's a word!". I know there's a difference between the equal and identical operators, but the first example is not equal is it?
== does the type-conversion for you before comparison.
When you did an == with an integer 0, it converted 'word' into the appropriate integer value.
intval() returns 0 when supplied a pure-string, so 0 matched. The other was matched in string-context, and that matched as well.
=== does no such implicit conversion, so it returned true only in one case, when the strings were actually identical.
PHP variables have type.
== checkes equality after conversion to the same type, === also checks the type. Use var_dump to see what the real types are.
See #Cthulhu 's answer above which is much clear.
Apart from that, here is a different example.
strpos() function returns the position of the needle from haystack.
<?php
$pos_a = strpos('apple', 'a'); // a is in the first position.
$pos_b = strpos('apple', 'b'); // there is no b.
if ($pos_a){
echo 'we got a!'."\n";
}
if ($pos_b){
echo 'we got b!'."\n";
}
strpos return FALSE if the needle is not found. But you will see that php does not run any echo statement.
If you var_dumo()'d these 2 values, you will see that $pos_a and $pos_b contain 0 and FALSE.
if statement just failed because 0 and FALSE both are considered FALSE unless you use ===
Now try this:
<?php
$pos_a = strpos('apple', 'a'); // a is in the first position.
$pos_b = strpos('apple', 'b'); // there is no b.
if ($pos_a !== FALSE){
echo 'we got a!'."\n";
}
if ($pos_b !== FALSE){
echo 'we got b!'."\n";
}
Now you will see the desired result as it echos "we got a!".
$a == $b Equal TRUE if $a is equal to $b after type juggling.
$a === $b Identical TRUE if $a is equal to $b, and they are of the same type.
it looks that
if you check 0 against a string with == then PHP returns true:
php -r 'var_dump(0 == "statuses");'
-> returns TRUE
but not if your string has a number at the beginning:
php -r 'var_dump(0 == "2statuses");'
-> returns FALSE
from the specs I get it that it attempts a conversion - in this case the string to number.
so better use ===
http://php.net/manual/en/language.operators.comparison.php
switch(false) {
case 'blogHitd':
echo('ddd');
break;
case false:
echo('bbbb');
break;
default:
echo 'alert("error action");';
}
-------output------
bbbb
switch(true) {
case 'blogHitd':
echo('ddd');
break;
case true:
echo('bbbb');
break;
default:
echo 'alert("error action");';
}
-------a strange output-------
ddd
Why, when I pass the value of true it will always select the first one?
From the PHP documentation on Booleans:
When converting to boolean, the following values are considered FALSE:
the boolean FALSE itself
the integer 0 (zero)
the float 0.0 (zero)
the empty string, and the string "0"
an array with zero
elements
an object with zero member variables (PHP 4 only)
the special
type NULL (including unset variables
SimpleXML objects created from
empty tags
Every other value is considered TRUE (including any resource).
The last sentence of this quoted passage is the line of interest in your case.
Switching "true" is only useful if you've got functions or variables in your "case" line
switch(true)
{
case is_array($array):
echo 'array';
break;
default:
echo 'something else';
break;
}
Note that switch/case does loose comparision.
http://www.php.net/manual/en/types.comparisons.php#types.comparisions-loose
PHP will typecast values for you, don't forget:
php > var_dump(true == 'bloghitd');
bool(true)
In this scenario the switch only runs the first valid case.
It is useful in the case that you have more than one possible answer but you want to run only the first one. For example:
switch(true){
case 1 == 2:
echo '1 == 2';
break;
case 2 == 2:
echo '2 == 2';
break;
case 3 == 3:
echo '3 == 3';
break;
case 4 == 1:
echo '4 == 1';
break;
}
The output:
2 == 2
Both the second and third cases are true, but we only get the second (which is the first TRUE).
I have the following php code that gives me an unexpected result:
$foo = NULL;
switch($foo)
{
case 0:
print "What?!";
}
I'd expect the result to be nothing, but it matches case 0. The php manual says that NULL is a non-value, so how can it equal 0?
The switch statement applies loose comparison which means that the following things are treated as equivalent to 0:
false
0
"0"
NULL
"any string"
""
beacuse php is not type strict language
$foo = NULL;
if( isset( $foo ) ) {
switch( $foo ) {
case 0:
print "WTF!!!";
}
}
This can be also written like
$foo = NULL;
switch( true )
{
case ( 0 === $foo ):
print "What?!";
default:
print "Default?!";
}
PHP is doing a type-coerced, weak comparison. You will need to do this instead:
$foo = NULL;
if ($foo === 0)
print "WTF!!!";
You can do what I did - it's lazy but it works.
Before running the switch, I checked if the value is null and, if so, changed it to something known:
IF ($foo==null) {
$foo == 99;
}
switch($foo)
{
case 99:
print "This is NULL"; break;
case 0:
print "What?!";
}
I'm assuming here, but it could be that the switch statement coerces the value of $foo when comparing to 0. To test this hypothesis, why don't you try adding this above the switch statement:
echo $foo == NULL;
This should echo 1 before the curse, if I'm correct...
EDIT: The inaccuracy is with my testing, and it was pointed out to me. Check the comments, if you are interested.
From what I've tested, the top answer is inaccurate.
It seems as though a PHP switch statement sees NULL as a "joker", and applies any case to it.
I tried with different numbers and with a string, and they all fired. Nothing there suggests it should be NULL, not even on PHP loose comparison.
So my suggestion is adding a case NULL: at the start, just as you add default at the end.
$foo = NULL;
switch($foo)
{
case NULL:
print "This is NULL"; break;
case 0:
print "What?!";
}
As of PHP 8, you can use the match expression:
The match expression branches evaluation based on an identity check of a value.
Similarly to a switch statement, a match expression has a subject expression that is compared against multiple alternatives.
Unlike switch, it will evaluate to a value much like ternary expressions. Unlike switch, the comparison is an identity check (===) rather than a weak equality check (==).
Match expressions are available as of PHP 8.0.0.
Per your example:
$foo = null;
$value = match($foo) {
0 => print('What?')
};
Will output:
Fatal error: Uncaught UnhandledMatchError: Unhandled match value of type null
So you can add a try/catch and handle it accordingly or add a default "catch-all":
$compare = null;
$value = match($compare) {
0 => print('What?'),
default => print('Default!')
};