稍后在同一文件中定义的派生类“不存在”? [英] Derived class defined later in the same file "does not exist"?

查看:121
本文介绍了稍后在同一文件中定义的派生类“不存在”?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

让我们假设我们有两个php文件a.php和b.php
这里是文件a.php的内容:

 <?php // a.php的内容
class A {
}

这里是文件b.php的内容

 <?php // b .php 
包括dirname(__ FILE__)。 /a.php;
echoA:,class_exists(A)? exists:不存在,\\\
;
echoB:,class_exists(B)? exists:不存在,\\\
;
echoBA(before):,class_exists(BA)? exists:不存在,\\\
;
echoBB:,class_exists(BB)? exists:不存在,\\\
;
class B {
}
class BA extend A {
}
class BB extend B {
}
echoBA :,class_exists(BA)? exists:不存在,\\\
;

如果你启动b.php脚本,你有这样的输出:

  A:存在
B:存在
BA(之前):不存在
BB:存在
为什么BA类仅在类定义之后存在?为什么BA类只存在于类定义之后?为什么其他类甚至在它们的定义之前就存在?这是什么区别?我想在两种情况下都有一个共同的行为...
是否有一种方法,我可以使用BA类甚至在它的定义之前?



谢谢您



Michele

解决方案

免责声明:了解Zend的内部工作原理。以下是我对PHP源代码的解释,其中很大一部分来源于受过教育的猜测。即使我完全相信结论,术语或细节可能已经关闭。我很乐意听到任何有Zend内部经验的人就这件事。



调查



PHP解析器我们可以看到当类声明遇到 zend_do_early_binding 函数。 这里是处理声明的代码派生类:

  case ZEND_DECLARE_INHERITED_CLASS:
{
zend_op * fetch_class_opline = opline-1;
zval * parent_name;
zend_class_entry ** pce;

parent_name =& CONSTANT(fetch_class_opline-> op2.constant);
if((zend_lookup_class(Z_STRVAL_P(parent_name),Z_STRLEN_P(parent_name),& pce TSRMLS_CC)== FAILURE)||
((CG(compiler_options)& ZEND_COMPILE_IGNORE_INTERNAL_CLASSES)&&
((* pce) - > type == ZEND_INTERNAL_CLASS))){
if(CG(compiler_options)& ZEND_COMPILE_DELAYED_BINDING){
zend_uint * opline_num =& CG(active_op_array) - > early_binding;

while(* opline_num!= -1){
opline_num =& CG(active_op_array) - > opcodes [* opline_num] .result.opline_num;
}
* opline_num = opline - CG(active_op_array) - > opcodes;
opline-> opcode = ZEND_DECLARE_INHERITED_CLASS_DELAYED;
opline-> result_type = IS_UNUSED;
opline-> result.opline_num = -1;
}
return;
}
if(do_bind_inherited_class(CG(active_op_array),opline,CG(class_table),* pce,1 TSRMLS_CC)== NULL){
return;
}
/ *清除不必要的ZEND_FETCH_CLASS操作码* /
zend_del_literal(CG(active_op_array),fetch_class_opline-> op2.constant);
MAKE_NOP(fetch_class_opline);

table = CG(class_table);
break;
}

此代码会立即调用 zend_lookup_class 查看父类是否存在



让我们首先看看如果父类

,它是做什么的。 / em> found:

  if(do_bind_inherited_class(CG(active_op_array),opline,CG(class_table),* pce,1 TSRMLS_CC )== NULL){
return;
}

转到 do_bind_inherited_class ,我们看到最后一个参数在这个调用 1 )被调用 compile_time 。这听起来很有趣。

  if(compile_time){
op1 =& CONSTANT_EX(op_array,opline - > op1.constant);
op2 =& CONSTANT_EX(op_array,opline-> op2.constant);
} else {
op1 = opline-> op1.zv;
op2 = opline-> op2.zv;
}

found_ce = zend_hash_quick_find(class_table,Z_STRVAL_P(op1),Z_STRLEN_P(op1),Z_HASH_P(op1),(void **)& pce);

if(found_ce == FAILURE){
if(!compile_time){
/ *如果我们在编译时,实际上很可能
*我们永远不会在运行时遇到这个类声明,
*所以我们关闭它。这允许if(!defined('FOO')){return; }
*方法工作。
* /
zend_error(E_COMPILE_ERROR,无法重新命名类%s,Z_STRVAL_P(op2));
}
return NULL;
} else {
ce = * pce;
}

好吧,所以它读取父类和派生类名称静态(从PHP用户的角度)或动态上下文,取决于 compile_time 状态。然后它试图在类表中找到类条目(ce),如果是不是,那么...在编译时它不返回任何东西,运行时出现致命错误



