我正在运行一些单元测试,并使用我正在使用的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)”
有人可以解释为什么会这样吗?
答案 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建议并将值转换为(字符串)。
谢谢!