Skip to content

项目目录结构

良好的项目目录结构是代码可维护性的基础。合理的目录划分使开发者能够快速定位代码、理解项目架构,并支持项目随业务增长而扩展。本节将介绍 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.md

PSR-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.xml

PSR-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.md

Monorepo 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/

最佳实践

  1. 遵循 PSR-4:确保命名空间与目录结构严格对应
  2. 分层清晰:保持各层职责明确,避免跨层调用
  3. 入口最小化:public 目录只放必要文件,其余通过框架路由
  4. 配置外置:所有环境相关配置通过 .env 文件管理
  5. 测试镜像源码:tests 目录结构应镜像 src 目录结构
  6. 文档同目录:将文档放在对应目录中或统一管理
  7. 使用 .gitkeep:在空目录中放置 .gitkeep 以保留目录结构

下一节

继续学习:设计模式

参考链接