composer require 一行命令就能用别人的库,但 require 'vendor/autoload.php' 背后到底发生了什么?这篇把自动加载的链路拆开,顺便说清楚 PSR-4 的映射规则和几个常见坑。
一、没有自动加载的时候
每个类都要手写 require:
require_once 'lib/Db.php';
require_once 'lib/UserRepository.php';
require_once 'lib/UserService.php';
$service = new UserService(new UserRepository(Db::conn()));文件一多就是灾难:漏一个就 Class not found,顺序错了还会因为类常量初始化而报错。
二、spl_autoload_register 是核心
PHP 提供了一个机制:当代码里 new 一个不存在的类时,引擎会依次调用注册过的「自动加载函数」。
spl_autoload_register(function (string $class): void {
$file = __DIR__ . '/lib/' . $class . '.php';
if (is_file($file)) {
require_once $file;
}
});
// 此时才触发自动加载
$service = new UserService();规则很简单:函数负责把类名映射到文件路径,然后 require 进来。 剩下的事引擎接管。
Composer 做的,就是把这件事做得通用、高效、可配置。
三、vendor/autoload.php 到底加载了什么
打开 vendor/composer/ 目录,会看到几个文件:
vendor/
├── autoload.php
└── composer/
├── autoload_real.php
├── autoload_psr4.php
├── autoload_classmap.php
├── autoload_files.php
├── autoload_namespaces.php
└── installed.phpautoload.php 只是入口,真正干活的是 autoload_real.php,它大致做了三件事:
- 注册一个
ClassLoader实例 - 把
autoload_psr4.php里的命名空间映射塞进去 - 把
autoload_classmap.php里的精确映射塞进去,然后register()
autoload_psr4.php 长这样:
return [
'Monolog\\' => [$baseDir . '/monolog/monolog/src/Monolog'],
'Psr\\Log\\' => [$baseDir . '/psr/log/src'],
'App\\' => [$baseDir . '/../src'],
];autoload_classmap.php 长这样:
return [
'App\\Kernel' => $baseDir . '/../src/Kernel.php',
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
];classmap 是「类名 → 绝对路径」的精确查表,PSR-4 是「命名空间前缀 → 目录」的推导。 查表永远比推导快,这就是为什么 dump-autoload -o 能提速。
四、PSR-4 的映射规则
假设 composer.json 这么配:
{
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}那么:
| 类名 | 文件路径 |
|---|---|
App\Kernel | src/Kernel.php |
App\Http\Controller\HomeController | src/Http/Controller/HomeController.php |
App\Db\PdoFactory | src/Db/PdoFactory.php |
规则是:去掉命名空间前缀 App\,剩下的 \ 换成 /,加上 .php,拼到 src/ 后面。
多目录映射也支持:
{
"autoload": {
"psr-4": {
"App\\": ["src/", "legacy/src/"]
}
}
}Composer 会按数组顺序查找,第一个找到就用。
也可以配多个前缀:
{
"autoload": {
"psr-4": {
"App\\": "src/",
"App\\Tests\\": "tests/",
"Acme\\": "lib/acme/"
}
}
}注意前缀最长优先匹配。App\Tests\Foo 会匹配 App\Tests\ 而不是 App\。
五、四种自动加载方式怎么选
{
"autoload": {
"psr-4": { "App\\": "src/" },
"psr-0": { "Legacy_": "lib/" },
"classmap": ["database/seeds", "database/factories"],
"files": ["src/helpers.php"]
}
}| 方式 | 适用场景 | 说明 |
|---|---|---|
psr-4 | 现代项目,首选 | 命名空间与目录严格对应 |
psr-0 | 遗留的下划线命名风格 | 已废弃,别在新项目用 |
classmap | 目录里文件命名不符合 PSR-4 | 扫描目录生成映射表 |
files | 纯函数库(如 helpers.php) | 每次请求都无条件 require |
files 要慎用。它会在每次请求时 require 这些文件,放多了就是纯开销。Laravel 的 helpers.php 就是典型例子,文件很大但每个请求都得加载。
六、三个提速手段
1. 生成优化后的 classmap
composer dump-autoload -o-o(--optimize)会把所有 PSR-4 能推导出来的类也写进 classmap,运行时直接查表,不再走文件系统推导。
2. 权威 classmap
composer dump-autoload -o --classmap-authoritative这个参数告诉 Composer:classmap 里没有的类就是不存在,不用再去文件系统找。少了一次 is_file() 检查,是生产环境最快的模式。
代价是:动态生成的类(比如运行时 eval、或者运行时往目录里丢文件)会加载不到。确定项目没这种用法再开。
3. APCu 缓存
composer dump-autoload --apcu把 classmap 塞进 APCu,多进程共享内存,省掉每次请求读 PHP 文件解析的开销。需要装 APCu 扩展,CLI 下默认不启用(apc.enable_cli=1)。
七、几个真实踩过的坑
坑一:Linux 下大小写敏感
本地 Windows 开发,App\http\Router 能加载 src/Http/Router.php(Windows 文件系统不区分大小写)。部署到 Linux 直接 Class not found。
规范:命名空间和文件名的大小写必须严格一致。 提交前在 CI 里跑一次 Linux 环境的测试能提前发现。
坑二:一个文件多个类
PSR-4 是「一个类一个文件」。如果 src/Utils.php 里塞了 Utils、Str、Arr 三个类,只有 Utils 能被自动加载,另外两个得手动 require。
要么拆文件,要么用 classmap:
{ "autoload": { "classmap": ["src/Utils.php"] } }坑三:类文件里有副作用
自动加载是「用到才加载」,所以这些副作用什么时候执行是不确定的。类文件里只应该有类定义。
坑四:改了 composer.json 忘了 dump
手动编辑 autoload 配置后,必须执行:
composer dump-autoload否则 vendor/composer/autoload_psr4.php 还是旧的,新命名空间不生效。这个坑新手最容易踩,因为 composer install 不会重新生成 autoload 映射(除非 composer.lock 变了)。
坑五:生产环境用了 --no-dev 却依赖了 dev 包
composer install --no-dev --optimize-autoloader--no-dev 不装 require-dev 里的包。如果某个「只在测试用」的类被业务代码意外引用了,本地跑得好好的,上线就炸。所以 require-dev 里的包不要出现在 src/ 中。
八、一张图理清链路
new App\Http\Router()
│
▼
引擎发现类不存在
│
▼
遍历 spl_autoload_register 注册的函数
│
▼
Composer\Autoload\ClassLoader::loadClass()
│
├─ 1. 查 classmap(精确命中,最快)
│
├─ 2. 按 PSR-4 前缀匹配,拼路径,is_file() 检查
│
└─ 3. 找到就 includeFile(),找不到就返回
│
▼
类定义进入内存,new 成功小结
spl_autoload_register是机制,Composer 是它的通用实现- PSR-4 靠命名空间前缀推导路径,classmap 是精确查表
- 生产环境用
composer dump-autoload -o --classmap-authoritative - 一个类一个文件、大小写严格一致、类文件不放副作用
理解这三条,Class not found 这类问题基本能自己定位了。



0 条评论