PHP 自动加载和 Composer

C/C++ 中的#include一样,PHP 提供 require 和 include 等方法允许将一个外部 PHP 文件引入到当前文件中,include与require没有本质上的区别,唯一的不同在于错误级别,当文件无法被正常加载时include会抛出warning警告,而require则会抛出error错误。

在大型的项目开发中,文件之间的错综复杂的引用关系很容易导致一个文件被多次引入,虽然 PHP 提供了 require_once 和 include_once 避免文件的重复引入,require_once 和 include_once 会将引入过的文件保存在EG(included_files)哈希表中,再次加载时检查这个哈希表,如果发现已经加载过则直接跳过,随着引入文件的增多,维护哈希表的代价会增大,因此 PHP 5 引入了自动加载(autoloat)机制,这种机制会在文件被需要的时候引入近来,这种文件包含方式也称作懒加载(lazy loading),在前端的 NPM 包加载中也被广泛使用。

__autoload()

PHP 将所有以 __(两个下划线)开头的类方法保留为魔术方法,这类方法往往是在触发某个异常之前被执行。

如在调用不存在的方法是__call()方法会被自动调用,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php

class TestCall
{
public function __call($name, $arguments)
{
// TODO throw exception
echo 'function '.$name.' not exist!';
}

}

$test = new TestCall();
$test->sayHello(); // function sayHello not exist!

类似的,PHP 提供了 魔术方法 __autoload(), 当使用一个未定义的类时,以该方法被自动调用,类名作为参数,我们可以在发生异常之前的最后一刻去尝试引入这个类,这就是自动加载的原理,如下代码:

1
2
3
4
function __autoload($className){
// 自定义加载规则,包括类名称,类存储位置等
require_once($className.'class.php');
}

值得注意的是,如果要实现自动加载必须能根据类名确定文件位置和文件名。

autoload 缺点:

  • 类名与文件名的所有逻辑映射规则都要在一个函数中实现从而使得自动加载规则相当不灵活,代码也变得很臃肿

__spl_autoload_register()

为了改进这一问题,PHP 推出了 spl_autoload_register() ,用于注册给定的函数作为 __autoload 的实现,从而实现类名与文件名之间可以有多套逻辑映射规则,不同的映射规则分别调用一个 __autoload 函数即可,比如规则自动加载文件分别存在 A 目录和 B 目录下,使用 spl_autoload_register() 可以注册两个自动加载函数,分别将 A和B目录下的以类名同名的文件引入进来。

1
2
3
4
5
6
7
8
9
10
11
function ruleA($class) {
$file = 'A/'.$class . '.php';
require_once $file;
}
function ruleB($class) {
$file = 'B/'.$class . '.php';
require_once $file;
}
spl_autoload_register("ruleA");
spl_autoload_register("ruleB");
$text = new test(); // test 不存在,自动加载器会尝试引入 A/test.php 和 B/test.php

spl_autoload_register() 一起推出的还有很多函数,这些函数可以很灵活的管理自动加载,

  1. spl_autoload_register:注册 _autoload() 函数
  2. spl_autoload_unregister:注销已注册的函数
  3. spl_autoload_functions:返回所有已注册的函数
  4. spl_autoload_call:尝试所有已注册的函数来加载类
  5. spl_autoload :_autoload() 的默认实现
  6. spl_autoload_extionsions: 注册并返回 spl_autoload 函数使用的默认文件扩展名。

Composer

Composer 是 PHP 包管理工具,其作用和 JavaScript 中的 NPM 相当,其基本使用参考文档。每个 Composer 包都有一个 composer.json 的配置文件,想要创建一个包只需在包的根路径下执行命令composer init。composer.json 中 autoload 字段提供配置自动加载方式,包括如下四种

classmap
psr-0
psr-4
files

  1. classmap

这应该是最最简单的 autoload 模式了。大概的意思就是这样的:

1
2
3
{
"classmap": ["src/"]
}

