在 PHP 语言中,自动加载是一种在代码中自动包含项目类文件的方法。假设你有一个复杂的面向对象的 PHP 项目,其中包含一百多个 PHP 类。你需要确保在使用所有类之前加载它们。本文旨在帮助你理解 PHP 中自动加载、命名空间和 use
关键字的概念、原因和使用方法。
什么是自动加载?
在一个复杂的 PHP 项目中,你可能会使用数百个类。如果没有自动加载,你可能需要手动包含每个类。你的代码可能看起来像这样:
<?php
// manually load every file the whole project uses
require_once __DIR__.'/includes/SomeClass1.php';
require_once __DIR__.'/includes/SomeClass2.php';
require_once __DIR__.'/includes/SomeClass3.php';
require_once __DIR__.'/includes/SomeClass4.php';
require_once __DIR__.'/includes/SomeClass5.php';
require_once __DIR__.'/includes/SomeClass6.php';
require_once __DIR__.'/includes/SomeClass7.php';
require_once __DIR__.'/includes/SomeClass8.php';
require_once __DIR__.'/includes/SomeClass9.php';
require_once __DIR__.'/includes/SomeClass10.php';
// ... a hundred more times
$my_example = new SomeClass1();
这往好听了说是繁琐,往难听了说是难以维护。
如果可以,让 PHP 在你需要时自动加载类文件,那会怎么样?你可以通过自动加载来实现。
PHP 自动加载 101
创建自动加载器只需两个步骤。
- 编写一个函数来查找需要包含的文件。
- 使用核心 PHP 函数
spl_autoload_register()
注册该函数。
以下是如何为上面的示例执行此操作:
<?php
/**
* Simple autoloader
*
* @param $class_name - String name for the class that is trying to be loaded.
*/
function my_custom_autoloader( $class_name ){
$file = __DIR__.'/includes/'.$class_name.'.php';
if ( file_exists($file) ) {
require_once $file;
}
}
// add a new autoloader by passing a callable into spl_autoload_register()
spl_autoload_register( 'my_custom_autoloader' );
$my_example = new SomeClass1(); // this works!
就这样。你不再需要手动 require_once
项目中的每个类文件。相反,通过你的自动加载器,系统会在使用类时自动 require 文件。
为了更好地理解这里发生了什么,请逐步了解上面的代码:
- 函数
my_custom_autoloader
期望一个名为$class_name
的参数。给定一个类名,该函数会查找具有该名称的文件并加载该文件。
- PHP 中的
spl_autoload_register()
函数期望一个可调用参数。可调用参数可以是多种类型,例如函数名、类方法,甚至是匿名函数。在本例中,它是一个名为my_custom_autoloader
的函数。
- 因此,代码能够实例化名为
SomeClass1
的类,而无需首先 require 其 PHP 文件。
那么,当运行此脚本时会发生什么?
- PHP 意识到尚未加载名为
SomeClass1
的类,因此它会执行已注册的自动加载器。
- PHP 执行自定义自动加载函数 (
my_custom_autoloader
),并将字符串SomeClass1
作为$class_name
的值传递给它。
- 自定义函数将文件定义为
$file = __DIR__.'/includes/SomeClass1.php';
,查找其是否存在 (file_exists()
),然后(只要找到该文件)使用require_once __DIR__.'/includes/SomeClass1.php';
将其标记为 required。因此,类的 PHP 文件会自动加载。
万岁!你现在有了一个非常简单的自动加载器,它可以在首次实例化类时自动加载类文件。在一个中等规模的项目中,你已经避免了编写数百行代码。
什么是 PHP 命名空间?
命名空间是一种封装类似功能或属性的方法。一个简单(且实用)的类比是操作系统的目录结构。文件 foo.txt
可以同时存在于目录 /home/greg
和 /home/other
中,但是两个 foo.txt
的副本不能共存于同一目录中。
此外,要访问 /home/greg
目录之外的 foo.txt
文件,你必须使用目录分隔符将目录名称前置到文件名,才能得到 /home/greg/foo.txt
。
你可以在 PHP 文件的顶部使用 namespace
关键字定义命名空间:
<?php
namespace Jonathan;
function do_something() {
echo "this function does a thing";
}
在上面的示例中,我将 do_something()
函数封装在 Jonathan
的 namespace
中。这意味着很多事情——最重要的是,这些事情都不会与全局范围内同名的函数冲突。
例如,假设你将上面的代码放在名为 jonathan-stuff.php
的文件中。在另一个文件中,你有这个:
<?php
require_once "jonathan-stuff.php";
function do_something(){ echo "this function does a completely different thing"; }
do_something();
// this function does a completely different thing
没有冲突。你有两个名为 do_something()
的函数,它们能够彼此共存。
现在你所要做的就是弄清楚如何访问命名空间方法。这是通过与目录结构非常相似的语法完成的,使用反斜杠:
<?php
\Jonathan\do_something();
// this function does a thing
此代码执行位于 Jonathan
命名空间内的名为 do_something()
的函数。
此方法也(更常见地)用于类。例如:
?php
namespace Jonathan;
class SomeClass { }
可以像这样实例化:
<?php
$something = new \Jonathan\SomeClass();
借助命名空间,非常大的项目可以包含许多同名的类,而不会发生任何冲突。很棒,对吧?
命名空间解决了哪些问题?
要了解命名空间提供的优势,你只需回顾一下没有命名空间的 PHP。在 PHP 5.3 版本之前,你无法封装类,因此它们始终面临与另一个同名类冲突的风险。前缀类名曾经(并且在某种程度上仍然)很常见:
<?php
class Jonathan_SomeClass { }
你可以想象,代码库越大,类越多,前缀就越长。如果你打开一些旧的 PHP 项目,并发现类名超过 60 个字符长,例如:
<?php
class Jonathan_SomeEntity_SomeBundle_SomeComponent_Validator { }
编写像这样的长类名和编写像 \Jonathan\SomeEntity\SomeBundle\SomeComponent\Validator
这样的长类名有什么区别?这是一个很好的问题,答案在于在给定上下文中多次使用该类的简易程度。想象一下,你不得不在单个 PHP 文件中多次使用一个长类名。目前,你有两种方法可以做到这一点。
没有命名空间:
<?php
class Jonathan_SomeEntity_SomeBundle_SomeComponent_Validator { }
$a = new Jonathan_SomeEntity_SomeBundle_SomeComponent_Validator();
$b = new Jonathan_SomeEntity_SomeBundle_SomeComponent_Validator();
$c = new Jonathan_SomeEntity_SomeBundle_SomeComponent_Validator();
$d = new Jonathan_SomeEntity_SomeBundle_SomeComponent_Validator();
$e = new Jonathan_SomeEntity_SomeBundle_SomeComponent_Validator();
哎哟,这要输入很多东西。这是使用命名空间的版本:
<?php
namespace Jonathan\SomeEntity\SomeBundle\SomeComponent;
class Validator { }
在代码的其他地方:
<?php
$a = new \Jonathan\SomeEntity\SomeBundle\SomeComponent\Validator();
$b = new \Jonathan\SomeEntity\SomeBundle\SomeComponent\Validator();
$c = new \Jonathan\SomeEntity\SomeBundle\SomeComponent\Validator();
$d = new \Jonathan\SomeEntity\SomeBundle\SomeComponent\Validator();
$e = new \Jonathan\SomeEntity\SomeBundle\SomeComponent\Validator();
这当然没有好多少。幸运的是,还有第三种方法。你可以利用 use
关键字来引入命名空间。
use
关键字
use
关键字将给定的命名空间导入到当前上下文中。这允许你使用其内容,而无需每次使用它时都引用其完整路径。
<?php
namespace Jonathan\SomeEntity\SomeBundle\SomeComponent;
class Validator { }
现在你可以这样做:
<?php
use Jonathan\SomeEntity\SomeBundle\SomeComponent\Validator;
$a = new Validator();
$b = new Validator();
$c = new Validator();
$d = new Validator();
$e = new Validator();
除了封装之外,导入是命名空间的真正力量。
现在你已经了解了自动加载和命名空间的概念,你可以将它们结合起来,创建一种可靠的项目文件组织方式。
PSR-4:PHP 自动加载和命名空间的标准
PHP 标准建议 (PSR) 4 是一种常用的模式,用于组织 PHP 项目,使类的命名空间与该类文件的相对文件路径相匹配。
例如,你在一个使用 PSR-4 的项目中工作,并且你有一个名为 \Jonathan\SomeBundle\Validator();
的命名空间类。你可以确定该类的文件可以在文件系统中的这个相对位置找到:<relative root>/Jonathan/SomeBundle/Validator.php
。
为了进一步强调这一点,以下是使用 PSR-4 的项目中类的 PHP 文件存在的更多示例:
- 命名空间和类:
\Project\Fields\Email\Validator()
- 文件位置:
<relative root>/Project/Fields/Email/Validator.php
- 文件位置:
- 命名空间和类:
\Acme\QueryBuilder\Where
- 文件位置:
<relative root>/Acme/QueryBuilder/Where.php
- 文件位置:
- 命名空间和类:
\MyFirstProject\Entity\EventEmitter
- 文件位置:
<project root>/MyFirstProject/Entity/EventEmitter.php
- 文件位置:
这实际上并非 100% 准确。项目的每个组件都有自己的相对根目录,但不要忽视此信息:了解 PSR-4 意味着类的文件位置可以帮助你轻松找到大型项目中的任何类。
PSR-4 如何工作?
PSR-4 工作的原因是它通过自动加载器函数实现。查看一个 PSR-4 示例自动加载器函数:
<?php
spl_autoload_register( 'my_psr4_autoloader' );
/**
* An example of a project-specific implementation.
*
* @param string $class The fully-qualified class name.
* @return void
*/
function my_psr4_autoloader($class) {
// replace namespace separators with directory separators in the relative
// class name, append with .php
$class_path = str_replace('\\', '/', $class);
$file = __DIR__ . '/src/' . $class_path . '.php';
// if the file exists, require it
if (file_exists($file)) {
require $file;
}
}
现在假设你刚刚实例化了 new \Foo\Bar\Baz\Bug();
类。
- PHP 使用字符串值
$class = "\Foo\Bar\Baz\Bug"
执行带有$class
参数的自动加载器。
- 使用
str_replace()
将所有反斜杠更改为正斜杠(像大多数目录结构一样使用),将命名空间转换为目录路径。
- 在
<relative root>/src/Foo/Bar/Baz/Bug.php
位置查找该文件是否存在。
- 如果找到该文件,则加载它。
换句话说,你将 Foo\Bar\Baz\Bug
更改为 /src/Foo/Bar/Baz/Bug.php
,然后找到该文件。
Composer 和自动加载
Composer 是一个命令行 PHP 包管理器。你可能已经看到一个项目的根目录中有一个 composer.json
文件。此文件告诉 Composer 关于项目的信息,包括项目的依赖项。
这是一个简单的 composer.json
文件示例:
{
"name": "jonathan/example",
"description": "This is an example composer.json file",
"require": {
"twig/twig": "^1.24"
}
}
此项目名为 "jonathan/example",并且有一个依赖项:Twig 模板引擎(版本 1.24 或更高版本)。
安装 Composer 后,你可以使用 JSON 文件下载项目的依赖项。这样做时,Composer 会生成一个 autoload.php
文件,该文件会自动处理所有依赖项中类的自动加载。

(Jonathan Daggerheart,CC BY-SA 4.0)
如果将此新文件包含在项目中,则会在需要时自动加载依赖项中的所有类。
PSR 使 PHP 更好
由于 PSR-4 标准及其广泛采用,Composer 可以生成一个自动加载器,该自动加载器可以在你在项目中实例化依赖项时自动处理加载它们。下次编写 PHP 代码时,请记住命名空间和自动加载。
评论已关闭。