这听起来非常重要。让我们回到 zend_do_early_binding

  if(CG(compiler_options)& ZEND_COMPILE_DELAYED_BINDING){
zend_uint * opline_num =& CG(active_op_array) - > early_binding;

while(* opline_num!= -1){
opline_num =& CG(active_op_array) - > opcodes [* opline_num] .result.opline_num;
}
* opline_num = opline - CG(active_op_array) - > opcodes;
opline-> opcode = ZEND_DECLARE_INHERITED_CLASS_DELAYED;
opline-> result_type = IS_UNUSED;
opline-> result.opline_num = -1;
}
return;

似乎它正在生成操作码将触发一个调用再次 do_bind_inherited_class 但这次, compile_time 的值将为 0 (false)。



最后,如何实现 class_exists PHP函数?查看源代码显示以下片段:

  found = zend_hash_find(EG(class_table),name,len + 1, *)& ce); 

太棒了! class_table 变量与 do_bind_inherited_class class_table c>我们前面看到的!因此 class_exists 的返回值取决于该类的一个条目是否已经被<$ c>插入 class_table $ c> do_bind_inherited_class



结论



在编译时不会对 include 指令起作用(即使文件名是硬编码的)。



如果是,那么没有理由基于 compile_time 标志未设置发出类重定义致命错误;



当编译器遇到一个派生类声明,其中的基类没有在同一个脚本文件中声明,它会推送



从上面的最后一个代码片段可以看出,它设置了一个 ZEND_DECLARE_INHERITED_CLASS_DELAYED 在脚本执行时注册类的操作码。此时, compile_time 标志将为 false ,行为将略有不同。



class_exists 的返回值取决于类是否已经注册。



因为这在编译时和运行时以不同的方式发生,所以 class_exists 的行为也是不同的:




  • 其祖先都包含在同一源文件中的类在编译时注册;它们存在并且可以在该脚本中的任何位置实例化

  • 在运行时注册在另一个源文件中定义的祖先的类;在VM执行与源定义相对应的操作码之前,这些类不存在用于所有实际目的( class_exists 返回 false ,实例化给出了致命错误)


Let’s suppose we’ve got two php files, a.php and b.php Here’s content of file a.php:

<?php // content of a.php
class A {
}

And here’s the content of file b.php

<?php  // content of b.php
include dirname(__FILE__) . "/a.php";
echo "A: ", class_exists("A") ? "exists" : "doesn’t exist", "\n";
echo "B: ", class_exists("B") ? "exists" : "doesn’t exist", "\n";
echo "BA (before): ", class_exists("BA") ? "exists" : "doesn’t exist", "\n";
echo "BB: ", class_exists("BB") ? "exists" : "doesn’t exist", "\n";
class B {
}
class BA extends A {
}
class BB extends B {
}
echo "BA (after): ", class_exists("BA") ? "exists" : "doesn’t exist", "\n";

If you launch the b.php script you have this output:

A: exists
B: exists
BA (before): doesn’t exist
BB: exists
BA (after): exists

Why does the BA class exist only after the class definition? And why does the other classes exist even before their definition? Which is the difference? I’d expect to have a common behavior in both cases... Is there a way I could use the BA class even before its definition?

Thank you

Michele

解决方案

Disclaimer: I don't claim to understand the inner workings of Zend. The following is my interpretation of the PHP source, fueled in great part by educated guesses. Even though I am fully confident in the conclusion, the terminology or details might be off. I 'd love to hear from anyone with experience in Zend internals on the matter.

The investigation

From the PHP parser we can see that when a class declaration is encountered the zend_do_early_binding function is called. Here is the code that handles the declaration of derived classes:

case ZEND_DECLARE_INHERITED_CLASS:
{
    zend_op *fetch_class_opline = opline-1;
    zval *parent_name;
    zend_class_entry **pce;

    parent_name = &CONSTANT(fetch_class_opline->op2.constant);
    if ((zend_lookup_class(Z_STRVAL_P(parent_name), Z_STRLEN_P(parent_name), &pce TSRMLS_CC) == FAILURE) ||
        ((CG(compiler_options) & ZEND_COMPILE_IGNORE_INTERNAL_CLASSES) &&
         ((*pce)->type == ZEND_INTERNAL_CLASS))) {
        if (CG(compiler_options) & ZEND_COMPILE_DELAYED_BINDING) {
            zend_uint *opline_num = &CG(active_op_array)->early_binding;

            while (*opline_num != -1) {
                opline_num = &CG(active_op_array)->opcodes[*opline_num].result.opline_num;
            }
            *opline_num = opline - CG(active_op_array)->opcodes;
            opline->opcode = ZEND_DECLARE_INHERITED_CLASS_DELAYED;
            opline->result_type = IS_UNUSED;
            opline->result.opline_num = -1;
        }
        return;
    }
    if (do_bind_inherited_class(CG(active_op_array), opline, CG(class_table), *pce, 1 TSRMLS_CC) == NULL) {
        return;
    }
    /* clear unnecessary ZEND_FETCH_CLASS opcode */
    zend_del_literal(CG(active_op_array), fetch_class_opline->op2.constant);
    MAKE_NOP(fetch_class_opline);

    table = CG(class_table);
    break;
}

