PHP's spl_autoload_register如何解决require_once的循环依赖


How does PHP's spl_autoload_register resolve circular dependencies with require_once?

如何解决PHP的spl_autoload_register与require_once循环依赖关系?

循环依赖可以在某些情况下解决,但不是全部。让我们从一个失败的例子开始。假设在不同的文件中定义了三个类:

cat.php

class Cat extends Animal {}

animal.php

require_once('cat.php');
class Animal extends Creature {}

creature.php

class Creature {}

假设我们也有一个脚本,它有一个自动加载器,并创建了一个Animal的实例:

run.php

spl_autoload_register(function($className) {
    require_once("$className.php");
});
$a = new Animal();

使用"php run.php"运行此脚本将导致php致命错误:

PHP致命错误:Class 'Animal' not found in…/Cat.php

我认为这对我来说很直观,因为动物和猫之间的循环依赖:

  1. 自动加载程序试图加载animal.php
  2. 由于require_once()
  3. 加载animal.php会导致cat.php加载
  4. 加载cat.php失败,因为它扩展了Animal,并且Animal不能被自动加载器加载两次。

这里有一些修改以确保我们不会得到致命的

  1. animal.php不应该有require_once('cat.php')
      这似乎是最好的解决方案,因为它有效地消除了动物和猫之间的循环依赖
  2. 动物类不应该扩展Creature
  3. 而不是使用自动加载器在run.php,只是有一个require_once()为animal.php和creature.php

问题:

  1. 为什么#2有效?为什么动物不扩展生物导致解决动物和猫之间的循环依赖关系?
  2. 为什么#3有效?自动加载器不只是在后台做require_once()吗?

这个例子的完整代码(包括一些额外的日志记录)可以在这里找到

既然你的自动加载器——正如它的名字所说——自动加载你的类,你不需要任何其他的要求,除了在自动加载器函数。

如果你使用require_once而不是require,它仍然只会加载一次,无论你是从它扩展还是仅仅创建一个对象。

所以只要使用你在你的问题中发布的代码,并删除require_once()在你的animal.php,因为自动加载器已经需要它。


旁注:如果你不想自己创建自动加载器,你可以使用composer自动加载器。它易于安装并且非常有用,因为它处理子目录并使您遵循严格的命名空间约定。

如果你想这样做,你需要先安装composer。然后,创建一个名为composer的文件。在您的基本目录中使用以下内容

{
    "autoload": {
        "psr-4": { "YourProject''": "src/" }
    }
}

然后需要在命令行中执行以下命令:

cd path/to/your/project
composer dump-autoload

如果你已经这样做了,把你的类放在basedirectory/src注意,现在必须为所有类提供一个名称空间,在本例中是namespace YourProject。你终于完成了!

现在进入基本目录并创建一个文件,命名为index.php:

require_once ('vendor/autoloader.php');
$a = new YourProject'Animal();

对不起,先生,我的边注太长了!

从animal.php中删除require_once('cat.php');

如果对象不存在,则调用自动加载器,对不存在的对象单独调用。所以,如果每个对象都有自己的文件名,那就没问题了。

当前的代码流是这样的:

$a = new Animal();
#animal does not exist, spl_autoload('animal');
#include cat.php
#creature does not exist, spl_autoload('creature')

然而,这是通过脚本路径位置。说明cat.phpanimal.php可能不在同一个目录下,所以该文件可能不存在

然而,对于一个更动态的自动装填系统,我推荐PSR-4自动装填机。它使用名称空间来确定类在目录树中的位置。

new 'path'to'Animal();

将导致自动加载器包含/path/to/animal.php文件

您的示例失败的原因是您试图在定义Animal之前使用它的定义。

首先,尝试自动加载Animal:

$a = new Animal(); // This triggers the require_once of animal.php

在Animal .php中,在定义Animal类

之前需要cat.php
require_once('cat.php');    // This file will be fully evaluated before the next line
class Animal extends Creature {}  // Since the prior line relied on the existence of Animal, you get a compile error.

Animal类不能在cat.php中自动加载,因为你已经在它的包含文件上调用了require_once;它只是还没有完全被评估。

正如其他人所说,这是直接调用require_once与自动加载器混合的结果。作为另一种解决方法,您可以将require_once('cat.php')移到文件的末尾,它应该像您期望的那样工作。