已经遇到过这么多次,我不知道为什么会这样让我感到好奇.有些班级在宣布之前有效,有些则没有;
例1
$test = new TestClass(); // top of class
class TestClass {
function __construct() {
var_dump(__METHOD__);
}
}
Run Code Online (Sandbox Code Playgroud)
产量
string 'TestClass::__construct' (length=22)
Run Code Online (Sandbox Code Playgroud)
例2
当类扩展另一个类或实现任何接口时
$test = new TestClass(); // top of class
class TestClass implements JsonSerializable {
function __construct() {
var_dump(__METHOD__);
}
public function jsonSerialize() {
return json_encode(rand(1, 10));
}
}
Run Code Online (Sandbox Code Playgroud)
产量
Fatal error: Class 'TestClass' not found
Run Code Online (Sandbox Code Playgroud)
例3
让我们尝试上面的同一个班级,但改变位置
class TestClass implements JsonSerializable {
function __construct() {
var_dump(__METHOD__);
}
public function jsonSerialize() {
return json_encode(rand(1, 10));
}
}
$test = new TestClass(); // move this from top to bottom
Run Code Online (Sandbox Code Playgroud)
产量
string 'TestClass::__construct' (length=22)
Run Code Online (Sandbox Code Playgroud)
示例4(我还使用class_exists进行了测试)
var_dump(class_exists("TestClass")); //true
class TestClass {
function __construct() {
var_dump(__METHOD__);
}
public function jsonSerialize() {
return null;
}
}
var_dump(class_exists("TestClass")); //true
Run Code Online (Sandbox Code Playgroud)
一旦实施JsonSerializable(或任何其他)
var_dump(class_exists("TestClass")); //false
class TestClass implements JsonSerializable {
function __construct() {
var_dump(__METHOD__);
}
public function jsonSerialize() {
return null;
}
}
var_dump(class_exists("TestClass")); //true
Run Code Online (Sandbox Code Playgroud)
还检查了操作码withoutJsonSerializable
line # * op fetch ext return operands
---------------------------------------------------------------------------------
3 0 > SEND_VAL 'TestClass'
1 DO_FCALL 1 $0 'class_exists'
2 SEND_VAR_NO_REF 6 $0
3 DO_FCALL 1 'var_dump'
4 4 NOP
14 5 > RETURN 1
Run Code Online (Sandbox Code Playgroud)
还检查了操作码withJsonSerializable
line # * op fetch ext return operands
---------------------------------------------------------------------------------
3 0 > SEND_VAL 'TestClass'
1 DO_FCALL 1 $0 'class_exists'
2 SEND_VAR_NO_REF 6 $0
3 DO_FCALL 1 'var_dump'
4 4 ZEND_DECLARE_CLASS $2 '%00testclass%2Fin%2FaDRGC0x7f563932f041', 'testclass'
5 ZEND_ADD_INTERFACE $2, 'JsonSerializable'
13 6 ZEND_VERIFY_ABSTRACT_CLASS $2
14 7 > RETURN 1
Run Code Online (Sandbox Code Playgroud)
题
Example 3工作是因为课程在其开始之前被宣布但为什么会Example 1在第一时间起作用?Opcodes应该让事情变得清晰,但只是让事情变得更加复杂,因为class_exists之前被召唤过,TestClass但事实恰恰相反.bub*_*bba 18
我找不到关于PHP类定义的文章; 但是,我认为它与您的实验所表明的用户定义函数完全相同.
在引用函数之前不需要定义函数,除非有条件地定义函数,如下面两个示例所示.当以条件方式定义函数时; 必须在被调用之前处理它的定义.
<?php
$makefoo = true;
/* We can't call foo() from here
since it doesn't exist yet,
but we can call bar() */
bar();
if ($makefoo) {
function foo()
{
echo "I don't exist until program execution reaches me.\n";
}
}
/* Now we can safely call foo()
since $makefoo evaluated to true */
if ($makefoo) foo();
function bar()
{
echo "I exist immediately upon program start.\n";
}
?>
Run Code Online (Sandbox Code Playgroud)
对于类来说也是如此:
JsonSerializable.通过实现接口或从另一个文件(require)扩展另一个类来使类成为条件.我称之为有条件,因为定义现在依赖于另一个定义.
想象一下PHP解释器首先看一下这个文件中的代码.它看到一个非条件类和/或函数,因此它继续并将它们加载到内存中.它会看到一些有条件的并跳过它们.
然后解释器开始解析页面以供执行.在例4中,它获取class_exists("TestClass")指令,检查内存,并说nope,我没有.如果没有它,因为它是有条件的.它继续执行指令,查看条件类并执行指令以将类实际加载到内存中.
然后它下降到最后一个class_exists("TestClass"),看到该类确实存在于内存中.
在读取您的操作码时,TestClass之前不会调用class_exist.你看到的是SEND_VAL,它发送值 TestClass,以便它在内存中用于下一行,它实际调用DO_FCALLclass_exists
然后,您可以看到它如何处理类定义本身:
这是第二部分ZEND_ADD_INTERFACE似乎阻止PHP引擎仅在其初始峰值上加载类.
如果您希望更详细地讨论PHP解释器如何在这些场景中编译和执行代码,我建议看看@StasM 对这个问题的回答,他提供了一个比这个答案更深入的概述.
我想我们回答了你所有的问题.
最佳实践:将每个在它自己的文件,你的类,然后自动加载它们根据需要,为@StasM状态在他的回答,用一个明智的文件命名和自动加载策略-例如 PSR-0 或类似的东西.当你这样做时,你不再需要关心引擎加载它们的顺序,它只是自动处理它.
基本前提是,对于要使用的类,必须定义它,即引擎已知的类.这永远不会改变 - 如果你需要某个类的对象,PHP引擎需要知道这个类是什么.
然而,发动机获得这种知识的那一刻可能会有所不同.首先,引擎消耗PHP代码包含两个独立的进程 - 编译和执行.在编译阶段,引擎将您知道的PHP代码转换为操作码集(您已经熟悉),在第二阶段,引擎通过操作码,因为处理器将通过内存中的指令并执行它们.
其中一个操作码是定义一个新类的操作码,该类通常插入到类定义在源中的相同位置.
但是,当编译器遇到类定义时,它可能能够在执行任何代码之前将类输入到引擎已知的类列表中.这称为"早期绑定".如果编译器确定它已经具有创建类定义所需的所有信息,并且没有理由将类创建推迟到实际运行时,则会发生这种情况.目前,引擎仅在类中执行此操作:
此行为也可以通过编译器选项进行修改,但这些行为仅适用于APC等扩展,因此除非您要开发APC或类似的扩展,否则不应该对您有太大的顾虑.
这也意味着这可以:
class B extends A {}
class A { }
Run Code Online (Sandbox Code Playgroud)
但这不会是:
class C extends B {}
class B extends A {}
class A { }
Run Code Online (Sandbox Code Playgroud)
由于A是早期绑定的,因此可用于B的定义,但B仅在第2行中定义,因此不适用于第1行的C定义.
在你的情况下,当你的类实现了接口时,它不是早期绑定的,因此在达到"class"语句时就被引擎所知.当它是没有接口的简单类时,它是早期绑定的,因此一旦文件编译完成就会被引擎知道(你可以在文件中的第一个语句之前看到这一点).
为了不打扰引擎的所有这些奇怪的细节,我会支持前一个答案的建议 - 如果你的脚本很小,只需在使用前声明类.如果您有更大的应用程序,请在单个文件中定义您的类,并具有合理的文件命名和自动加载策略 - 例如PSR-0或类似的东西,在您的情况下是合适的.
| 归档时间: |
|
| 查看次数: |
742 次 |
| 最近记录: |