为什么PHP属性不允许function?

我对PHP很新,但是我已经用类似的语言编写了多年。 我被以下的东西弄糊涂了:

class Foo { public $path = array( realpath(".") ); } 

它产生了一个语法错误: Parse error: syntax error, unexpected '(', expecting ')' in test.php on line 5这是realpath调用。

但是这工作正常:

 $path = array( realpath(".") ); 

在对我的头撞了一阵之后,我被告知不能在属性默认的情况下调用函数。 你必须在__construct 。 我的问题是:为什么? 这是一个“function”或马虎实施? 基本原理是什么?

编译器代码表明这是通过devise,但我不知道背后的官方推理是什么。 我也不确定需要多less努力才能可靠地实现这个function,但是目前的工作方式肯定有一些限制。

虽然我对PHP编译器的知识并不广泛,但我会试着说明我相信的事情,以便您可以看到问题出在哪里。 你的代码示例是这个过程的一个很好的候选者,所以我们将使用它:

 class Foo { public $path = array( realpath(".") ); } 

如你所知,这会导致语法错误。 这是PHP语法的结果,它使得以下相关定义成为可能:

 class_variable_declaration: //... | T_VARIABLE '=' static_scalar //... ; 

所以,当定义诸如$path之类的variables的值时,期望值必须与静态标量的定义匹配。 不出所料,由于静态标量的定义还包括其值也是静态标量的数组types,所以这有点误称。

 static_scalar: /* compile-time evaluated scalars */ //... | T_ARRAY '(' static_array_pair_list ')' // ... //... ; 

让我们暂时假设语法是不同的,类variablesparsing规则中的注释行看上去更像下面的代码样本(尽pipe破坏了其他有效的赋值):

 class_variable_declaration: //... | T_VARIABLE '=' T_ARRAY '(' array_pair_list ')' // ... ; 

重新编译PHP之后,示例脚本将不会再因语法错误而失败。 相反,它会失败,编译时错误“无效的绑定types” 。 由于代码现在基于语法是有效的,这表明实际上在编译器的devise中存在一些特定的问题。 为了弄清楚是什么,让我们回到原来的语法片段,想象代码示例有一个$path = array( 2 );的有效赋值$path = array( 2 );

使用语法作为指导,在parsing此代码示例时,可以遍历编译器代码中调用的操作。 我留下了一些不太重要的部分,但是这个过程看起来像这样:

 // ... // Begins the class declaration zend_do_begin_class_declaration(znode, "Foo", znode); // Set some modifiers on the current znode... // ... // Create the array array_init(znode); // Add the value we specified zend_do_add_static_array_element(znode, NULL, 2); // Declare the property as a member of the class zend_do_declare_property('$path', znode); // End the class declaration zend_do_end_class_declaration(znode, "Foo"); // ... zend_do_early_binding(); // ... zend_do_end_compilation(); 

虽然编译器在这些不同的方法中做了很多工作,但注意一些事情是很重要的。

  1. 调用zend_do_begin_class_declaration()导致对zend_do_begin_class_declaration()的调用。 这意味着它将新的操作码添加到当前的操作码数组中。
  2. zend_do_add_static_array_element() array_init()zend_do_add_static_array_element()不会生成新的操作码。 而是立即创build数组并将其添加到当前类的属性表中。 方法声明通过zend_do_begin_function_declaration()的特殊情况以类似的方式工作。
  3. zend_do_early_binding() 消耗当前操作码数组上的最后一个操作码,在将其设置为NOP之前检查以下types之一:
    • ZEND_DECLARE_FUNCTION
    • ZEND_DECLARE_CLASS
    • ZEND_DECLARE_INHERITED_CLASS
    • ZEND_VERIFY_ABSTRACT_CLASS
    • ZEND_ADD_INTERFACE

请注意,在最后一种情况下,如果操作码types不是预期types之一,则会抛出错误 – “无效绑定types”错误。 从这里我们可以看出,允许非静态值被分配的方式会导致最后一个操作码不是预期的。 那么,当我们使用修改过的语法的非静态数组时,会发生什么?

编译器不是调用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()会获得一个意外的input并引发exception。 由于早期绑定类和函数定义的过程似乎是PHP编译过程中相当不可或缺的部分,所以不能忽略它(尽pipeDECLARE_CLASS生产/消耗有点混乱)。 同样,尝试和内联评估这些额外的操作码也是不现实的(您不能确定给定的函数或类是否已经parsing),所以无法避免生成操作码。

一个潜在的解决scheme是构build一个新的操作码数组,其范围与类variables声明相似,类似于处理方法定义的方式。 这样做的问题是决定何时评估这样的一次运行序列。 当包含类的文件被加载时,当属性第一次被访问时,或者当这个types的对象被构​​造时,它会被完成吗?

正如你所指出的那样,其他dynamic语言已经find了处理这种情况的方法,所以做出这个决定并使之起作用并不是不可能的。 从我所知道的情况来看,在PHP的情况下这样做并不是一个单行的解决scheme,语言devise者似乎已经决定,这是不值得包括在内的东西。

我的问题是:为什么? 这是一个“function”或马虎实施?

我会说这绝对是一个function。 类定义是代码蓝图,在定义的时候不应该执行代码。 它会打破对象的抽象和封装。

不过,这只是我的看法。 我无法确定开发者在定义这个时候有什么想法。

你可能可以实现类似这样的事情:

 class Foo { public $path = __DIR__; } 

IIRC __DIR__需要php 5.3+, __FILE__已经存在了更长的时间

这是一个草率的parsing器实现。 我没有正确的术语来描述它(我认为术语“缩减beta”在某种程度上适合…),但是PHP语言parsing器比需要更复杂和更复杂,所以各种各样的不同的语言结构需要特殊的shell。

我的猜测是,如果在可执行文件行中没有发生错误,你将无法获得正确的堆栈跟踪…因为使用常量初始化值不会有任何错误,所以没有任何问题,但函数可以抛出exception/错误,需要在可执行的行内调用,而不是声明式的。