然后 composer 在背后就会读取这个文件夹中所有的文件 然后再 vendor/composer/autoload_classmap.php 中怒将所有的 class 的 namespace + classname 生成成一个 key => value 的 php 数组

1
2
3
4
5
<?php
return [
'App\\Console\\Kernel' => $baseDir . '/app/Console/Kernel.php'
];
?>

然后就可以光明正大地用 spl_autoload_register 这个函数来怒做自动加载了。

好吧 上面的例子其实有点 tricky 就是上面这个 autoload 实际上是根据 prs-4 来生成出来的。不过这不重要,了解底层重要点,我们可以看到所有的所谓的 autoloading 其实可以理解为生成了这么一个 classmap,这是 composer dump-autoload -o 做的事儿。不然的话compoesr 会吭哧吭哧地去动态读取 psr-4 和 prs-0 的内容。

  1. psr-0

现在这个标准已经过时了。当初制定这个标准的时候主要是在 php 从 5.2 刚刚跃迁到 5.3+ 有了命名空间的概念。所以这个时候 psr-0 的标准主要考虑到了 <5.2 的 php 中 类似 Acme_Util_ClassName 这样的写法。

1
2
3
4
5
6
7
8
{
"name": "acme/util",
"auto" : {
"psr-0": {
"Acme\\Util\\": "src/"
}
}
}

文档结构是这样的

1
2
3
4
5
6
7
8
vendor/
acme/
util/
composer.json
src/
Acme/
Util/
ClassName.php

ClassName.php 中是这样的

1
2
3
<?php
class Acme_Util_ClassName{}
?>

我们可以看到由于旧版本的 php 没有 namespace 所以必须通过 _ 将类区分开。

这样稍微有点麻烦。指向一个文件夹之后 src 还要在 src 中分成好几层文档树。这样太深了。没有办法,但是似乎想要兼容 _ 的写法仔细想想这是唯一的办法了。(psr-0 要求 autoloading 的时候将 类中的 _ 转义为 ‘’)

所以在 php5.2 版本已经彻底被抛弃的今天, FIG 就怒推出了 psr-4

  1. psr-4

这个标准出来的时候一片喷声,大概的意思就是 FIG 都是傻逼么,刚刚出了 psr-0 然后紧跟着进推翻了自己。不过 FIG 也有自己的苦衷,帮没有 namespace 支持的 php5.2 擦了那么久的屁股,在2014年10月21日的早晨,终于丢失了睡眠。

抛弃了 psr-0 的 composer 从此变得非常清爽。

最简单来讲就是可以把 prs-4 的 namespace 直接想想成 file structure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"name": "acme/util",
"auto" : {
"psr-4": {
"Acme\\Util\\": "src/"
}
}
}
vendor/
acme/
util/
composer.json
src/
ClassName.php

可以看到将 Acme\Util 指向了 src 之后 psr-4 就会默认所有的 src 下面的 class 都已经有了 Acme\Util 的 基本 namespace,而 psr-4 中不会将 _ 转义成 \ 所以就没有必要有 psr-0 那么深得文档结构了。

1
2
3
4
<?php
namespace Acme\Util;
class ClassName {}
?>
  1. file

然而这还是不够。因为可能会有一些全局的 helper function 的存在。

这个写法很简单就不多看了。

1
2
3
4
5
{
"files": [
"path/to/file.php"
]
}

PHP 命名规范

在过去的 2018 年里,笔者曾经介绍过PHP 命名空间,当时提到 PSR 要求,命名空间应与文件路径一致,比如 Laravel 默认生成的 app/Http/Controllers/Auth/LoginController.php,其命名空间为 App\Http\Controllers\Auth, PSR 的命名规范是给出了类名与文件名之间的一种映射,以便实现自动加载。

参考

本文标题:PHP 自动加载和 Composer

文章作者:Syncher

发布时间:2019年03月01日 - 20:03

最后更新:2019年07月02日 - 08:07

原始链接:https://0x400.com/2019-03-01-php-autoload-and-composer.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。