为什么不';PHP属性是否允许函数?
我对PHP非常陌生,但多年来我一直在用类似的语言编程。我被以下事情弄糊涂了:为什么不';PHP属性是否允许函数?,php,language-design,php-internals,Php,Language Design,Php Internals,我对PHP非常陌生,但多年来我一直在用类似的语言编程。我被以下事情弄糊涂了: class Foo { public $path = array( realpath(".") ); } DECLARE_CLASS 'Foo' SEND_VAL '.' DO_FCALL 'realpath' INIT_ARRAY 它产生了一个语法错误:Parse error:syntax error,test.php中第5行的“(”,预期“)”,这
class Foo {
public $path = array(
realpath(".")
);
}
DECLARE_CLASS 'Foo'
SEND_VAL '.'
DO_FCALL 'realpath'
INIT_ARRAY
它产生了一个语法错误:Parse error:syntax error,test.php中第5行的“(”,预期“)”,这是realpath
调用
但这很好:
$path = array(
realpath(".")
);
在我的头撞了一会儿之后,有人告诉我不能在属性默认值中调用函数;您必须在\u构造中执行此操作。我的问题是:为什么?!这是一个“特性”还是草率的实现?理由是什么
我的问题是:为什么?!这是一个“特性”还是草率的实现
我认为这绝对是一个特色。类定义是一个代码蓝图,不应该在定义时执行代码。它将破坏对象的抽象和封装
不过,这只是我的看法。我不能确定开发者在定义这个时有什么想法 这是一个草率的解析器实现。我没有正确的术语来描述它(我认为术语“beta reduce”在某种程度上是合适的…),但是PHP语言解析器比它需要的更复杂,因此,不同的语言结构需要各种特殊的大小写。我的猜测是,如果错误不发生在可执行行上,您将无法获得正确的堆栈跟踪。。。由于使用常量初始化值不会出现任何错误,所以这没有问题,但函数可能会引发异常/错误,需要在可执行行中调用,而不是在声明行中调用。您可能会实现类似的效果:
class Foo
{
public $path = __DIR__;
}
// ...
// 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();
IIRC\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。我也不确定可靠地实现这一功能需要多少努力,但目前的工作方式肯定存在一些限制
虽然我对PHP编译器的知识并不丰富,但我将尝试说明我所相信的情况,以便您可以看到哪里存在问题。您的代码示例非常适合此过程,因此我们将使用:
class Foo {
public $path = array(
realpath(".")
);
}
您很清楚,这会导致语法错误。这是一个结果,这使得以下相关定义:
class_variable_declaration:
//...
| T_VARIABLE '=' static_scalar //...
;
因此,在定义变量(如$path
)的值时,预期值必须与静态标量的定义相匹配。毫不奇怪,这有点用词不当,因为静态标量的定义还包括值也是静态标量的数组类型:
static_scalar: /* compile-time evaluated scalars */
//...
| T_ARRAY '(' static_array_pair_list ')' // ...
//...
;
让我们先假设语法是不同的,类变量delcaration规则中的注释行看起来更像以下内容,这将与您的代码示例相匹配(尽管破坏了其他有效的赋值):
在重新编译PHP之后,示例脚本将不再因该语法错误而失败。相反,它将因编译时错误“无效绑定类型”而失败。由于代码现在基于语法是有效的,这表明编译器的设计中确实存在一些特定的问题。为了弄清楚这是什么,让我们暂时回到原始语法,想象一下代码示例的有效赋值是$path=array(2)代码>
使用语法作为指导,可以在解析此代码示例时浏览在中调用的操作。我遗漏了一些不太重要的部分,但过程如下所示:
class Foo
{
public $path = __DIR__;
}
// ...
// 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\u do\u begin\u class\u declaration()
会导致调用get\u next\u op()
。这意味着它将向当前操作码数组添加一个新的操作码
array\u init()
和zend\u do\u add\u static\u array\u element()
不生成新的操作码。相反,数组会立即创建并添加到当前类的属性表中。方法声明以类似的方式工作,通过zend\u do\u begin\u function\u declaration()
中的一个特例
zend\u do\u early\u binding()
使用当前操作码数组上的最后一个操作码,在将其设置为NOP之前检查以下类型之一:
- ZEND_声明_函数
- ZEND_声明_类
- ZEND_声明_继承的_类
- ZEND_验证_抽象_类
- ZEND_添加_接口
请注意,在最后一种情况下,如果操作码类型不是预期的类型之一,则会引发一个错误——“无效绑定类型”错误。由此,我们可以看出,允许非静态值以某种方式分配会导致最后一个操作码不是预期的。那个么,当我们使用修改语法的非静态数组时会发生什么呢
编译器不调用array\u init()
,而是准备参数并调用zend\u do\u init\u array()
。这将依次调用get\u next\u op()
,并添加一个新的,生成如下内容:
class Foo {
public $path = array(
realpath(".")
);
}
DECLARE_CLASS 'Foo'
SEND_VAL '.'
DO_FCALL 'realpath'
INIT_ARRAY
这就是问题的根源所在。通过添加这些操作码,zend\u do\u early\u binding()
获得意外输入并引发异常。由于早期绑定类和函数定义的过程似乎是PHP编译过程中不可或缺的一部分,因此不能忽略它(尽管DECLARE_类的生成/使用有点混乱)。同样,尝试内联计算这些额外的操作码也不切实际(您无法确定给定的函数或类是否已解析),因此无法避免生成操作码
一个潜在的解决方案是构建一个新的操作码数组,其作用域为类变量声明,类似于ho