gpt4 book ai didi

php - 稍后在同一文件 "does not exist"中定义的派生类?

转载 作者:IT王子 更新时间:2023-10-29 00:13:22 25 4
gpt4 key购买 nike

假设我们有两个 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 类?

谢谢

米歇尔

最佳答案

免责声明:我并不声称了解 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”),如果没有找到,那么......它在编译时不做任何事情就返回,但在运行时发出 fatal error 。

这听起来非常重要。让我们回到 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 (错误的)。

最后,如何实现 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 发出类重新声明 fatal error 。未设置标志;错误可以无条件地发出。

当编译器遇到派生类声明而基类未在同一脚本文件中声明时,它会将在其内部数据结构中注册类的行为推送到运行时。

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

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

由于这在编译时和运行时以不同的方式发生, class_exists 的行为也不一样:
  • 其祖先都包含在同一个源文件中的类在编译时注册;它们存在并且可以在该脚本中的任何点被实例化
  • 在另一个源文件中定义了祖先的类在运行时注册;在 VM 执行与源中的类定义相对应的操作码之前,这些类对于所有实际用途并不存在( class_exists 返回 false ,实例化会产生 fatal error )
  • 关于php - 稍后在同一文件 "does not exist"中定义的派生类?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12617188/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com