项目目录结构
良好的项目目录结构是代码可维护性的基础。合理的目录划分使开发者能够快速定位代码、理解项目架构,并支持项目随业务增长而扩展。本节将介绍 PHP 项目的标准目录布局、PSR-4 自动加载兼容设计以及 Monorepo 管理策略。
基础概念
目录结构设计原则
- 关注点分离:不同职责的代码放在不同目录
- 一致性:遵循社区广泛接受的标准(如 PSR-4)
- 可发现性:开发者能够根据直觉找到需要的文件
- 可扩展性:结构能适应项目增长而不需要大规模重构
- 最小意外原则:新成员加入时能快速理解项目组织方式
标准目录布局
通用 PHP 项目结构
text
my-project/
├── app/ # 应用核心代码
│ ├── Console/ # CLI 命令
│ ├── Exceptions/ # 自定义异常
│ ├── Http/ # HTTP 层
│ │ ├── Controllers/ # 控制器
│ │ ├── Middleware/ # 中间件
│ │ ├── Requests/ # 表单请求验证
│ │ └── Resources/ # API 资源转换
│ ├── Models/ # 数据模型 / Eloquent 模型
│ ├── Policies/ # 授权策略
│ ├── Providers/ # 服务提供者
│ ├── Services/ # 业务服务层
│ └── Traits/ # 可复用 Trait
├── bootstrap/ # 框架引导文件
│ ├── app.php # 应用启动
│ └── providers.php # 提供者注册
├── config/ # 配置文件
│ ├── app.php
│ ├── auth.php
│ ├── database.php
│ ├── cache.php
│ ├── queue.php
│ └── mail.php
├── database/ # 数据库相关
│ ├── factories/ # 模型工厂
│ ├── migrations/ # 数据库迁移
│ └── seeders/ # 数据填充
├── public/ # Web 可访问目录(文档根目录)
│ ├── index.php # 入口文件
│ ├── .htaccess # Apache 重写规则
│ ├── favicon.ico
│ ├── robots.txt
│ └── build/ # 前端构建产物
│ ├── css/
│ ├── js/
│ └── images/
├── resources/ # 视图和前端资源
│ ├── views/ # Blade 模板
│ │ ├── layouts/
│ │ ├── components/
│ │ └── emails/
│ ├── lang/ # 多语言文件
│ │ ├── en/
│ │ └── zh_CN/
│ ├── css/ # 源码 CSS(如果使用纯 CSS)
│ └── js/ # 源码 JavaScript
├── routes/ # 路由定义
│ ├── web.php
│ ├── api.php
│ ├── console.php
│ └── channels.php
├── storage/ # 应用生成文件
│ ├── app/ # 应用文件
│ ├── framework/ # 框架缓存
│ └── logs/ # 日志文件
├── tests/ # 测试目录
│ ├── Unit/ # 单元测试
│ ├── Feature/ # 功能测试
│ ├── Integration/ # 集成测试
│ └── Bootstrap/ # 测试引导
├── .env # 环境变量(不提交到 Git)
├── .env.example # 环境变量示例
├── .gitignore
├── composer.json # Composer 配置
├── composer.lock # Composer 锁定文件
├── package.json # 前端依赖(如使用)
├── phpunit.xml # PHPUnit 配置
├── phpstan.neon.dist # PHPStan 配置
├── .php-cs-fixer.php # PHP-CS-Fixer 配置
└── README.mdPSR-4 兼容的纯 PHP 项目结构
对于非框架项目(如 SDK、库、工具包),推荐更简洁的结构:
text
my-library/
├── src/ # 源代码(PSR-4 根目录)
│ ├── Client.php # 主入口类
│ ├── Exception/ # 异常类
│ │ ├── ApiException.php
│ │ └── AuthException.php
│ ├── Http/ # HTTP 客户端
│ │ ├── Request.php
│ │ └── Response.php
│ ├── Service/ # 业务逻辑
│ │ └── UserService.php
│ └── Support/ # 辅助工具
│ ├── Arr.php
│ └── Str.php
├── tests/ # 测试
│ ├── Unit/
│ │ ├── ClientTest.php
│ │ └── Service/
│ │ └── UserServiceTest.php
│ └── Integration/
│ └── ApiIntegrationTest.php
├── composer.json
├── phpunit.xml
├── phpstan.neon.dist
└── README.md对应的 composer.json PSR-4 配置:
json
{
"name": "my-company/my-library",
"description": "A PHP library for ...",
"type": "library",
"require": {
"php": "^8.1",
"guzzlehttp/guzzle": "^7.0",
"ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "^10.0",
"phpstan/phpstan": "^1.10"
},
"autoload": {
"psr-4": {
"MyCompany\\MyLibrary\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"MyCompany\\MyLibrary\\Tests\\": "tests/"
}
},
"autoload-files": [
"src/functions.php"
]
}DDD(领域驱动设计)项目结构
对于大型复杂项目,DDD 架构提供了清晰的模块划分:
text
my-ddd-project/
├── src/
│ ├── Application/ # 应用层 - 编排用例
│ │ ├── Command/ # 命令对象
│ │ │ ├── User/
│ │ │ │ ├── RegisterUserCommand.php
│ │ │ │ └── UpdateProfileCommand.php
│ │ │ └── Order/
│ │ │ ├── CreateOrderCommand.php
│ │ │ └── CancelOrderCommand.php
│ │ ├── CommandHandler/ # 命令处理器
│ │ │ ├── RegisterUserHandler.php
│ │ │ └── CreateOrderHandler.php
│ │ ├── Query/ # 查询对象
│ │ │ ├── GetUserByIdQuery.php
│ │ │ └── ListOrdersQuery.php
│ │ ├── QueryHandler/ # 查询处理器
│ │ │ ├── GetUserByIdHandler.php
│ │ │ └── ListOrdersHandler.php
│ │ ├── DTO/ # 数据传输对象
│ │ │ ├── UserDto.php
│ │ │ └── OrderDto.php
│ │ ├── Event/ # 领域事件
│ │ │ ├── UserRegistered.php
│ │ │ └── OrderPlaced.php
│ │ └── EventListener/ # 事件监听器
│ │ ├── SendWelcomeEmailListener.php
│ │ └── NotifyInventoryListener.php
│ ├── Domain/ # 领域层 - 核心业务逻辑
│ │ ├── User/
│ │ │ ├── User.php # 用户实体
│ │ │ ├── UserId.php # 值对象
│ │ │ ├── Email.php # 值对象
│ │ │ ├── UserRepositoryInterface.php # 仓储接口
│ │ │ ├── UserFactory.php
│ │ │ └── Exception/
│ │ │ ├── UserAlreadyExistsException.php
│ │ │ └── InvalidEmailException.php
│ │ └── Order/
│ │ ├── Order.php # 订单实体
│ │ ├── OrderId.php
│ │ ├── OrderLine.php # 值对象
│ │ ├── OrderStatus.php
│ │ ├── OrderRepositoryInterface.php
│ │ └── Exception/
│ │ └── OrderCannotBeCancelledException.php
│ └── Infrastructure/ # 基础设施层 - 技术实现
│ ├── Persistence/
│ │ ├── Doctrine/
│ │ │ ├── UserRepository.php
│ │ │ ├── OrderRepository.php
│ │ │ └── Mappings/
│ │ │ ├── User.orm.xml
│ │ │ └── Order.orm.xml
│ │ └── Redis/
│ │ └── UserCacheRepository.php
│ ├── Messaging/
│ │ └── RabbitMq/
│ │ └── EventPublisher.php
│ ├── Notification/
│ │ ├── EmailNotificationService.php
│ │ └── SmsNotificationService.php
│ └── External/
│ ├── PaymentGateway/
│ │ └── StripePaymentGateway.php
│ └── Storage/
│ └── S3FileStorage.php
├── interfaces/ # 接口层 - 对外暴露
│ ├── Web/
│ │ ├── Controller/
│ │ │ ├── UserController.php
│ │ │ └── OrderController.php
│ │ ├── Middleware/
│ │ └── Request/
│ └── Cli/
│ └── Command/
│ ├── CreateUserCommand.php
│ └── ProcessOrdersCommand.php
├── config/
├── tests/
│ ├── Unit/
│ │ ├── Domain/
│ │ └── Application/
│ └── Integration/
├── composer.json
└── phpunit.xmlPSR-4 兼容设计
命名空间与目录映射
php
<?php
declare(strict_types=1);
// composer.json 中的 PSR-4 映射
// "App\\": "src/"
// "App\\Domain\\": "src/Domain/"
// "App\\Tests\\": "tests/"
// 文件路径与命名空间一一对应
// src/Domain/User/User.php
namespace App\Domain\User;
class User { }
// src/Application/Command/User/RegisterUserCommand.php
namespace App\Application\Command\User;
class RegisterUserCommand { }
// src/Infrastructure/Persistence/Doctrine/UserRepository.php
namespace App\Infrastructure\Persistence\Doctrine;
class UserRepository implements \App\Domain\User\UserRepositoryInterface { }
// tests/Unit/Domain/User/UserTest.php
namespace App\Tests\Unit\Domain\User;
class UserTest extends \PHPUnit\Framework\TestCase { }Composer 自动加载配置
json
{
"autoload": {
"psr-4": {
"App\\": "src/"
},
"psr-0": {
"LegacyNamespace_": "src/Legacy/"
},
"classmap": [
"src/functions.php",
"src/legacy_classes.php"
],
"files": [
"src/helpers.php"
]
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
}
}自动加载优化
运行 composer dump-autoload -o 生成优化的自动加载文件,提升生产环境性能。添加 -a 参数使用 classmap 生成绝对最快的加载器。
Monorepo 结构
Monorepo 适用场景
当组织管理多个相关包时,Monorepo 可以简化依赖管理和版本同步:
text
my-monorepo/
├── packages/ # 所有子包
│ ├── core/ # 核心包
│ │ ├── src/
│ │ │ ├── Collection.php
│ │ │ └── Contracts/
│ │ │ ├── Arrayable.php
│ │ │ └── Jsonable.php
│ │ ├── tests/
│ │ └── composer.json
│ ├── http/ # HTTP 相关包
│ │ ├── src/
│ │ │ ├── Client.php
│ │ │ ├── Middleware/
│ │ │ └── Request/
│ │ ├── tests/
│ │ └── composer.json
│ ├── database/ # 数据库包
│ │ ├── src/
│ │ │ ├── Connection.php
│ │ │ ├── QueryBuilder.php
│ │ │ └── Schema/
│ │ ├── tests/
│ │ └── composer.json
│ └── support/ # 辅助工具包
│ ├── src/
│ │ ├── Str.php
│ │ ├── Arr.php
│ │ └── Functions.php
│ ├── tests/
│ └── composer.json
├── apps/ # 应用层
│ ├── web-app/ # Web 应用
│ │ ├── src/
│ │ ├── public/
│ │ ├── tests/
│ │ └── composer.json
│ ├── api-app/ # API 应用
│ │ ├── src/
│ │ ├── tests/
│ │ └── composer.json
│ └── console-app/ # CLI 应用
│ ├── src/
│ ├── bin/
│ ├── tests/
│ └── composer.json
├── .github/ # CI/CD 配置
├── composer.json # 根 composer.json
└── README.mdMonorepo Composer 配置
根 composer.json:
json
{
"name": "my-company/monorepo",
"require": {
"php": "^8.1"
},
"require-dev": {
"phpunit/phpunit": "^10.0",
"phpstan/phpstan": "^1.10"
},
"repositories": [
{
"type": "path",
"url": "packages/*"
}
],
"autoload": {
"psr-4": {
"MyCompany\\": "packages/*/src/"
}
},
"scripts": {
"test": [
"@test:packages",
"@test:apps"
],
"test:packages": "find packages -name phpunit.xml -exec phpunit -c {} \\;",
"test:apps": "find apps -name phpunit.xml -exec phpunit -c {} \\;",
"stan": "phpstan analyse packages/*/src apps/*/src",
"cs-fix": "php-cs-fixer fix"
}
}子包 composer.json(packages/core):
json
{
"name": "my-company/core",
"description": "Core utilities and contracts",
"require": {
"php": "^8.1"
},
"autoload": {
"psr-4": {
"MyCompany\\Core\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"MyCompany\\Core\\Tests\\": "tests/"
}
}
}实战示例
初始化新项目目录结构
bash
#!/bin/bash
# 项目初始化脚本
PROJECT_NAME=$1
# 创建目录结构
mkdir -p ${PROJECT_NAME}/{src,tests,config,public,storage/{app,framework,logs},database/{migrations,seeders,factories}}
# 初始化 Composer
cd ${PROJECT_NAME}
composer init -n --name="my-company/${PROJECT_NAME}" --php="8.1"
# 安装核心依赖
composer require php
composer require --dev phpunit/phpunit phpstan/phpstan friendsofphp/php-cs-fixer squizlabs/php_codesniffer
# 创建配置文件
touch .env .env.example .gitignore
# 创建 PHPUnit 配置
cat > phpunit.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.0/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true">
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory>tests/Feature</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory>src</directory>
</include>
</source>
</phpunit>
EOF
echo "Project ${PROJECT_NAME} initialized successfully!"注意事项
文件与目录命名
命名一致性
确保目录命名风格与文件命名风格一致。PHP 类文件使用 PascalCase,配置文件和脚本使用 kebab-case 或 snake_case。
- src/ 下的 PHP 文件:PascalCase(与类名一致)
- config/ 下的配置文件:snake_case(如
database.php) - database/migrations/ 下的迁移文件:
{timestamp}_{description}.php - tests/ 下的测试文件:
{ClassName}Test.php - scripts/ 下的脚本文件:kebab-case(如
create-admin-user.php)
版本控制忽略
gitignore
# .gitignore
/vendor/
/node_modules/
/.idea/
/.vscode/
*.swp
.env
.env.local
.env.*.local
storage/
!storage/.gitignore
.php-cs-fixer.cache
.phpunit.result.cache
.phpstan-cache/
.psalm-cache/最佳实践
- 遵循 PSR-4:确保命名空间与目录结构严格对应
- 分层清晰:保持各层职责明确,避免跨层调用
- 入口最小化:public 目录只放必要文件,其余通过框架路由
- 配置外置:所有环境相关配置通过 .env 文件管理
- 测试镜像源码:tests 目录结构应镜像 src 目录结构
- 文档同目录:将文档放在对应目录中或统一管理
- 使用 .gitkeep:在空目录中放置 .gitkeep 以保留目录结构
下一节
继续学习:设计模式