稍后在同一文件中定义的派生类“不存在”? [英] Derived class defined later in the same file "does not exist"?
问题描述
让我们假设我们有两个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 $ c $中涉及的 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屋!