This code immediately calls zend_lookup_class to see if the parent class exists in the symbol table... and then diverges depending on whether the parent is found or not.

Let's first see what it does if the parent class is found:

if (do_bind_inherited_class(CG(active_op_array), opline, CG(class_table), *pce, 1 TSRMLS_CC) == NULL) {
    return;
}

Going over to do_bind_inherited_class, we see that the last argument (which in this call is 1) is called compile_time. This sounds interesting. What does it do with this argument?

if (compile_time) {
    op1 = &CONSTANT_EX(op_array, opline->op1.constant);
    op2 = &CONSTANT_EX(op_array, opline->op2.constant);
} else {
    op1 = opline->op1.zv;
    op2 = opline->op2.zv;
}

found_ce = zend_hash_quick_find(class_table, Z_STRVAL_P(op1), Z_STRLEN_P(op1), Z_HASH_P(op1), (void **) &pce);

if (found_ce == FAILURE) {
    if (!compile_time) {
        /* If we're in compile time, in practice, it's quite possible
         * that we'll never reach this class declaration at runtime,
         * so we shut up about it.  This allows the if (!defined('FOO')) { return; }
         * approach to work.
         */
        zend_error(E_COMPILE_ERROR, "Cannot redeclare class %s", Z_STRVAL_P(op2));
    }
    return NULL;
} else {
    ce = *pce;
}

Okay... so it reads the parent and derived class names either from a static (from the PHP user's perspective) or dynamic context, depending on the compile_time status. It then tries to find the class entry ("ce") in the class table, and if it is not found then... it returns without doing anything in compile time, but emits a fatal error at runtime.

This sounds enormously important. Let's go back to zend_do_early_binding. What does it do if the parent class is not found?

if (CG(compiler_options) & ZEND_COMPILE_DELAYED_BINDING) {
    zend_uint *opline_num = &CG(active_op_array)->early_binding;

    while (*opline_num != -1) {
        opline_num = &CG(active_op_array)->opcodes[*opline_num].result.opline_num;
    }
    *opline_num = opline - CG(active_op_array)->opcodes;
    opline->opcode = ZEND_DECLARE_INHERITED_CLASS_DELAYED;
    opline->result_type = IS_UNUSED;
    opline->result.opline_num = -1;
}
return;

It seems that it is generating opcodes that will trigger a call to do_bind_inherited_class again -- but this time, the value of compile_time will be 0 (false).

Finally, what about the implementation of the class_exists PHP function? Looking at the source shows this snippet:

found = zend_hash_find(EG(class_table), name, len+1, (void **) &ce);

Great! This class_table variable is the same class_table that gets involved in the do_bind_inherited_class call we saw earlier! So the return value of class_exists depends on whether an entry for the class has already been inserted into class_table by do_bind_inherited_class.

The conclusions

The Zend compiler does not act on include directives at compile time (even if the filename is hardcoded).

If it did, then there would be no reason to emit a class redeclaration fatal error based on the compile_time flag not being set; the error could be emitted unconditionally.

When the compiler encounters a derived class declaration where the base class has not been declared in the same script file, it pushes the act of registering the class in its internal data structures to runtime.

This is evident from the last code snippet above, which sets up a ZEND_DECLARE_INHERITED_CLASS_DELAYED opcode to register the class when the script is executed. At that point the compile_time flag will be false and behavior will be subtly different.

The return value of class_exists depends on whether the class has already been registered.

Since this happens in different ways at compile time and at run time, the behavior of class_exists is also different:

  • classes whose ancestors are all included in the same source file are registered at compile time; they exist and can be instantiated at any point in that script
  • classes which have an ancestor defined in another source file are registered at runtime; before the VM executes the opcodes that correspond to the class definition in the source these classes do not exist for all practical purposes (class_exists returns false, instantiating gives a fatal error)

这篇关于稍后在同一文件中定义的派生类“不存在”?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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