编译器的代码表明这是有意为之的,虽然我不知道官方背后的原因。我也不确定要可靠地实现这个功能需要多少努力,但目前做事情的方式肯定存在一些限制。
尽管我对PHP编译器的了解不太广泛,但我会尝试阐述我认为发生了什么,以便您可以看到存在的问题。您的代码示例很适合这个过程,所以我们将使用它:
class Foo {
public $path = array(
realpath(".")
);
}
你很清楚,这会导致语法错误。这是由PHP语法造成的,因为它包含以下相关定义:
class_variable_declaration:
//...
| T_VARIABLE '=' static_scalar //...
所以,在定义变量的值时,如$path
,期望的值必须与静态标量的定义相匹配。毫不奇怪,这有点不准确,因为静态标量的定义还包括其值也是静态标量的数组类型:
static_scalar: /* compile-time evaluated scalars */
//...
| T_ARRAY '(' static_array_pair_list ')' // ...
//...
;
假设语法不同,类变量声明规则中的注释行看起来更像以下内容,这将匹配你的代码示例(尽管会破坏其他有效的赋值):
class_variable_declaration:
//...
| T_VARIABLE '=' T_ARRAY '(' array_pair_list ')' // ...
;
重新编译PHP后,示例脚本不再出现语法错误,而是会出现编译时错误"Invalid binding type"。由于代码基于语法现在是有效的,这表明编译器设计中确实存在某些特定问题。为了找出问题所在,让我们暂时回到原始语法,并想象一下代码示例具有有效的赋值$path = array(2);
。
使用语法作为指南,可以遍历解析此代码示例时调用的操作。编译器代码。我省略了一些不那么重要的部分,但过程大致如下:
zend_do_begin_class_declaration(znode, "Foo", znode);
array_init(znode);
zend_do_add_static_array_element(znode, NULL, 2);
zend_do_declare_property('$path', znode);
zend_do_end_class_declaration(znode, "Foo");
zend_do_early_binding();
zend_do_end_compilation();
虽然编译器在这些方法中做了很多工作,但需要注意以下几点:
1. 调用
zend_do_begin_class_declaration()
会导致调用
get_next_op()
。这意味着它会向当前操作码数组中添加一个新的操作码。
2.
array_init()
和
zend_do_add_static_array_element()
不会生成新的操作码。相反,数组会立即被创建并添加到当前类的属性表中。方法声明以类似的方式工作,通过
zend_do_begin_function_declaration()
中的特殊情况实现。
3.
zend_do_early_binding()
会消耗当前操作码数组上的最后一个操作码,并在将其设置为NOP之前检查以下某个类型:
ZEND_DECLARE_FUNCTION
ZEND_DECLARE_CLASS
ZEND_DECLARE_INHERITED_CLASS
ZEND_VERIFY_ABSTRACT_CLASS
ZEND_ADD_INTERFACE
请注意,在最后一种情况下,如果操作码类型不是预期类型之一,则会抛出错误-"无效绑定类型"。从这里我们可以看出,允许将非静态值分配给某些东西会导致最后一个操作码变成意料之外的东西。那么,当我们使用非静态数组和修改过的语法时会发生什么?
编译器不会调用
array_init()
,而是准备参数并调用
zend_do_init_array()
。这又调用了
get_next_op()
并添加了一个新的
INIT_ARRAY操作码,生成类似于以下内容:
DECLARE_CLASS 'Foo'
SEND_VAL '.'
DO_FCALL 'realpath'
INIT_ARRAY
问题的根源就在这里。通过添加这些操作码,zend_do_early_binding()
得到了意外的输入,从而抛出了异常。
早期绑定类和函数定义的过程似乎非常重要,因此不能忽略它(尽管DECLARE_CLASS的生产/消费有点混乱)。同样,尝试在内联中评估这些附加操作码也不切实际(无法确定给定的函数或类是否已解析),因此无法避免生成操作码。
一个潜在的解决方案是构建一个作用于类变量声明的新操作码数组,类似于方法定义的处理方式。但这样做的问题是决定何时评估运行一次的序列。是在加载包含类的文件时进行,还是在第一次访问属性时进行,或者是在构造该类型的对象时进行?
正如您指出的,其他动态语言已经找到了处理这种情况的方法,因此决定并让其工作并不是不可能的。但据我所知,在PHP的情况下这样做不是一个一行代码的修复,而且语言设计者似乎已经决定不值得在这个点上包括它。
zend_object.c
中,但我对Zend引擎不是很熟悉,所以你可能需要挖掘一下。我已经将Zend引擎添加到标签列表中。也许会吸引更多了解的人。 - Gordon