遇到了这么多次,不知道为什么,所以让我感到好奇。有些类在声明之前起作用,而另一些则没有。

示例1

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

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

示例2

当一个类扩展另一个类或实现任何接口(interface)时
$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(我也用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

一旦实现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

还检查了操作码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

还检查了操作码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中扩展或接口(interface)的整个过程如何使一个有效而另一个无效?
  • 示例4中到底发生了什么?
  • Opcodes本来可以使事情变得清楚,但只是使其变得更加复杂,因为class_existsTestClass之前被调用,但情况恰恰相反。
  • 最佳答案

    我找不到关于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 第一次因为该类是有条件的,所以为false,但由于该类已被加载而后又成功。

  • 通过实现接口(interface)或从另一个文件(require)扩展另一个类,使该类成为有条件的。我称它为有条件的,因为该定义现在依赖于另一个定义。

    想象一下,PHP解释器首先查看了该文件中的代码。它看到了无条件的类和/或函数,因此它继续并将它们加载到内存中。它会看到一些有条件的条件,并跳过它们。

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

    然后,它下降到最后一个class_exists("TestClass"),并看到该类确实存在于内存中。

    在阅读您的操作码时,不会在TestClass之前调用class_exist。您所看到的是SEND_VAL,它将发送值TestClass,以便它在内存中用于下一行,该行实际上在class_exists上调用DO_FCALL

    然后,您可以看到它如何处理类定义本身:
  • ZEND_DECLARE_CLASS -这正在加载您的类定义
  • ZEND_ADD_INTERFACE -这会获取JsonSerializable并将其添加到您的类定义中
  • ZEND_VERIFY_ABSTRACT_CLASS -这可验证一切正常。

  • 正是第二部分 ZEND_ADD_INTERFACE 似乎阻止了PHP Engine仅在该类的初始峰值处加载该类。



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

    最佳做法:将每个类放在自己的文件中,然后根据需要autoload进行分类,如@StasM在他的回答中所述,请使用明智的文件命名和自动加载策略-例如PSR-0或类似的内容。当您执行此操作时,您不再需要担心引擎加载它们的顺序,它会自动为您处理。

    关于php - 类扩展或接口(interface)如何工作?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/15688642/

    10-14 12:41
    查看更多