改变函数调用结果语义的括号

2022-08-30 10:24:52

在另一个问题中指出,将PHP函数调用的结果括在括号中可以以某种方式将结果转换为完全成熟的表达式,这样以下方法就可以了:

<?php
error_reporting(E_ALL | E_STRICT);

function get_array() {
   return array();
}

function foo() {
   // return reset(get_array());
   //              ^ error: "Only variables should be passed by reference"

   return reset((get_array()));
   //           ^ OK
}

foo();

我试图在文档中找到任何内容,以明确无误地解释这里发生的事情。与C++不同,我对PHP语法及其对语句/表达式的处理不够了解,无法自己派生它。

文档中是否隐藏了有关此行为的内容?如果不是,其他人可以在不诉诸假设的情况下解释它吗?


更新

我首先发现这个EBNF旨在代表PHP语法,并试图自己解码我的脚本,但最终放弃了。

然后,使用 phc 生成两个变体的文件,我使用以下命令为两个脚本生成 AST 图像:.dotfoo()

$ yum install phc graphviz
$ phc --dump-ast-dot test1.php > test1.dot
$ dot -Tpng test1.dot > test1.png
$ phc --dump-ast-dot test2.php > test2.dot
$ dot -Tpng test2.dot > test2.png

在这两种情况下,结果完全相同:

Parse tree of snippets 1 and 2


答案 1

此行为可以归类为错误,因此您绝对不应该依赖它。

在函数调用中引发消息的(简化)条件如下(请参阅操作码ZEND_SEND_VAR_NO_REF的定义):

  • 参数不是函数调用(或者如果是,则通过引用返回),并且
  • 参数要么是引用,要么引用计数为 1(如果引用计数为 1,则将其转换为引用)。

让我们更详细地分析这些。

第一个点为真(不是函数调用)

由于附加了括号,PHP 不再检测参数是函数调用。

解析非空函数参数列表时,PHP 有三种可能性:

  • expr_without_variable
  • 一个variable
  • (A 后跟 a ,表示已删除的调用时间传递引用功能)&variable

当编写时,PHP认为这是一个.get_array()variable

(get_array())另一方面,不符合.它是一个.variableexpr_without_variable

这最终会影响代码的编译方式,即操作码的扩展值将不再包含标志,这是在操作码实现中检测函数调用的方式。SEND_VAR_NO_REFZEND_ARG_SEND_FUNCTION

第二点为真(参考计数为 1)

在多个点上,Zend 引擎允许引用计数为 1 的非引用,其中需要引用。这些细节不应该暴露给用户,但不幸的是,它们在这里。

在您的示例中,您返回的是一个未从其他任何地方引用的数组。如果是这样,您仍然会得到消息,即第二点将不是真的。

因此,以下非常相似的示例不起作用

<?php

$a = array();
function get_array() {
   return $GLOBALS['a'];
}

return reset((get_array()));

答案 2

A)要理解这里发生的事情,需要了解PHP对值/变量和引用的处理(PDF,1.2MB)。正如整个文档所述“引用不是指针”;并且您只能通过从函数引用来返回变量 - 仅此而已。

在我看来,这意味着PHP中的任何函数都将返回引用。但是一些函数(内置于PHP中)需要值/变量作为参数。现在,如果要嵌套函数调用,则内部的函数返回一个引用,而外部的函数需要一个值。这导致了“著名”E_STRICT错误“只有变量应该通过引用传递”。

$fileName = 'example.txt';
$fileExtension = array_pop(explode('.', $fileName));
// will result in Error 2048: Only variables should be passed by reference in…

B)我在问题中链接的PHP语法描述中发现了一行。

expr_without_variable = "(" expr ")"

结合文档中的这句话:“在PHP中,你写的几乎任何东西都是一个表达式。定义表达式的最简单但最准确的方法是'任何具有值的东西'.“,这使我得出结论,即使是PHP中的表达式,其计算结果为值为5的整数。(5)

(不仅是赋值,也是表达式,可累加到 5。$a = 5

结论

如果传递对表达式的引用,则此表达式将返回一个值,然后可以将其作为参数传递给外部函数。如果(我的想法)是正确的,那么以下两行应该等效地工作:(...)

// what I've used over years: (spaces only added for readability)
$fileExtension = array_pop( ( explode('.', $fileName) ) );
// vs
$fileExtension = array_pop( $tmp = explode('.', $fileName) );

另请参阅 PHP 5.0.5:致命错误:只能通过引用传递变量;2005 年 9 月 13 日


推荐