为什么PHP属性不允许使用函数? [英] Why don't PHP attributes allow functions?

查看:97
本文介绍了为什么PHP属性不允许使用函数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对PHP还是很陌生,但是多年来我一直在使用类似的语言进行编程.我被以下内容弄糊涂了:

I'm pretty new to PHP, but I've been programming in similar languages for years. I was flummoxed by the following:

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

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

It produced a syntax error: Parse error: syntax error, unexpected '(', expecting ')' in test.php on line 5 which is the realpath call.

但这很好用:

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

碰了一段时间后,我被告知您不能在默认属性中调用函数.您必须在__construct中执行此操作.我的问题是:为什么?这是功能"还是草率的实现?有什么道理?

After banging my head against this for a while, I was told you can't call functions in an attribute default; you have to do it in __construct. My question is: why?! Is this a "feature" or sloppy implementation? What's the rationale?

推荐答案

编译器代码表明这是设计使然,尽管我不知道其背后的官方原因是什么.我也不确定要可靠地实现此功能需要花费多少精力,但是目前完成工作的方式肯定存在一些限制.

The compiler code suggests that this is by design, though I don't know what the official reasoning behind that is. I'm also not sure how much effort it would take to reliably implement this functionality, but there are definitely some limitations in the way that things are currently done.

尽管我对PHP编译器的了解并不广泛,但我将尝试说明我认为的情况,以便您可以发现问题所在.您的代码示例非常适合此过程,因此我们将使用它:

Though my knowledge of the PHP compiler isn't extensive, I'm going try and illustrate what I believe goes on so that you can see where there is an issue. Your code sample makes a good candidate for this process, so we'll be using that:

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

众所周知,这会导致语法错误.这是 PHP语法的结果做出以下相关定义:

As you're well aware, this causes a syntax error. This is a result of the PHP grammar, which makes the following relevant definition:

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

因此,在定义诸如$path之类的变量的值时,期望值必须与静态标量的定义匹配.毫不奇怪,考虑到静态标量的定义还包括其值也是静态标量的数组类型,这有点用词不当:

So, when defining the values of variables such as $path, the expected value must match the definition of a static scalar. Unsurprisingly, this is somewhat of a misnomer given that the definition of a static scalar also includes array types whose values are also static scalars:

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

让我们先假设语法是不同的,并且类变量代用规则中的注释行看起来更像以下内容,它将与您的代码示例相匹配(尽管会破坏有效的赋值):

Let's assume for a second that the grammar was different, and the noted line in the class variable delcaration rule looked something more like the following which would match your code sample (despite breaking otherwise valid assignments):

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

重新编译PHP之后,示例脚本将不再因该语法错误而失败.相反,它将因编译时错误无效的绑定类型" 而失败.由于代码现在基于语法是有效的,因此这表明实际上在编译器设计中存在某些特定问题,这些问题会引起麻烦.为了弄清楚是什么,让我们暂时恢复原始语法,并假设代码示例的有效分配为$path = array( 2 );.

After recompiling PHP, the sample script would no longer fail with that syntax error. Instead, it would fail with the compile time error "Invalid binding type". Since the code is now valid based on the grammar, this indicates that there actually is something specific in the design of the compiler that's causing trouble. To figure out what that is, let's revert to the original grammar for a moment and imagine that the code sample had a valid assignment of $path = array( 2 );.

以语法为指导,可以逐步了解

Using the grammar as a guide, it's possible to walk through the actions invoked in the compiler code when parsing this code sample. I've left some less important parts out, but the process looks something like this:

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

尽管编译器在这些方法中做了很多事情,但重要的是要注意一些事情.

While the compiler does a lot in these various methods, it's important to note a few things.

  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
  1. A call to zend_do_begin_class_declaration() results in a call to get_next_op(). This means that it adds a new opcode to the current opcode array.
  2. array_init() and zend_do_add_static_array_element() do not generate new opcodes. Instead, the array is immediately created and added to the current class' properties table. Method declarations work in a similar way, via a special case in zend_do_begin_function_declaration().
  3. zend_do_early_binding() consumes the last opcode on the current opcode array, checking for one of the following types before setting it to a NOP:
    • ZEND_DECLARE_FUNCTION
    • ZEND_DECLARE_CLASS
    • ZEND_DECLARE_INHERITED_CLASS
    • ZEND_VERIFY_ABSTRACT_CLASS
    • ZEND_ADD_INTERFACE

请注意,在最后一种情况下,如果操作码类型不是预期的类型之一,则会引发错误. 无效的绑定类型" 错误.由此可见,允许以某种方式分配非静态值会导致最后的操作码不是预期的.那么,当我们使用带有修改后的语法的非静态数组时会发生什么呢?

Note that in the last case, if the opcode type is not one of the expected types, an error is thrown – The "Invalid binding type" error. From this, we can tell that allowing the non-static values to be assigned somehow causes the last opcode to be something other than expected. So, what happens when we use a non-static array with the modified grammar?

编译器代替调用array_init(),而准备参数并调用zend_do_init_array().依次调用get_next_op()并添加一个新的 INIT_ARRAY操作码,从而产生一些结果类似于以下内容:

Instead of calling array_init(), the compiler prepares the arguments and calls zend_do_init_array(). This in turn calls get_next_op() and adds a new INIT_ARRAY opcode, producing something like the following:

DECLARE_CLASS   'Foo'
SEND_VAL        '.'
DO_FCALL        'realpath'
INIT_ARRAY

这就是问题的根源.通过添加这些操作码,zend_do_early_binding()获得了意外的输入并引发了异常.由于早期绑定类和函数定义的过程似乎是PHP编译过程不可或缺的一部分,因此不能仅仅忽略它(尽管DECLARE_CLASS的生产/使用有点混乱).同样,尝试内联评估这些附加的操作码也是不切实际的(您无法确定给定的函数或类是否已解决),因此无法避免生成操作码.

Herein lies the root of the problem. By adding these opcodes, zend_do_early_binding() gets an unexpected input and throws an exception. As the process of early binding class and function definitions seems fairly integral to the PHP compilation process, it can't just be ignored (though the DECLARE_CLASS production/consumption is kind of messy). Likewise, it's not practical to try and evaluate these additional opcodes inline (you can't be sure that a given function or class has been resolved yet), so there's no way to avoid generating the opcodes.

一个潜在的解决方案是建立一个新的操作码数组,该数组的作用域为类变量声明,类似于方法定义的处理方式.这样做的问题是决定何时评估这样的一次运行序列.在加载包含类的文件时,第一次访问该属性时或在构造该类型的对象时会完成此操作吗?

A potential solution would be to build a new opcode array that was scoped to the class variable declaration, similar to how method definitions are handled. The problem with doing that is deciding when to evaluate such a run-once sequence. Would it be done when the file containing the class is loaded, when the property is first accessed, or when an object of that type is constructed?

正如您所指出的那样,其他动态语言也找到了一种处理这种情况的方法,因此做出决定并使之生效并非并非不可能.据我所知,在PHP的情况下这样做不是一站式解决方案,而且语言设计人员似乎已决定在这一点上不值得这样做.

As you've pointed out, other dynamic languages have found a way to handle this scenario, so it's not impossible to make that decision and get it to work. From what I can tell though, doing so in the case of PHP wouldn't be a one-line fix, and the language designers seem to have decided that it wasn't something worth including at this point.

这篇关于为什么PHP属性不允许使用函数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