PSR-4 自动加载
PSR-4(PHP Standard Recommendation 4)是 PHP-FIG(PHP Framework Interop Group)制定的自动加载标准,定义了从命名空间到文件目录的映射规则。通过 PSR-4,你只需 require 一个 vendor/autoload.php 文件,就可以使用项目中任何类,无需手动编写大量 require 或 include 语句。本节将全面介绍 PSR-4 规范、Composer 中的 autoload 配置,以及在实际项目中的应用。
前置知识
在阅读本节之前,你需要了解:
- PHP 命名空间(namespace)基础
- Composer 基本用法(参见 Composer 基本命令)
composer.json配置基础(参见 composer.json 配置)- PSR-12 代码风格(类文件的组织方式)
什么是 PSR-4
PSR-4 的全称是 "Autoloading Standard",它定义了一套从 命名空间 映射到 文件系统路径 的规则。核心思想是:
命名空间中的每一个 \ 分隔符对应文件系统中的一个目录分隔符 /,最终的类名(不包含命名空间前缀)对应文件名。
PSR-4 要求:
- 一个完全限定的类名(FQCN)具有如下形式:
\<NamespacePrefix>(\<SubNamespaceNames>)*\<ClassName> - 命名空间前缀对应一个或多个"基础目录"
- 命名空间前缀之后的连续子命名空间对应基础目录中的子目录
- 最终的类名对应一个以
.php为后缀的文件
映射规则图解
命名空间: App\Http\Controllers\UserController
对应路径: app/Http/Controllers/UserController.php
规则分解:
App → 基础目录前缀(对应 src/ 或 app/)
\Http → Http 子目录
\Controllers → Controllers 子目录
\UserController → UserController.php命名空间与文件路径的映射
<?php
declare(strict_types=1);
// 文件:app/Http/Controllers/UserController.php
namespace App\Http\Controllers;
class UserController
{
public function index(): void
{
echo "User Controller";
}
}
// 使用时:
// require_once 'vendor/autoload.php';
// $controller = new \App\Http\Controllers\UserController();
// $controller->index();PSR-4 vs PSR-0
PSR-4 是 PSR-0 的后继标准,两者主要区别:
- PSR-0 允许在命名空间中使用下划线
_(已废弃),PSR-4 不允许 - PSR-0 要求类名中包含下划线
_映射到目录分隔符,PSR-4 取消了这个规则 - PSR-4 的映射规则更简洁,完全依赖命名空间到目录的对应
- PSR-0 已于 2014 年被废弃,现在应统一使用 PSR-4
Composer 中的 autoload 配置
psr-4 配置
在 composer.json 中通过 autoload.psr-4 配置命名空间到目录的映射:
{
"autoload": {
"psr-4": {
"App\\": "app/",
"Domain\\": "src/Domain/",
"Infrastructure\\": "src/Infrastructure/"
}
}
}配置格式:"命名空间前缀\\": "目录路径/"
命名空间中的反斜杠
在 JSON 中,反斜杠 \ 需要转义为 \\。所以 App\ 在 JSON 中写为 "App\\"。
目录结构示例
my-project/
├── composer.json
├── vendor/
│ └── autoload.php ← Composer 生成的自动加载入口
├── app/ ← App\ 命名空间根目录
│ ├── Http/
│ │ ├── Controllers/
│ │ │ ├── UserController.php ← App\Http\Controllers\UserController
│ │ │ └── OrderController.php ← App\Http\Controllers\OrderController
│ │ ├── Middleware/
│ │ │ └── AuthMiddleware.php ← App\Http\Middleware\AuthMiddleware
│ │ └── Requests/
│ │ └── StoreUserRequest.php ← App\Http\Requests\StoreUserRequest
│ ├── Models/
│ │ ├── User.php ← App\Models\User
│ │ └── Order.php ← App\Models\Order
│ ├── Services/
│ │ ├── PaymentService.php ← App\Services\PaymentService
│ │ └── EmailService.php ← App\Services\EmailService
│ └── Providers/
│ └── AppServiceProvider.php ← App\Providers\AppServiceProvider
└── src/
├── Domain/
│ ├── User/
│ │ ├── UserId.php ← Domain\User\UserId
│ │ └── UserRepository.php ← Domain\User\UserRepository
│ └── Order/
│ ├── OrderId.php ← Domain\Order\OrderId
│ └── OrderRepository.php ← Domain\Order\OrderRepository
└── Infrastructure/
├── Persistence/
│ └── MySqlUserRepository.php ← Infrastructure\Persistence\MySqlUserRepository
└── Http/
└── GuzzleHttpClient.php ← Infrastructure\Http\GuzzleHttpClient类文件编写规范
<?php
declare(strict_types=1);
// 文件路径: app/Http/Controllers/UserController.php
// 命名空间: App\Http\Controllers
namespace App\Http\Controllers;
use App\Models\User;
use App\Services\UserService;
class UserController
{
public function __construct(
private readonly UserService $userService,
) {}
public function index(): array
{
return $this->userService->getAllUsers();
}
public function show(int $id): ?User
{
return $this->userService->findUser($id);
}
public function store(array $data): User
{
return $this->userService->createUser($data);
}
public function update(int $id, array $data): ?User
{
return $this->userService->updateUser($id, $data);
}
public function destroy(int $id): bool
{
return $this->userService->deleteUser($id);
}
}<?php
declare(strict_types=1);
// 文件路径: app/Models/User.php
// 命名空间: App\Models
namespace App\Models;
class User
{
public function __construct(
public readonly int $id,
public readonly string $name,
public readonly string $email,
public readonly \DateTimeImmutable $createdAt,
) {}
public function toArray(): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'created_at' => $this->createdAt->format('Y-m-d H:i:s'),
];
}
}<?php
declare(strict_types=1);
// 文件路径: app/Services/UserService.php
// 命名空间: App\Services
namespace App\Services;
use App\Models\User;
use Domain\User\UserRepository;
use Domain\User\UserId;
class UserService
{
public function __construct(
private readonly UserRepository $userRepository,
) {}
public function getAllUsers(): array
{
// 业务逻辑
return [];
}
public function findUser(int $id): ?User
{
$userId = new UserId($id);
return $this->userRepository->findById($userId);
}
public function createUser(array $data): User
{
// 创建用户逻辑
// ...
return new User(1, $data['name'], $data['email'], new \DateTimeImmutable());
}
public function updateUser(int $id, array $data): ?User
{
// 更新用户逻辑
return $this->findUser($id);
}
public function deleteUser(int $id): bool
{
$userId = new UserId($id);
$this->userRepository->delete($userId);
return true;
}
}classmap 自动加载
除了 PSR-4,Composer 还支持 classmap 方式加载类。它会扫描指定目录,建立类名到文件路径的映射。
{
"autoload": {
"psr-4": {
"App\\": "app/"
},
"classmap": [
"src/legacy/",
"src/functions.php"
]
}
}classmap 适用于:
- 不符合 PSR-4 规范的旧代码
- 函数库文件(如包含大量全局函数的文件)
- 不使用命名空间的遗留代码
classmap 的缺点
classmap需要在每次修改文件后重新生成(运行composer dump-autoload)- 不支持新增文件的热加载
- 映射关系不透明,不如 PSR-4 直观
- 仅在没有其他选择时使用
files 手动加载
files 配置允许你指定在每次请求时都自动加载的文件:
{
"autoload": {
"psr-4": {
"App\\": "app/"
},
"files": [
"app/helpers.php",
"app/constants.php"
]
}
}示例文件 app/helpers.php:
<?php
declare(strict_types=1);
// 全局辅助函数
if (!function_exists('array_get')) {
/**
* 使用点号语法从数组中获取值
*/
function array_get(array $array, string $key, mixed $default = null): mixed
{
$keys = explode('.', $key);
$value = $array;
foreach ($keys as $segment) {
if (!is_array($value) || !array_key_exists($segment, $value)) {
return $default;
}
$value = $value[$segment];
}
return $value;
}
}
if (!function_exists('str_slug')) {
/**
* 生成 URL 友好的 slug
*/
function str_slug(string $title, string $separator = '-'): string
{
$title = preg_replace('/[^\p{L}\p{N}\s]/u', '', $title);
$title = preg_replace('/[\s-]+/', $separator, $title);
return strtolower(trim($title, $separator));
}
}
if (!function_exists('response_json')) {
/**
* 返回 JSON 响应
*/
function response_json(array $data, int $statusCode = 200): void
{
http_response_code($statusCode);
header('Content-Type: application/json; charset=utf-8');
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
exit;
}
}files 的使用场景
files 应该谨慎使用,因为它会在每次请求时加载指定的文件(而非按需加载)。推荐的使用场景:
- 全局辅助函数库
- 定义常量
- 启动引导文件
不应滥用 files 来加载大量类文件,那应该使用 PSR-4 的按需加载机制。
autoload 与 autoload-dev 的区别
{
"autoload": {
"psr-4": {
"App\\": "app/",
"Domain\\": "src/Domain/"
},
"files": [
"app/helpers.php"
]
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/",
"Fixtures\\": "tests/Fixtures/"
}
}
}| 属性 | autoload | autoload-dev |
|---|---|---|
| 用途 | 生产环境和开发环境 | 仅开发环境 |
| 何时加载 | 始终加载 | 仅在安装了开发依赖时加载 |
| 典型内容 | 应用代码、库代码、辅助函数 | 测试类、测试夹具、Mock |
| 生产部署 | 包含 | 使用 --no-dev 排除 |
# 开发环境安装所有依赖
composer install
# 生产环境安装(排除开发依赖,autoload-dev 中的内容不生成自动加载)
composer install --no-dev --optimize-autoloaderdump-autoload 命令
当修改了 composer.json 中的 autoload 配置,或新增/删除了类文件后,需要重新生成自动加载文件:
# 重新生成自动加载文件
composer dump-autoload
# 等价于
composer dump-autoload
# 优化自动加载(生成 classmap)
# 将命名空间到文件的映射缓存为静态 classmap
# 生产环境推荐使用
composer dump-autoload -o
# 或
composer dump-autoload --optimize
# 不优化(开发环境推荐)
# 每次请求实时查找文件,方便开发调试
composer dump-autoload --no-dev优化模式的工作原理
使用 -o(优化模式)时,Composer 会扫描所有 PSR-4 配置的目录,为每个类生成一个精确的 classmap 缓存。这意味着:
- 类加载更快(无需每次实时查找目录)
- 新增的类需要重新运行
dump-autoload才能被发现 - 文件大小会增加
开发环境建议不使用 -o,以便新增的类能被立即发现。
实战示例:完整的项目目录结构
下面是一个遵循 PSR-4 规范的中型 PHP 项目结构:
my-project/
├── composer.json
├── composer.lock
├── .env.example
├── phpunit.xml
├── public/
│ └── index.php ← Web 入口
├── app/ ← "App\" 命名空间
│ ├── bootstrap.php ← 应用引导文件
│ ├── helpers.php ← 全局辅助函数
│ ├── Http/
│ │ ├── Controllers/
│ │ │ ├── HomeController.php
│ │ │ ├── UserController.php
│ │ │ └── ApiController.php
│ │ ├── Middleware/
│ │ │ ├── AuthMiddleware.php
│ │ │ └── CorsMiddleware.php
│ │ └── Requests/
│ │ └── UserFormRequest.php
│ ├── Models/
│ │ ├── User.php
│ │ └── Post.php
│ ├── Services/
│ │ ├── UserService.php
│ │ └── PaymentService.php
│ ├── Repositories/
│ │ ├── UserRepository.php
│ │ └── PostRepository.php
│ └── Exceptions/
│ ├── NotFoundException.php
│ └── ValidationException.php
├── src/ ← 额外的命名空间
│ ├── Domain/
│ │ ├── Events/
│ │ │ └── UserRegistered.php
│ │ └── ValueObjects/
│ │ ├── Email.php
│ │ └── Money.php
│ └── Support/
│ ├── Collection.php
│ └── Str.php
├── tests/ ← "Tests\" 命名空间(autoload-dev)
│ ├── Unit/
│ │ ├── Http/
│ │ │ └── Controllers/
│ │ │ └── UserControllerTest.php
│ │ ├── Models/
│ │ │ └── UserTest.php
│ │ └── Services/
│ │ └── UserServiceTest.php
│ ├── Feature/
│ │ └── UserRegistrationTest.php
│ └── Fixtures/
│ └── UserFixture.php
├── config/
│ ├── app.php
│ └── database.php
└── storage/
├── logs/
└── cache/对应的 composer.json:
{
"name": "myorg/my-project",
"description": "A well-structured PHP application",
"type": "project",
"require": {
"php": "^8.1",
"ext-pdo": "*",
"ext-json": "*",
"ext-mbstring": "*",
"vlucas/phpdotenv": "^5.5",
"guzzlehttp/guzzle": "^7.5"
},
"require-dev": {
"phpunit/phpunit": "^10.0",
"phpstan/phpstan": "^1.10"
},
"autoload": {
"psr-4": {
"App\\": "app/",
"Domain\\": "src/Domain/",
"Support\\": "src/Support/"
},
"files": [
"app/helpers.php",
"app/bootstrap.php"
]
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/",
"Tests\\Fixtures\\": "tests/Fixtures/"
}
},
"scripts": {
"test": "phpunit",
"phpstan": "phpstan analyse app src --level=8",
"check": ["@phpstan", "@test"]
},
"config": {
"optimize-autoloader": true,
"sort-packages": true
},
"minimum-stability": "stable",
"prefer-stable": true
}入口文件 public/index.php:
<?php
declare(strict_types=1);
// 加载 Composer 自动加载
require __DIR__ . '/../vendor/autoload.php';
// 加载环境变量
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/..');
$dotenv->safeLoad();
// 引导应用
$app = require __DIR__ . '/../app/bootstrap.php';
// 路由分发
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$method = $_SERVER['REQUEST_METHOD'];
// 简易路由匹配
$routes = require __DIR__ . '/../config/routes.php';
$matched = false;
foreach ($routes as [$routeMethod, $routePath, $handler]) {
if ($method !== $routeMethod) {
continue;
}
$pattern = preg_replace('/\{(\w+)\}/', '(?P<$1>[^/]+)', $routePath);
$regex = '~^' . $pattern . '$~';
if (preg_match($regex, $uri, $matches)) {
$matched = true;
[$controllerClass, $action] = $handler;
$controller = new $controllerClass();
echo $controller->$action(...array_slice(array_values($matches), 1));
break;
}
}
if (!$matched) {
http_response_code(404);
echo json_encode(['error' => 'Not Found']);
}注意事项
1. 文件名必须与类名一致
<?php
// 文件名: User.php
namespace App\Models;
class User {} // 正确 ✓
// 文件名: user.php (小写)
// 在 Linux/macOS 下可以工作(文件系统不区分大小写取决于文件系统)
// 在严格区分大小写的文件系统上可能失败
// 建议:始终保持文件名与类名大小写一致2. 一个文件一个类
PSR-4 规范要求每个文件只包含一个类,虽然技术上可以在一个文件中定义多个类,但不推荐:
<?php
// 不推荐:一个文件中定义多个类
namespace App\Models;
class User {}
class Admin extends User {}
class Guest extends User {}
// 推荐:每个类单独一个文件
// User.php → class User
// Admin.php → class Admin
// Guest.php → class Guest3. 命名空间前缀末尾的反斜杠
{
"autoload": {
"psr-4": {
"App\\": "app/"
}
}
}命名空间前缀末尾必须有 \\(JSON 中转义为 \\\\),它表示这是一个命名空间前缀,而不是完整的命名空间。没有这个反斜杠,PSR-4 会把整个字符串当作前缀匹配。
4. Composer 生成的自动加载文件
vendor/
├── autoload.php ← 你需要 require 的唯一文件
├── composer/
│ ├── autoload_classmap.php ← classmap 映射(-o 后生成)
│ ├── autoload_namespaces.php ← PSR-0 映射(已废弃)
│ ├── autoload_psr4.php ← PSR-4 映射
│ ├── autoload_static.php ← 静态 classmap
│ ├── autoload_files.php ← files 配置中的文件列表
│ ├── ClassLoader.php ← Composer 的自动加载器类
│ └── installed.json ← 已安装包的信息
└── ...最佳实践
1. 保持命名空间与目录结构一致
<?php
// 命名空间和目录完全对应
// App\Http\Controllers\UserController → app/Http/Controllers/UserController.php
// App\Services\PaymentService → app/Services/PaymentService.php
// Domain\Events\UserRegistered → src/Domain/Events/UserRegistered.php
// 避免出现不一致:
// 命名空间是 App\Services 但文件放在 app/Service(缺少 s)
// 这种不一致会导致 PSR-4 自动加载失败2. 使用有意义的命名空间前缀
{
"autoload": {
"psr-4": {
"MyCompany\\MyProject\\": "src/",
"MyCompany\\Shared\\": "packages/shared/src/"
}
}
}3. 使用 Composer 插件检查命名空间
# 安装 PHP-CS-Fixer 或 PHP_CodeSniffer 来检查 PSR-4 合规性
# PHP-CS-Fixer
composer require --dev friendsofphp/php-cs-fixer
vendor/bin/php-cs-fixer fix --rules=psr4
# PHP_CodeSniffer + PSR-12 sniff
composer require --dev squizlabs/php_codesniffer
vendor/bin/phpcs --standard=PSR12 src/4. 开发环境关闭优化,生产环境开启
# 开发环境(便于新增类文件后无需 dump-autoload)
composer dump-autoload
# 生产环境(类加载更快)
composer dump-autoload -o下一节
你已经掌握了 PSR-4 自动加载的完整规范和实战配置。至此,PHP 入门的"运行时与 Composer"部分已经完成。接下来建议你继续学习 PHP 语言基础部分。