问题描述
让我们假设我们有两个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解析器当类声明遇到函数。 是处理声明的代码派生类:
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;
}
此代码会立即调用查看父类是否存在
让我们首先看看如果父类是
,它是做什么的。 / em> found:if(do_bind_inherited_class(CG(active_op_array),opline,CG(class_table),* pce,1 TSRMLS_CC )== NULL){
return;
}
转到,我们看到最后一个参数在这个调用
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)。
最后,如何实现 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.phpHere’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 is1
) is calledcompile_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 ofcompile_time
will be0
(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 sameclass_table
that gets involved in thedo_bind_inherited_class
call we saw earlier! So the return value ofclass_exists
depends on whether an entry for the class has already been inserted intoclass_table
bydo_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 thecompile_time
flag will befalse
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
returnsfalse
, instantiating gives a fatal error)这篇关于稍后在同一文件中定义的派生类“不存在”?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!