Unexpected behavior of PHP switches

I ran some unit tests and came across unexpected behavior using the switch statement that I used. I highlighted the condition below.

function test($val) { switch($val) { case 'a': case 'b': return 'first'; break; case 'c': return 'second'; break; default: return 'third'; } } 

here is my first round of tests:

 test('a') => 'first' test('b') => 'first' test('c') => 'second' test('d') => 'third' test('0') => 'third' test('1') => 'third' test('true') => 'third' test('false') => 'third' 

Is this a matter of course right? ok now check them out:

 test(0) => 'first' // expected 'third' test(1) => 'third' test(true) => 'first' // expected 'third' test(false) => 'third' test(null) => 'third' test([]) => 'third' 

What about weird results with 0 and true? I would do this so as not to print if 1 / true and 0 / false returned the same values. But they do not!

If I convert the value to a string, the switch works as intended.

 test((string) 0) => 'third' test((string) 1) => 'third' test((string) true) => 'third' test((string) false) => 'third' 

I do not understand why the switch does not work, as I thought, without using "(string)"

Can someone explain why this is happening?

+7
php switch-statement
source share
5 answers

This is the expected behavior. When you perform comparisons, PHP will change the type of value when it matches.

 test(0) => 'first' // 'a' is altered into int 0 and therefore matches var_dump((int) 'a'); // results 'int(0)' test(true) => 'first' // both true and 'a' are truthy statements therefore matches. if ('a' == true) echo "its true"; 

PHP is a weakly typed language that sometimes bites you in the butt. You can consider rearranging the switch into an if / else if / else structure and use the === operator for strong comparisons.

+3
source share

In the PHP documentation:

Note that the switch / case does not compare the comparison.

http://php.net/manual/en/control-structures.switch.php

If you want to do type comparisons, you will need to rebuild your code. Example:

 function test($val) { if($val === 'a' || $val === 'b') return 'first'; if($val === 'c') return 'second'; return 'third'; } 

Note that I do not have else . This is because each operator returns something ... Otherwise, the function will return third by default.

+2
source share

You have simple answers: free comaprison. But what is a solution? If you want strings not to be used in comparison numbers. If you want boolean, use boolean. Try to check (or apply) the variables before calling the function / method. Write your code in a strongly typed language, if you want an int, you can do something like this:

 /** * @param int $val */ function test($val) { //exception if (!is_int($val)) { throw new InvalidArgumentException('$val expected to be int'); } //or cast - but may beahave unexpected $val = (int)$val switch($val) { case 0: return 'first'; break; case 1: return 'second'; break; default: return 'third'; } } 
0
source share

PHP is weak (or loose) typed , so you need to give $val appropriately and / or approve its type if necessary:

 /** * @param string $val */ function test($val) { assert('is_string($val)', 'expecting string'); switch((string)$val) { case 'a': } 

An alternative is to use a dictionary:

 $opts = [ 'a' => 'first', 'b' => 'first', ... ]; foreach ($opts as $opt => $response) { if ($opt === $val) { return $response; } } return 'default'; 

Since you are talking about running tests, keep in mind that the above has probably an undesirable side effect of masking cyclic complexity, i.e. you really have solutions to count($opts) +1, but the main cyclomatic complexity sees only two.

A more inconvenient setup that retains complexity (i.e. you get the correct code coverage from tests) may be

 private function testA($val) { return 'first'; } private function testB($val) { return $this->testA($val); // or 'first' again } ... public function test($val) { if (!is_class_method($this, $method = 'test' . $val)) { $method = 'testDefault'; } $this->$method($val); } 

The aforementioned drawback does not allow you to have all the options in a single unity (i.e. a switch) and can be accepted only if you have values ​​that can be used in the function name, and the check is case-insensitive, But after several attempts, I I find this effective compromise.

0
source share

Short answer: enter the input value

Thanks for all the answers. The following link was very helpful in wrapping my head around what was happening.

http://php.net/manual/en/types.comparisons.php#types.comparisions-loose

The method I tested was never intended to be used with integers. I just had to drop a few different types just for fun and accidentally stumbled upon inadvertent behavior.

After reading all of these answers, I will take a different approach and will not use the switch statement. I think the vocabulary approach mentioned by @lserni is the way to this particular implementation. However, if I wanted to keep the same code, a quick solution would be to do what @Styx suggested and pass the value in the form of (string).

Thanks!

0
source share

All Articles