意外的PHP切换行为

时间:2015-08-04 21:10:33

标签: php switch-statement

我正在运行一些单元测试,并使用我正在使用的switch语句遇到意外行为。我已经隔离了下面的条件。

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

这是我的第一轮测试:

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

这是非常明显的吗?好了,现在看看这些:

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

0和true的奇怪结果是什么?如果1 / true和0 / false返回相同的值,我会把它写成松散的输入。但他们没有!

如果我将值转换为(字符串),则交换机按预期工作。

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

我不明白为什么开关不会像我想要的那样“工作”而不使用“(string)”

有人可以解释为什么会这样吗?

5 个答案:

答案 0 :(得分:3)

这是预期的行为。在进行比较时,PHP会在搜索匹配项时更改值类型。

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是一种弱类型的语言,有时会让你陷入困境。您可以考虑将交换机重新分解为if / else if / else结构,并使用===运算符进行强有力的比较。

答案 1 :(得分:2)

根据PHP的文档:

  

请注意,开关/外壳的比较松散。

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

如果要进行类型比较,则需要重新构建代码。例如:

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

    if($val === 'c') 
        return 'second';

    return 'third';
}

请注意我没有else的任何内容。这是因为每个语句都返回一些内容......否则该函数默认返回third

答案 2 :(得分:0)

你有简单的答案:松散的comaprison。但是什么是解决方案?如果您想要比较数字,请不要使用字符串。如果你想要布尔值,请使用布尔值。尝试在调用函数/方法之前验证(或转换)变量。像强类型语言一样编写代码,如果你想要int,你可以这样做:

/**
 * @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';
    }
}

答案 3 :(得分:0)

PHP为weakly (or loosely) typed,因此您需要适当地转换$val和/或在必要时断言其类型:

/**
 * @param string $val
 */

function test($val)
{
    assert('is_string($val)', 'expecting string');
    switch((string)$val) {
        case 'a':

}

另一种方法是使用字典:

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

由于您正在讨论运行测试,请注意上述内容可能具有屏蔽可能带来的不良副作用,即您确实拥有count($opts) + 1个决策路径在那里,但基本的圈复杂度只能看到两个。

一个更加笨拙的设置,可以保持复杂性(即从测试中获得正确的代码覆盖率)

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);
}

上面的缺点是不允许在单个统一(即交换机)中拥有所有选项,并且只有在函数名中可以使用的值时才能采用,并且检查不区分大小写。但在尝试了几种方法后,我发现这是一个可行的妥协。

答案 4 :(得分:0)

简答:投出输入值

感谢您的回复。以下链接非常有助于围绕正在发生的事情。

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

我测试的方法从未打算用于整数。我只是为了好玩而碰巧抛出了几种不同的类型,偶然发现了意想不到的行为。

阅读完所有这些回复后,我将采用不同的方法,而不是使用switch语句。我认为@lserni提到的字典方法是这种特定实现的方法。但是,如果我想保留相同的代码,快速解决方法是执行@Styx建议并将值转换为(字符串)。

谢谢!