2013-03-28 49 views
33

曾经遇到过这么多次,我不知道为什么这让我好奇。有些课程在宣布之前就开始工作,有些则没有;类扩展或接口如何工作?

实施例1

$test = new TestClass(); // top of class 
class TestClass { 
    function __construct() { 
     var_dump(__METHOD__); 
    } 
} 

输出

string 'TestClass::__construct' (length=22) 

实施例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)); 
    } 
} 

输出

Fatal error: Class 'TestClass' not found 

实施例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 

输出

string 'TestClass::__construct' (length=22) 

实施例4只要它实现JsonSerializable(I也与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 

(或任何其他)

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 

还检查操作码without JsonSerializable

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 

也Chec糟透了操作码with JsonSerializable

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 

问题

  • 我知道Example 3工作是因为类是宣布启动之前,但为什么会在第一时间Example 1工作?
  • 这个扩展或接口在PHP中工作的整个过程如何使一个有效和另一个无效?
  • 例4中究竟发生了什么?
  • Opcodes本来应该让事情变得清楚,但它使它更加复杂,因为class_existsTestClass之前被调用,但事实恰恰相反。
+2

我还想知道在过去,我从'Iterator'和朋友都知道这一点。 – hakre

+0

如果实现的类也存在于同一个文件中,这是不同的吗?也许这与php找到引用类的方式有关。 (在文件中查找,甚至访问一个_autoload(),最后寻找在类的本地代码) –

+1

它即使包括一类同样的问题... – Baba

回答

18

我找不到写在PHP类定义;不过,我想它与您的实验所指出的User-defined functions完全相同。它们被引用之前

函数不需要定义,除了当一个函数被有条件地限定为显示在下面的两个例子。当以有条件的方式定义函数时;其定义必须先处理以前的才能被调用。

<?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"; 
} 

?> 

这是类也是如此:

  • 例1作品,因为该类不是在别的条件。
  • 实施例2失败,因为类是在JsonSerializable条件。
  • 实施例3作品因为类被正确之前被调用定义。
  • 实施例4得到假第一次因为类是有条件的,但是,因为该类已经被加载以后成功。

类由或由条件实现一个接口或从另一个文件(require)延伸的另一类。我称之为条件性的,因为现在定义依赖于另一个定义。

想象一下PHP解释器需要在这个文件中的代码先看看。它看到一个非条件类和/或函数,所以它继续并将它们加载到内存中。它看到一些有条件的并跳过它们。

然后解释器开始解析执行页面。在示例4中,它获得class_exists("TestClass")指令,检查内存,并说不,我没有。如果没有它,因为它是有条件的。它继续执行指令,查看条件类并执行指令以实际将类加载到内存中。

然后下降到最后的class_exists("TestClass")并认为该类确实存在于内存中。

在读取您的操作码时,在class_exist之前不会调用TestClass。你看到的是它发送 TestClass中,以便它在内存中的下一行,这实际上对class_exists

调用DO_FCALL然后,您可以看到它是如何处理类定义本身SEND_VAL:

  1. ZEND_DECLARE_CLASS - 这是加载类定义
  2. ZEND_ADD_INTERFACE - 这取JsonSerializable并添加到您的类确定指标
  3. ZEND_VERIFY_ABSTRACT_CLASS - 这验证了一切都是理智的。

它是第二片ZEND_ADD_INTERFACE出现,以防止PHP引擎从仅仅加载上在它的初始峰值的类。

如果你希望如何PHP解释 编译的更详细的讨论和执行这些方案的代码,我建议采取一个 看看@StasManswer to this question,他 提供它的一个很好的概括,在大于深度这个答案是。

我想我们回答了您的所有问题。

最佳实践:将每个在它自己的文件,你的类,然后autoload他们根据需要,在他的回答@StasM状态,使用一个明智的文件命名和自动加载策略 - 例如PSR-0或类似的东西。当你这样做时,你不再需要关心引擎加载它们的顺序,它只是自动为你处理。

+1

我真的很喜欢你的答案 - 非常简洁,你提到'autoload'。更多的人应该使用这个伟大的机制。 – Joshua

5

基本前提是,对于要使用的类,必须对其进行定义,即对引擎已知。这永远不能改变 - 如果你需要某个类的对象,PHP引擎需要知道类是什么。

但是,发动机获得这种知识的时刻可能不同。首先,引擎对PHP代码的使用由两个独立的进程组成 - 编译和执行。在编译阶段,引擎将PHP代码按照您所知的方式转换为您已熟悉的一组操作代码,在引擎通过操作代码的第二阶段,处理器会通过内存中的指令并执行它们。

其中一个操作码是定义新类的操作码,它通常被插入源类中的类定义所在的相同位置。

但是,当编译器遇到类定义时,它可能能够在执行任何代码之前将该类输入到引擎已知的类的列表中。这被称为“早期绑定”。如果编译器确定它已经拥有创建类定义所需的所有信息,并且没有理由推迟创建类实例直到实际运行时才会发生这种情况。目前,发动机做到这一点只有在类:

  1. 没有连接到它
  2. 接口或特征不是抽象
  3. 要么不扩展任何类或延伸仅仅是已知的类发动机
  4. 被声明为顶级的语句(即没有内部状况,功能等)

这种行为也可以通过编译器选项修改,但这些只适用于像APC扩展所以不应该成为你非常关心的问题,除非你打算开发APC或类似的扩展。

这也意味着这将是确定:

class B extends A {} 
class A { } 

,但是这不会是:

class C extends B {} 
class B extends A {} 
class A { } 

由于A将被早期绑定,因此可用于B的定义,而B会仅在第2行所定义,从而对第1行的下

在你的情况下,当你的类实现的接口的定义不可用,它不早束缚,从而变成已知至t他在发现“阶级”声明时引擎。当它是没有接口的简单类时,它是早期绑定的,因此一旦文件编译完成,引擎就会知道该引擎(您可以在文件中的第一个语句之前看到这一点)。

为了不与发动机的所有这些奇怪的细节打扰,我会支持以前的答案的建议 - 如果你的脚本是小,只是在使用前声明类。如果您有更大的应用程序,请在单个文件中定义您的课程,并制定明智的文件命名和自动加载策略 - 例如PSR-0或类似的东西,适合你的情况。