处理算术、按位和串联运算中的差异
算术、位运算和连接操作是任何 PHP 应用程序的核心。在本节中,您将了解在迁移到 PHP 8 后,这些简单操作中可能出现的隐患。您必须了解 PHP 8 中的变化,这样才能避免应用程序中可能出现的代码错误。因为这些操作非常普通,如果不了解这些知识,就很难发现迁移后的错误。
让我们先看看 PHP 在算术和位操作中是如何处理非标量数据类型的。
处理算术和位运算中的非标量数据类型
从历史上看,PHP 引擎对在算术或位运算中使用混合数据类型非常宽容。我们已经了解了涉及数字、前导数字和非数字字符串和数字的比较操作。正如我们所学到的,当使用非严格比较时,PHP 会调用类型戏法,在执行比较之前将字符串转换为数字。当 PHP 执行涉及数字和字符串的算术运算时,也会进行类似的操作。
在 PHP 8 之前,算术运算中允许使用非标量数据类型(除字符串、int、浮点数或布尔型之外的数据类型)。PHP 8 已经禁止了这种不良做法,不再允许数组、资源或对象类型的操作数。当在算术运算中使用非标量操作数时,PHP 8 始终会抛出 TypeError。这一普遍变化的唯一例外是,当所有操作数都是数组类型时,仍然可以执行算术运算。
有关算术和按位运算重大变化的更多信息,请查看此处: https://wiki.php.net/rfc/arithmetic_operator_type_checks 。 |
下面的代码示例说明了 PHP 8 中算术运算符处理差异:
-
首先,我们定义要在算术运算中测试的非标量数据样本:
// /repo/ch06/php8_arith_non_scalar_ops.php $fn = __DIR__ . '/../sample_data/gettysburg.txt'; $fh = fopen($fn, 'r'); $obj = new class() { public $val = 99; }; $arr = [1,2,3];
-
然后,我们尝试将整数 99 添加到资源和对象中,并对数组执行模运算:
echo "Adding 99 to a resource\n"; try { var_dump($fh + 99); } catch (Error $e) { echo $e . "\n"; } echo "\nAdding 99 to an object\n"; try { var_dump($obj + 99); } catch (Error $e) { echo $e . "\n"; } echo "\nPerforming array % 99\n"; try { var_dump($arr % 99); } catch (Error $e) { echo $e . "\n"; }
-
最后,我们将两个数组相加:
echo "\nAdding two arrays\n"; try { var_dump($arr + [99]); } catch (Error $e) { echo $e . "\n"; }
运行示例代码时,请注意 PHP 7 是如何执行静默转换并允许操作继续进行的:
root@php8_tips_php7 [ /repo/ch06 ]#
php php8_arith_non_scalar_ops.php
Adding 99 to a resource
/repo/ch06/php8_arith_non_scalar_ops.php:10:
int(104)
Adding 99 to an object
PHP Notice: Object of class class@anonymous could not be
converted to int in /repo/ch06/php8_arith_non_scalar_ops.php on
line 13
/repo/ch06/php8_arith_non_scalar_ops.php:13:
int(100)
Performing array % 99
/repo/ch06/php8_arith_non_scalar_ops.php:16:
int(1)
Adding two arrays
/repo/ch06/php8_arith_non_scalar_ops.php:19:
array(3) {
[0] => int(1)
[1] => int(2)
[2] => int(3)
}
尤其令人吃惊的是,我们可以对数组执行求模操作!在 PHP 7 中,给对象加值时会生成一个通知。然而,PHP 类型会将对象转换为一个值为 1 的整数,从而使算术运算的结果为 100。
在 PHP 8 中运行相同代码示例的输出结果则完全不同:
root@php8_tips_php8 [ /repo/ch06 ]#
php php8_arith_non_scalar_ops.php
Adding 99 to a resource
TypeError: Unsupported operand types: resource + int in /repo/
ch06/php8_arith_non_scalar_ops.php:10
Adding 99 to an object
TypeError: Unsupported operand types: class@anonymous + int in
/repo/ch06/php8_arith_non_scalar_ops.php:13
Performing array % 99
TypeError: Unsupported operand types: array % int in /repo/
ch06/php8_arith_non_scalar_ops.php:16
Adding two arrays
array(3) {
[0]=> int(1)
[1]=> int(2)
[2]=> int(3)
}
从输出中可以看到,PHP 8 始终会抛出 TypeError
,除了在添加两个数组时。在这两个输出中,您可以观察到当添加两个数组时,第二个操作数被忽略。如果目的是合并两个数组,则必须使用 array_merge()
。
现在让我们来看看 PHP 8 在字符串处理中与优先级顺序有关的一个潜在的重大变化。
检查优先顺序的变化
优先顺序,又称运算顺序或运算符优先,是 18 世纪末 19 世纪初确立的数学概念。PHP 也采用了数学运算符优先规则,但增加了一个独特的运算符:连接运算符。PHP 语言的创始人假设连接运算符的优先级等同于算术运算符。直到 PHP 8 的出现,这一假设才受到质疑。
在 PHP 8 中,算术运算优先于连接运算。连接运算符的降级现在将其置于位移运算符(<<
和 >>
)之下。如果不使用括号明确定义混合算术和连接操作,就有可能出现向后兼容中断。
这一更改本身不会引发错误或产生警告或通知,因此有可能造成隐藏的代码中断。
欲了解这一变化的更多原因,请参阅以下链接: https://wiki.php.net/rfc/concatenation_precedence |
下面的示例最清楚地显示了这一变化的效果:
echo 'The sum of 2 + 2 is: ' . 2 + 2;
下面是这个简单语句在 PHP 7 中的输出结果:
root@php8_tips_php7 [ /repo/ch06 ]#
php -r "echo 'The sum of 2 + 2 is: ' . 2 + 2;"
PHP Warning: A non-numeric value encountered in Command line code on line 1
2
在 PHP 7 中,由于连接运算符的优先级等同于加法运算符,字符串 The sum of 2 + 2 is:
首先与整数值 2 连接。然后将新字符串类型杂凑为整数,生成 Warning。新字符串的值求值为 0,然后加到整数 2 中,输出结果为 2。
而在 PHP 8 中,加法首先进行,然后将结果与初始字符串连接。下面是在 PHP 8 中运行的结果:
root@php8_tips_php8 [ /repo/ch06 ]#
php -r "echo 'The sum of 2 + 2 is: ' . 2 + 2;"
The sum of 2 + 2 is: 4
从输出结果可以看出,结果更接近人类的预期!
再看一个例子,就会明白降级连接运算符所带来的不同。请看这行代码:
echo '1' . '11' + 222;
下面是在 PHP 7 中运行的结果:
root@php8_tips_php7 [ /repo/ch06 ]#
php -r "echo '1' . '11' + 222;"
333
PHP 7 首先执行连接,产生一个字符串 111。然后进行类型杂凑,将其与整数 222 相加,得到最终值整数 333。下面是在 PHP 8 中运行的结果:
root@php8_tips_php8 [ /repo/ch06 ]#
php -r "echo '1' . '11' + 222;"
1233
在 PHP 8 中,第二个字符串 11 经过类型杂凑后与整数 222 相加,产生一个临时值 233。然后将其类型杂凑为字符串,并以 1 作为前缀,最后得到字符串值 1233。
在了解了 PHP 8 中算术运算、位运算和连接运算的变化之后,让我们来看看 PHP 8 中引入的新趋势:locale 独立性。