为什么PHP属性不允许使用函数?

我对PHP相当陌生,但我多年来一直使用类似的语言进行编程。 我被以下内容淹没了:

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

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

但是,这工作正常:

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

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


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

尽管我对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之后,示例脚本不会再因语法错误而失败。 相反,它会因编译时错误“无效绑定类型”而失败。 由于代码现在基于语法是有效的,这表明在编译器的设计中确实存在特定的问题,这是造成麻烦的原因。 为了弄清楚是什么,让我们回到原始语法一段时间,并设想代码示例具有$path = array( 2 );的有效赋值$path = array( 2 );

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

// ...
// 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();

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

  • 要在通话zend_do_begin_class_declaration()导致调用get_next_op() 这意味着它会向当前操作码数组添加一个新的操作码。
  • zend_do_add_static_array_element() array_init()zend_do_add_static_array_element()不会生成新的操作码。 相反,数组会立即创建并添加到当前类的属性表中。 方法声明通过zend_do_begin_function_declaration()的特殊情况以类似的方式工作。
  • 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()会得到意外的输入并引发异常。 由于早期绑定类和函数定义的过程对于PHP编译过程来说似乎相当不可或缺,所以不能忽略它(尽管DECLARE_CLASS生产/消费有点混乱)。 同样,尝试和内嵌评估这些附加操作码也是不现实的(您不能确定给定的函数或类是否已经解析),因此无法避免生成操作码。

    一个潜在的解决方案是构建一个新的操作码数组,该操作码数组的范围与类变量声明相同,类似于处理方法定义的方式。 这样做的问题在于决定何时评估这样的运行一次序列。 当包含该类的文件被加载,何时首次访问该属性,或者何时构建了该类型的对象时,会执行该操作吗?

    正如你所指出的那样,其他动态语言已经找到了处理这种情况的方法,所以做出这个决定并使其工作并不是不可能的。 从我所知道的情况来看,在PHP的情况下这样做并不是一条线,而语言设计者似乎已经决定在这一点上它不值得包括。


    我的问题是:为什么? 这是一个“功能”还是马虎实施?

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

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


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

    class Foo
    {
        public $path = __DIR__;
    }
    

    IIRC __DIR__需要php 5.3+, __FILE__ __DIR__需要更长的时间

    链接地址: http://www.djcxy.com/p/11987.html

    上一篇: Why don't PHP attributes allow functions?

    下一篇: Parse error: syntax error, unexpected '(', expecting ',' or ';' in