假设我们有两个php文件,a.php和b.php 这是文件a.php的内容:
<?php // content of a.php
class A {
}
这是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";
如果启动b.php脚本,则输出:
A: exists
B: exists
BA (before): doesn’t exist
BB: exists
BA (after): exists
为什么BA类只在类定义之后才存在?为什么其他类甚至在定义之前就已存在?有什么区别?我希望在这两种情况下都有一个共同的行为...... 有没有一种方法可以在定义之前使用BA类?
谢谢
米歇尔
答案 0 :(得分:6)
免责声明:我并不声称理解Zend的内部运作。以下是我对PHP源代码的解释,主要是通过有根据的猜测推动的。即使我对结论充满信心,但术语或细节可能会被取消。我很乐意听到任何有关Zend内部成员经验的人。
从PHP解析器we can see,当遇到类声明时,调用zend_do_early_binding
函数。 Here是处理派生类声明的代码:
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;
}
此代码立即调用zend_lookup_class
以查看父类是否存在于符号表中......然后根据是否找到父类来分叉。
如果发现父类 ,我们首先看看它的作用:
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 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;
}
好的......所以它从静态(从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;
它似乎正在生成will trigger a call再次do_bind_inherited_class
的操作码 - 但这一次,compile_time
的值将为0
(false)。
最后,class_exists
PHP函数的实现怎么样?查看源代码会显示以下代码段:
found = zend_hash_find(EG(class_table), name, len+1, (void **) &ce);
大!此class_table
变量与我们之前看到的class_table
调用中涉及的do_bind_inherited_class
相同!因此,class_exists
的返回值取决于该类的条目是否已由class_table
插入do_bind_inherited_class
。
Zend编译器在编译时不对include
指令起作用(即使文件名是硬编码的)。
如果确实如此,则没有理由根据未设置的compile_time
标志发出类重新声明致命错误;错误可以无条件地发出。
当编译器遇到派生类声明,其中基类尚未在同一个脚本文件中声明时,它会将其内部数据结构中的类注册到运行时。
从上面的最后一个代码片段可以看出这一点,该代码片段设置了一个ZEND_DECLARE_INHERITED_CLASS_DELAYED
操作码来在脚本执行时注册该类。此时,compile_time
标志将为false
,行为将略有不同。
class_exists
的返回值取决于该类是否已注册。
由于这在编译时和运行时以不同的方式发生,class_exists
的行为也不同:
class_exists
返回false
,实例化会产生致命错误)答案 1 :(得分:1)
这与包含文件include dirname(__FILE__) . "/a.php";
BB
存在,因为它扩展了在同一文件中定义的B
。
BA
不存在,因为PHP没有在线解析A
它被称为
两者都可以返回相同的结果
使用class BA extends B
include dirname(__FILE__) . "/a.php";
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 B {
}
class BB extends B {
}
echo "BA (after): ", class_exists("BA") ? "exists" : "doesn’t exist", "\n";
或定义class A
并使用class BA extends A
class A {
}
echo "<pre>";
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";
输出
BA (before): exists
BB: exists
BA (after): exists
结论
FORM PHP DOC
当包含文件时,它包含的代码将继承发生包含的行的变量范围。从那时起,调用文件中该行可用的任何变量都将在被调用文件中可用。但是,包含文件中定义的所有函数和类都具有全局范围。
我认为扩展类在PHP文档中有所涉及,可以将其视为需要更正的BUG,但主要时间包括您的课程,然后再调用或使用它们