繁体   English   中英

意外的PHP切换行为

[英]Unexpected PHP Switch Behavior

我正在运行一些单元测试,并使用我正在使用的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和真? 如果1 / true和0 / false返回相同的值,我会把它写成松散的输入。 但他们没有!

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

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

我不明白为什么开关不会按照我的意图“工作”而不使用“(字符串)”

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

这是预期的行为。 在进行比较时,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结构,并使用===运算符进行强比较。

根据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

你有简单的答案:松散的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';
    }
}

PHP是弱(或松散)类型的 ,因此您需要适当地转换$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);
}

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

简短回答:投射输入值

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

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

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

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

谢谢!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM