php 父类什么不能继承,php – 为什么不能继承一个尚未定义的类继承自一个尚未定义的类?...

所以,PHP使用的东西叫“后期绑定”。基本上,继承和类定义不会发生,直到文件的编译结束。

有许多的原因。第一个是你显示的示例(首先扩展第二个{}工作)。第二个原因是opcache。

为了使编译在opcache的领域中正确工作,编译必须发生,没有来自其他编译文件的状态。这意味着,当它编译一个文件时,类符号表被清空。

然后,缓存该编译的结果。然后在运行时,当编译文件从内存加载时,opcache运行后期绑定,然后执行继承,并实际声明类。

class First {}

当看到该类时,它立即添加到符号表中。无论它在文件中的什么位置。因为没有必要后期绑定任何东西,它已经完全定义。这种技术称为早期绑定,它允许你在声明之前使用类或函数。

class Third extends Second {}

当看到,它被编译,但实际上没有声明。相反,它被添加到“后期绑定”列表中。

class Second extends First {}

当这最终被看到,它被编译好,而不是实际声明。它被添加到晚期绑定列表,但后三。

所以现在,当后期绑定过程发生时,它一个接一个地通过“后期绑定”类的列表。它看到的第一个是第三。然后它试图找到第二个类,但不能(因为它实际上没有声明)。所以错误被抛出。

如果你重新安排类:

class Second extends First {}

class Third extends Second {}

class First {}

然后你会看到它工作正常。

为什么这样呢?

嗯,PHP很有趣。让我们想象一系列文件:

class Foo extends Bar {}

class Bar {

//impl 1

}

class Bar {

//impl 2

}

现在,你得到的哪个Foo实例将取决于您加载的b文件。如果你需要b2.php,你会得到Foo extends Bar(impl2)。如果你需要b1.php,你会得到Foo extends Bar(impl1)。

通常我们不以这种方式编写代码,但有一些情况下可能会发生。

在正常的PHP请求中,这是微不足道的处理。原因是我们可以知道关于Bar,而我们正在编译Foo。因此,我们可以相应地调整我们的编译过程。

但是当我们把一个操作码缓存放到混合中时,事情变得复杂得多。如果我们用b1.php的全局状态编译Foo,然后(在不同的请求中)切换到b2.php,事情会以奇怪的方式破坏。

因此,相反,操作码在编译文件之前缓存全局状态。所以a.php将被编译,就像它是应用程序中唯一的文件。

编译完成后,它被缓存到内存中(以后被请求重用)。

然后,在该点(或在未来请求中从内存加载之后),“延迟”步骤发生。然后,这将编译的文件耦合到请求的状态。

这样,opcache可以更有效地将文件作为独立实体缓存,因为绑定到全局状态发生在读取缓存后。

源代码。

看看为什么,让我们看看源代码。

在Zend/zend_compile.c,我们可以看到编译类的函数:zend_compile_class_decl()。大约一半左右,你会看到下面的代码:

if (extends_ast) {

opline->opcode = ZEND_DECLARE_INHERITED_CLASS;

opline->extended_value = extends_node.u.op.var;

} else {

opline->opcode = ZEND_DECLARE_CLASS;

}

所以它最初发出一个操作码来声明继承的类。然后,编译后,调用一个名为zend_do_early_binding()的函数。这预先声明文件中的函数和类(因此它们在顶部可用)。对于普通的类和函数,它只是将它们添加到符号表(声明它们)。

有趣的是在继承的情况下:

if (((ce = zend_lookup_class_ex(Z_STR_P(parent_name), parent_name + 1, 0)) == NULL) ||

((CG(compiler_options) & ZEND_COMPILE_IGNORE_INTERNAL_CLASSES) &&

(ce->type == ZEND_INTERNAL_CLASS))) {

if (CG(compiler_options) & ZEND_COMPILE_DELAYED_BINDING) {

uint32_t *opline_num = &CG(active_op_array)->early_binding;

while (*opline_num != (uint32_t)-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;

}

outer if基本上试图从符号表中获取类,并检查它是否不存在。第二个if检查我们是否使用延迟绑定(opcache已启用)。

然后,它将用于声明类的操作码复制到延迟早期绑定数组中。

最后,函数zend_do_delayed_early_binding()被调用(通常通过opcache),它循环遍历列表并实际绑定继承的类:

while (opline_num != (uint32_t)-1) {

zval *parent_name = RT_CONSTANT(op_array, op_array->opcodes[opline_num-1].op2);

if ((ce = zend_lookup_class_ex(Z_STR_P(parent_name), parent_name + 1, 0)) != NULL) {

do_bind_inherited_class(op_array, &op_array->opcodes[opline_num], EG(class_table), ce, 0);

}

opline_num = op_array->opcodes[opline_num].result.opline_num;

}

TL; DR

对于不扩展另一个类的类,顺序并不重要。

任何被扩展的类必须在实现之前定义(或必须使用自动装载器)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值