代码之家  ›  专栏  ›  技术社区  ›  Schwern

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

  •  41
  • Schwern  · 技术社区  · 14 年前

    我对PHP还不太熟悉,但多年来我一直在用类似的语言编程。我被以下事情搞糊涂了:

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

    Parse error: syntax error, unexpected '(', expecting ')' in test.php on line 5 哪一个是 realpath 打电话来。

    但这很管用:

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

    在我的头撞了一会儿之后,我被告知不能在属性默认值中调用函数;必须在 __construct . 我的问题是:为什么?!这是“特性”还是草率的实现?理由是什么?

    5 回复  |  直到 11 年前
        1
  •  51
  •   Tim Stone    14 年前

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

    虽然我对PHP编译器的了解并不广泛,但我将尝试说明我所相信的情况,以便您可以看到哪里存在问题。您的代码示例很适合此过程,因此我们将使用:

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

    PHP grammar ,从而做出以下相关定义:

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

    所以,当定义变量的值时,比如 $path

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

    让我们假设语法不同,类变量delcaration规则中的注释行看起来更像下面的代码示例(尽管破坏了其他有效的赋值):

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

    在重新编译PHP之后,示例脚本将不再因语法错误而失败。相反,它会因编译时错误而失败 “无效的绑定类型” . 由于代码现在基于语法是有效的,这表明编译器的设计中确实有一些特定的东西会引起问题。为了弄清楚这是什么,让我们暂时回到原始语法,并假设代码示例有一个有效的 $path = array( 2 ); .

    使用语法作为指导,可以遍历 compiler code 分析此代码示例时。我遗漏了一些不太重要的部分,但过程如下:

    // ...
    // 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() 导致调用 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_类
      • ZEND_DECLARE_INHERITED_类
      • ZEND_ADD_接口

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

    而不是打电话 ,编译器准备参数和调用 zend_do_init_array() 获取下一个操作() INIT_ARRAY opcode ,产生如下内容:

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

    zend_do_early_binding()

    一个可能的解决方案是构建一个新的操作码数组,该数组的作用域是类变量声明,类似于如何处理方法定义。这样做的问题是决定何时评估这种一次性运行序列。在加载包含类的文件时、首次访问属性时还是在构造该类型的对象时执行此操作?

    正如您所指出的,其他动态语言已经找到了处理这个场景的方法,所以做出这个决定并使其生效并非不可能。不过,据我所知,在PHP的情况下这样做并不是一个单行的解决方案,而且语言设计者似乎已经决定在这一点上不值得包括它。

        2
  •  22
  •   Pekka    14 年前

    我的问题是:为什么?!这是“特性”还是草率的实现?

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

        3
  •  6
  •   Robin    14 年前

    你可能会实现类似的目标:

    class Foo
    {
        public $path = __DIR__;
    }
    

    IIRC公司 __DIR__ 需要php 5.3+, __FILE__ 已经在附近呆了很久了

        4
  •  5
  •   Ignacio Vazquez-Abrams    14 年前

        5
  •  2
  •   Yanick Rochon    14 年前

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

    推荐文章