自动加载类

如你所知,要使用一个类,你需要包含定义它的文件。到目前为止,我们都是手动包含文件的,因为我们只有几个类,并在一个文件中使用它们。但如果我们在多个文件中使用多个类,会发生什么情况呢?一定有更聪明的方法,对吗?确实有。自动加载就是个好办法!

自动加载(Autoloading)是 PHP 的一项功能,它允许程序根据预定义的规则自动搜索和加载文件。每次引用 PHP 不知道的类时,它都会询问自动加载器。如果自动加载器能找出该类在哪个文件中,它就会加载该类,程序的执行将照常进行。如果不能,PHP 就会停止执行。

那么,什么是自动加载器呢?它不过是一个以类名作为参数的 PHP 函数,预期它将加载一个文件。实现自动加载器有两种方法:一种是使用 __autoload 函数,另一种是使用 spl_autoload_register 函数。

使用 __autoload 函数

定义一个名为 __autoload 的函数告诉 PHP 该函数是它必须使用的自动加载器。 您可以实施一个简单的解决方案:

function __autoload($classname) {
    $lastSlash = strpos($classname, '\\') + 1;
    $classname = substr($classname, $lastSlash);
    $directory = str_replace('\\', '/', $classname);
    $filename = __DIR__ . '/' . $directory . '.php';
    require_once($filename);
}

我们的目的是将所有 PHP 文件保存在 src(即源代码)中。在这个目录中,目录树将模仿类的命名空间树,但第一部分 BookStore 除外。这意味着我们的 Book 类(全名为 BookStore\Domain\Book )将位于 src/Domain/Book.php 中。

为了实现这个目标,我们的 __autoload 函数尝试用 strpos 找到第一个出现的反斜杠字符串,然后用 substr 从该位置开始提取,直到结尾。实际上,这只是删除了命名空间的第一部分,即 BookStore。之后,我们用 / 替换所有 \,这样文件系统就能理解路径了。最后,我们将当前目录、作为目录的类名和 .php 扩展名连接起来。

在尝试之前,请记住创建 src/Domain 目录并将两个类移入其中。另外,为了确保我们正在测试自动加载器,请将以下内容保存为 init.php,然后访问 http://localhost:8000/init.php

<?php

use Bookstore\Domain\Book;
use Bookstore\Domain\Customer;

function __autoload($classname) {
    $lastSlash = strpos($classname, '\\') + 1;
    $classname = substr($classname, $lastSlash);
    $directory = str_replace('\\', '/', $classname);
    $filename = __DIR__ . '/src/' . $directory . '.php'
    require_once($filename);
}

$book1 = new Book("1984", "George Orwell", 9785267006323, 12);
$customer1 = new Customer(5, 'John', 'Doe', 'johndoe@mail.com');

现在浏览器不会抱怨,也没有明确的 require_once。还要记住,__autoload 函数只需定义一次,而不是在每个文件中定义。因此,从现在起,当你想使用你的类时,只要该类在命名空间和文件中遵循约定,你只需定义 use 语句即可。比以前干净多了,不是吗?

使用 spl_autoload_register 函数

__autoload 解决方案看起来很不错,但它有一个小问题:如果我们的代码非常复杂,不只有一个约定,而我们需要不止一个 __autoload 函数的实现,那该怎么办?因为我们不能定义两个同名的函数,所以我们需要一种方法来告诉 PHP 保存一个自动加载器的可能实现列表,这样它就可以尝试所有的实现,直到其中一个有效为止。

这就是 spl_autoload_register 的工作。您可以用一个有效的名称定义自动加载器函数,然后调用函数 spl_autoload_register,并将自动加载器的名称作为参数。你可以根据代码中不同的自动加载器多次调用该函数。事实上,即使你只有一个自动加载器,使用这个系统仍然比使用 __autoload 系统更好,因为这样可以方便其他人以后添加新的自动加载器:

function autoloader($classname) {
    $lastSlash = strpos($classname, '\\') + 1;
    $classname = substr($classname, $lastSlash);
    $directory = str_replace('\\', '/', $classname);
    $filename = __DIR__ . '/' . $directory . '.php';
    require_once($filename);
}

spl_autoload_register('autoloader');