Skip to content

命名规范

命名规范是代码可读性和可维护性的基石。统一的命名约定使团队成员能够快速理解代码意图,减少认知负担,并降低因命名不当而引入 Bug 的风险。PHP 生态有多个广泛接受的规范(如 PSR-1、PSR-2/PSR-12),本节将系统梳理 PHP 项目中各类标识符的命名规则。

前置知识

阅读本节前,建议先了解:PSR-12 编码标准Composer 自动加载

基础概念

命名的核心原则

良好的命名应遵循以下原则:

  • 可读性优先:名称应能自解释,减少注释的需求
  • 一致性:同一项目中所有代码遵循相同的命名约定
  • 精确性:名称应准确反映实体用途,避免模糊命名如 $data$info
  • 长度适中:既不过短(丧失信息量),也不过长(降低可读性)
  • 上下文相关:名称的详细程度应与实体的作用域成正比

PHP 官方规范概览

PHP-FIG(PHP Framework Interop Group)发布的 PSR 系列规范是 PHP 社区的共识标准:

规范名称命名相关内容
PSR-1基本编码标准类名必须使用 PascalCase
PSR-4自动加载命名空间与目录结构对应
PSR-12扩展编码标准方法名使用 camelCase、常量使用 UPPER_SNAKE_CASE

类名与接口命名

类名(PascalCase)

PHP 类名使用 PascalCase(也称 UpperCamelCase),每个单词首字母大写,无分隔符:

php
<?php
declare(strict_types=1);

// ✅ 正确:PascalCase
class UserController { }
class ProductRepository { }
class PaymentGatewayService { }
class AbstractValidator { }
class CsvFileParser { }

// ❌ 错误:其他风格
class user_controller { }    // snake_case
class UserControllerNew { }   // 不必要的后缀
class ParseCSV { }            // 缩写不应全大写

命名约定补充规则

php
<?php
declare(strict_types=1);

// 抽象类使用 Abstract 前缀
abstract class AbstractRepository { }
abstract class AbstractExporter { }

// 接口使用后缀或前缀
interface Renderable { }
interface Arrayable { }

// Trait 使用后缀
trait HasTimestamps { }
trait SoftDeletes { }

// 异常类使用 Exception 后缀
class ValidationException extends RuntimeException { }
class DatabaseConnectionException extends RuntimeException { }

// 测试类使用 Test 后缀,遵循被测类名
class UserServiceTest extends TestCase { }
class PaymentGatewayTest extends TestCase { }

// 数据传输对象使用 DTO 后缀
class CreateUserDto { }
class UpdateProductDto { }

// 命令类使用 Command 后缀(CQRS 模式)
class RegisterUserCommand { }
class ProcessPaymentCommand { }

// 事件类使用 Event 后缀
class UserRegisteredEvent { }
class OrderPlacedEvent { }

// 监听器/处理器使用 Handler 或 Listener 后缀
class SendWelcomeEmailHandler { }
class OrderPlacedListener { }

// 枚举类型(PHP 8.1+)使用单数 PascalCase
enum Status: string
{
    case Active = 'active';
    case Inactive = 'inactive';
}

enum UserRole: int
{
    case Admin = 1;
    case Editor = 2;
    case Viewer = 3;
}

枚举命名(PHP 8.1+)

php
<?php
declare(strict_types=1);

// 枚举类型名使用单数名词
enum HttpStatus: int
{
    case Ok = 200;
    case NotFound = 404;
    case ServerError = 500;

    // 枚举方法使用 camelCase
    public function isSuccess(): bool
    {
        return $this->value >= 200 && $this->value < 300;
    }
}

// Backed Enum(有底类型的枚举)
// Case 名称使用 PascalCase,与常量命名区分
enum Color: string
{
    case Red = '#FF0000';
    case Green = '#00FF00';
    case Blue = '#0000FF';
}

方法与函数命名

方法名(camelCase)

方法名使用 camelCase(小驼峰),首个单词首字母小写,后续单词首字母大写:

php
<?php
declare(strict_types=1);

class UserRepository
{
    // ✅ 正确:动词或动词+名词
    public function findById(int $id): ?User { }
    public function findAll(): array { }
    public function save(User $user): void { }
    public function delete(User $user): void { }
    public function existsByEmail(string $email): bool { }
    public function countActiveUsers(): int { }

    // ✅ 布尔返回值方法使用 is/has/can 前缀
    public function isActive(): bool { }
    public function hasPermission(string $permission): bool { }
    public function canDelete(): bool { }
    public function isValid(): bool { }
    public function supports(string $format): bool { }

    // ✅ 事件/生命周期方法
    public function beforeSave(): void { }
    public function afterCreate(): void { }
    public function onUserRegistered(UserRegisteredEvent $event): void { }

    // ❌ 错误示例
    public function get_the_user() { }    // snake_case
    public function FindUser() { }        // PascalCase
    public function userData() { }        // 不明确
    public function process() { }         // 过于笼统
}

常见方法命名模式

php
<?php
declare(strict_types=1);

class OrderService
{
    // CRUD 操作
    public function create(CreateOrderDto $dto): Order { }
    public function read(int $id): ?Order { }
    public function update(Order $order, UpdateOrderDto $dto): Order { }
    public function delete(Order $order): void { }

    // 查询方法:find + 条件
    public function findByStatus(OrderStatus $status): array { }
    public function findOneByToken(string $token): ?Order { }
    public function findPendingOrders(): array { }

    // 聚合方法:count/sum/average/max/min
    public function countByStatus(OrderStatus $status): int { }
    public function calculateTotal(): float { }

    // 转换方法:to + 格式
    public function toArray(): array { }
    public function toJson(): string { }
    public function toString(): string { }

    // 工厂方法:create/make/build + 对象
    public function createFromRequest(Request $request): Order { }
    public function makeDefault(): Order { }
    public function buildQuery(array $filters): Builder { }
}

函数命名

独立函数(非类方法)同样使用 camelCase,通常放在命名空间中以避免全局污染:

php
<?php
declare(strict_types=1);

namespace App\Helpers;

// 独立函数使用 camelCase
function formatCurrency(float $amount, string $currency = 'CNY'): string
{
    return sprintf('%s %.2f', $currency, $amount);
}

function isValidEmail(string $email): bool
{
    return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}

function arrayFilterNull(array $items): array
{
    return array_filter($items, fn ($item) => $item !== null);
}

// 避免在全局命名空间定义函数
// ❌ 错误
function do_something() { }

变量命名

变量名规则

变量名使用 camelCase,应具有描述性:

php
<?php
declare(strict_types=1);

class InvoiceService
{
    // ✅ 正确:描述性命名
    private string $invoiceNumber;
    private float $totalAmount;
    private array $orderItems;
    private ?User $currentUser;
    private bool $isPaid;
    private int $retryCount;

    // ✅ 集合变量使用复数形式
    private array $users;
    private array $orderItems;
    private Collection $productCategories;

    // ✅ 布尔变量使用 is/has/can/should 前缀
    private bool $isEnabled;
    private bool $hasPermission;
    private bool $canEdit;
    private bool $shouldRetry;

    // ❌ 错误:不描述性或过短
    private $data;       // 什么数据?
    private $info;       // 什么信息?
    private $temp;       // 临时什么?
    private $val;        // 什么值?
    private $arr;        // 什么数组?
    private $flag;       // 什么标志?
}

循环变量

php
<?php
declare(strict_types=1);

// 简单循环:单字母可接受
foreach ($users as $user) {
    echo $user->getName();
}

foreach ($items as $item) {
    $total += $item->getPrice();
}

// 嵌套循环:使用有意义的名称区分
foreach ($orders as $order) {
    foreach ($order->getItems() as $orderItem) {
        $orderItemTotal += $orderItem->getSubtotal();
    }
}

// 索引变量
foreach ($items as $index => $item) {
    // $index 比 $i 更清晰
}

// 键值对
foreach ($config as $key => $value) {
    // 比 $k => $v 更清晰
}

临时变量

php
<?php
declare(strict_types=1);

// 临时变量应有足够描述性
$user = $this->userRepository->findById($userId);
$fileName = $this->generateFileName($template);
$isValid = $this->validator->validate($data);

// 仅在非常局部的作用域内允许简短命名
$result = array_map(
    fn (int $n) => $n * 2,
    $numbers
);

常量命名

类常量(UPPER_SNAKE_CASE)

类常量使用 UPPER_SNAKE_CASE,单词之间用下划线分隔:

php
<?php
declare(strict_types=1);

class PaymentGateway
{
    // ✅ 正确
    public const MAX_RETRY_COUNT = 3;
    public const DEFAULT_TIMEOUT = 30;
    public const API_VERSION = 'v2';
    public const STATUS_PENDING = 'pending';
    public const STATUS_COMPLETED = 'completed';
    public const CACHE_TTL_SECONDS = 3600;

    // PHP 8.3+ 支持类常量类型
    public const string MODE_LIVE = 'live';
    public const string MODE_TEST = 'test';
    public const int MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB

    // 枚举值(PHP 8.1+)Case 使用 PascalCase,不同于常量
    // enum case 名称用 PascalCase
    // class const 用 UPPER_SNAKE_CASE

    // 私有常量
    private const ENCRYPTION_KEY = 'secret-key';
    protected const INTERNAL_CACHE_PREFIX = 'app_';
}

全局常量

php
<?php
declare(strict_types=1);

// 使用 define() 定义的全局常量同样使用 UPPER_SNAKE_CASE
define('APP_VERSION', '2.0.0');
define('MAX_UPLOAD_SIZE', 5 * 1024 * 1024);
define('DB_CONNECTION_TIMEOUT', 30);

// 更推荐使用 class constant 或 enum
// ❌ 避免过多全局常量
// ✅ 推荐使用配置类
class AppConfig
{
    public const string VERSION = '2.0.0';
    public const int MAX_UPLOAD_SIZE = 5 * 1024 * 1024;
    public const int DB_CONNECTION_TIMEOUT = 30;
}

魔法常量与预定义常量

php
<?php
declare(strict_types=1);

// PHP 预定义常量保持原始大小写
echo PHP_VERSION;        // 不是 php_version
echo PHP_EOL;            // 不是 php_eol
echo PHP_INT_MAX;        // 不是 php_int_max
echo PHP_EOL;            // 不是 php_eol
echo SORT_ASC;           // PHP 预定义常量
echo E_ALL;              // 错误报告常量
echo DIRECTORY_SEPARATOR; // 目录分隔符

// 自定义常量保持 UPPER_SNAKE_CASE
class Environment
{
    public const string DEVELOPMENT = 'development';
    public const string STAGING = 'staging';
    public const string PRODUCTION = 'production';
}

命名空间命名

命名空间规则

命名空间使用 PascalCase,与目录结构一一对应(PSR-4):

php
<?php
declare(strict_types=1);

// 顶级命名空间:项目或组织名
namespace MyCompany;

// 子命名空间:按功能模块划分
namespace MyCompany\Application;
namespace MyCompany\Domain;
namespace MyCompany\Infrastructure;
namespace MyCompany\Interfaces\Web;
namespace MyCompany\Interfaces\Api;

// 按功能子模块
namespace MyCompany\Domain\User;
namespace MyCompany\Domain\Order;
namespace MyCompany\Infrastructure\Persistence;

// 架构层命名空间(DDD)
namespace MyCompany\Application\Command;
namespace MyCompany\Application\Query;
namespace MyCompany\Application\Event;

// 命名空间与文件路径对应(PSR-4)
// MyCompany\Domain\User\UserService
// → src/Domain/User/UserService.php

典型命名空间结构

src/
├── Application/           # 应用层
│   ├── Command/           # 命令对象
│   ├── CommandHandler/    # 命令处理器
│   ├── Query/             # 查询对象
│   ├── QueryHandler/      # 查询处理器
│   ├── DTO/               # 数据传输对象
│   └── Event/             # 领域事件
├── Domain/                # 领域层
│   ├── Model/             # 领域模型
│   ├── Repository/        # 仓储接口
│   ├── Service/           # 领域服务
│   └── Exception/          # 领域异常
├── Infrastructure/        # 基础设施层
│   ├── Persistence/       # 持久化实现
│   ├── Cache/             # 缓存实现
│   ├── Mail/              # 邮件发送
│   └── External/           # 外部服务集成
└── Presentation/          # 表现层
    ├── Controller/        # 控制器
    ├── Middleware/         # 中间件
    └── Request/            # 请求对象

文件命名

文件命名规则

文件名与类名保持一致,使用 PascalCase

text
# 类文件 - 与类名完全一致
src/Domain/User/User.php                  → class User
src/Domain/User/UserRepository.php        → class UserRepository
src/Service/PaymentGateway.php            → class PaymentGateway
src/Exception/ValidationException.php      → class ValidationException

# 接口文件
src/Contract/RenderableInterface.php       → interface RenderableInterface
src/Contract/UserRepositoryInterface.php   → interface UserRepositoryInterface

# Trait 文件
src/Trait/HasTimestamps.php                → trait HasTimestamps

# 枚举文件
src/Enum/OrderStatus.php                  → enum OrderStatus

# 测试文件
tests/Unit/Domain/User/UserTest.php       → class UserTest
tests/Feature/Service/PaymentTest.php     → class PaymentTest

# 配置文件 - 使用 snake_case 或 kebab-case
config/database.php
config/cache.php
config/app-config.php

# 入口文件
public/index.php
bin/console

# 脚本文件 - 使用 kebab-case
scripts/create-admin-user.php
scripts/migrate-database.php

PSR-4 自动加载映射

json
{
    "autoload": {
        "psr-4": {
            "MyCompany\\": "src/",
            "MyCompany\\Domain\\": "src/Domain/",
            "MyCompany\\Tests\\": "tests/"
        }
    }
}

命名空间前缀 MyCompany\ 映射到 src/ 目录,嵌套的命名空间对应子目录:

php
<?php
declare(strict_types=1);

// 文件:src/Domain/User/Service/RegistrationService.php
namespace MyCompany\Domain\User\Service;

class RegistrationService
{
    // ...
}

数据库与配置命名

数据库相关命名

php
<?php
declare(strict_types=1);

// 数据库表名:snake_case 复数形式
// users, order_items, product_categories, user_roles

// 数据库列名:snake_case
// created_at, updated_at, first_name, last_name, is_active

// 外键列:{关联表单数}_id
// user_id, order_id, category_id

// 数据库索引命名
// idx_{表名}_{列名}
// idx_users_email, idx_orders_created_at

// 唯一索引命名
// unq_{表名}_{列名}
// unq_users_email

// 外键命名
// fk_{表名}_{关联表}_{列名}
// fk_orders_users_user_id

// 多列索引
// idx_{表名}_{列1}_{列2}
// idx_order_items_order_id_product_id

// 迁移文件名
// {timestamp}_create_{table}_table.php
// 2024_01_15_000000_create_users_table.php
// 2024_01_15_000001_add_index_users_email.php

环境变量命名

bash
# 环境变量使用 UPPER_SNAKE_CASE,添加项目前缀避免冲突
APP_NAME="MyApplication"
APP_ENV="production"
APP_DEBUG=false
APP_URL="https://example.com"
APP_KEY="base64:..."

# 数据库配置
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=myapp
DB_USERNAME=root
DB_PASSWORD=secret

# 缓存配置
CACHE_DRIVER=redis
CACHE_PREFIX=myapp_cache_
REDIS_HOST=127.0.0.1
REDIS_PORT=6379

# 队列配置
QUEUE_CONNECTION=redis
QUEUE_NAME=default

# 邮件配置
MAIL_MAILER=smtp
MAIL_HOST=smtp.example.com
MAIL_PORT=587
MAIL_ENCRYPTION=tls
MAIL_USERNAME=null
MAIL_PASSWORD=null

前端模板与路由命名

路由命名

php
<?php
declare(strict_types=1);

// 路由名使用 kebab-case,点号分隔层级
// users.index, users.create, users.store
// users.show, users.edit, users.update, users.destroy
// api.v1.users.index
// admin.dashboard.settings

// 资源路由
Route::resource('users', UserController::class)->names([
    'index' => 'users.index',
    'create' => 'users.create',
    'store' => 'users.store',
    'show' => 'users.show',
    'edit' => 'users.edit',
    'update' => 'users.update',
    'destroy' => 'users.destroy',
]);

// API 路由
Route::prefix('api/v1')->group(function (): void {
    Route::get('users', [UserController::class, 'index'])->name('api.v1.users.index');
    Route::post('users', [UserController::class, 'store'])->name('api.v1.users.store');
});

视图模板文件命名

text
# Blade 模板(Laravel)
# 使用 kebab-case
resources/views/users/index.blade.php
resources/views/users/create.blade.php
resources/views/users/partials/user-card.blade.php
resources/views/layouts/app-layout.blade.php
resources/views/components/dropdown-menu.blade.php

# 目录使用 kebab-case 复数
resources/views/emails/
resources/views/notifications/
resources/views/vendor/

注释与文档命名

PHPDoc 注释中的命名

php
<?php
declare(strict_types=1);

/**
 * 用户服务类
 *
 * 负责用户相关的业务逻辑处理,包括注册、认证、权限管理。
 *
 * @see \MyCompany\Domain\User\User
 * @see \MyCompany\Domain\User\UserRepository
 */
class UserService
{
    /**
     * 根据邮箱查找用户
     *
     * @param string $email 用户邮箱地址
     * @return User|null 找到返回用户对象,否则返回 null
     * @throws \InvalidArgumentException 当邮箱格式不合法时
     */
    public function findByEmail(string $email): ?User
    {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new \InvalidArgumentException("Invalid email: {$email}");
        }

        return $this->userRepository->findOneByEmail($email);
    }

    /**
     * 注册新用户
     *
     * @param CreateUserDto $dto 用户注册数据
     * @return User 新创建的用户实体
     * @throws \MyCompany\Domain\User\Exception\UserAlreadyExistsException 当用户已存在时
     */
    public function register(CreateUserDto $dto): User
    {
        // ...
    }
}

实战示例

完整项目命名示例

php
<?php
declare(strict_types=1);

namespace MyCompany\Application\Command\User;

use MyCompany\Domain\User\User;
use MyCompany\Domain\User\UserRepositoryInterface;
use MyCompany\Domain\User\Exception\UserAlreadyExistsException;

/**
 * 创建用户命令处理器
 */
final class CreateUserHandler
{
    public function __construct(
        private readonly UserRepositoryInterface $userRepository,
    ) {}

    /**
     * 处理用户创建命令
     *
     * @param CreateUserCommand $command 创建用户命令
     * @return User 新创建的用户
     * @throws UserAlreadyExistsException
     */
    public function handle(CreateUserCommand $command): User
    {
        $existingUser = $this->userRepository->findOneByEmail($command->email);

        if ($existingUser !== null) {
            throw new UserAlreadyExistsException(
                message: "User with email {$command->email} already exists",
                code: 409,
            );
        }

        $user = new User(
            firstName: $command->firstName,
            lastName: $command->lastName,
            email: $command->email,
            password: $this->hashPassword($command->password),
        );

        $this->userRepository->save($user);

        return $user;
    }

    private function hashPassword(string $plainPassword): string
    {
        return password_hash($plainPassword, PASSWORD_BCRYPT, [
            'cost' => 12,
        ]);
    }
}

对应的目录结构

text
src/
├── Application/
│   └── Command/
│       └── User/
│           ├── CreateUserCommand.php      # 命令对象
│           └── CreateUserHandler.php      # 命令处理器
├── Domain/
│   └── User/
│       ├── User.php                       # 用户实体
│       ├── UserRepositoryInterface.php    # 仓储接口
│       └── Exception/
│           └── UserAlreadyExistsException.php
└── Infrastructure/
    └── Persistence/
        └── Doctrine/
            └── UserRepository.php          # 仓储实现

命名一致性检查工具

php
<?php
declare(strict_types=1);

namespace MyCompany\Tools;

use ReflectionClass;
use ReflectionMethod;
use RuntimeException;

/**
 * 命名规范检查工具
 *
 * 用于检查项目中各类标识符是否符合命名规范
 */
class NamingConventionChecker
{
    private array $violations = [];

    /**
     * 检查类名是否符合 PascalCase
     */
    public function checkClassName(ReflectionClass $class): bool
    {
        $className = $class->getShortName();

        if (!preg_match('/^[A-Z][a-zA-Z0-9]+$/', $className)) {
            $this->addViolation(
                "Class '{$className}' does not follow PascalCase convention",
                $class->getFileName(),
            );
            return false;
        }

        return true;
    }

    /**
     * 检查方法名是否符合 camelCase
     */
    public function checkMethodName(ReflectionMethod $method): bool
    {
        // 允许魔术方法
        $magicMethods = [
            '__construct', '__destruct', '__call', '__callStatic',
            '__get', '__set', '__isset', '__unset', '__sleep',
            '__wakeup', '__toString', '__invoke', '__set_state',
            '__clone', '__debugInfo', '__serialize', '__unserialize',
        ];

        if (in_array($method->getName(), $magicMethods, true)) {
            return true;
        }

        $methodName = $method->getName();

        if (!preg_match('/^[a-z][a-zA-Z0-9]+$/', $methodName)) {
            $this->addViolation(
                "Method '{$methodName}' does not follow camelCase convention",
                $method->getFileName(),
            );
            return false;
        }

        return true;
    }

    /**
     * 检查类常量是否符合 UPPER_SNAKE_CASE
     */
    public function checkClassConstants(ReflectionClass $class): bool
    {
        $isValid = true;

        foreach ($class->getReflectionConstants() as $constant) {
            $constName = $constant->getName();

            if (!preg_match('/^[A-Z][A-Z0-9_]+$/', $constName)) {
                $this->addViolation(
                    "Constant '{$constName}' in class '{$class->getShortName()}' "
                    . "does not follow UPPER_SNAKE_CASE convention",
                    $class->getFileName(),
                );
                $isValid = false;
            }
        }

        return $isValid;
    }

    private function addViolation(string $message, ?string $file): void
    {
        $this->violations[] = [
            'message' => $message,
            'file' => $file,
        ];
    }

    public function getViolations(): array
    {
        return $this->violations;
    }

    public function hasViolations(): bool
    {
        return !empty($this->violations);
    }
}

注意事项

常见命名错误

php
<?php
declare(strict_types=1);

// ❌ 避免缩写滥用
class UsrCtrl { }     // 不如 UserController
class ProdSvc { }     // 不如 ProductService

// ❌ 避免类型后缀冗余
class UserString { }  // 类型信息已在 PHPDoc 中
class UserArray { }

// ❌ 避免不必要的前缀/后缀
class IUserService { }    // I 前缀(C# 风格,不适用于 PHP)
class UserServiceImpl { }  // Impl 后缀(Java 风格)

// ❌ 避免单字母变量(除循环外)
$x = calculate();
$a = [];

团队协作建议

命名一致性

团队中命名规范的一致性比选择哪种规范更重要。如果团队已有约定俗成的命名风格,优先遵循团队约定,而非强制更换为新规范。

  1. 建立命名规范文档:将团队的命名约定写入项目文档,新成员入职时必须阅读
  2. 使用自动化检查:配置 PHP-CS-Fixer 和 PHP_CodeSniffer 强制执行命名规范
  3. Code Review 重点检查:在 Code Review 中将命名合理性作为检查项
  4. 重构时不惜改名:发现不合理的命名应立即重命名,现代 IDE 提供了安全的重命名重构功能

最佳实践

命名规范速查表

标识符类型命名风格示例
类名PascalCaseUserService, OrderRepository
接口名PascalCaseRenderable, UserRepositoryInterface
Trait 名PascalCaseHasTimestamps, SoftDeletes
枚举类型名PascalCaseStatus, UserRole
枚举 CasePascalCaseStatus::Active, UserRole::Admin
方法名camelCasefindById(), isActive()
函数名camelCaseformatCurrency(), isValidEmail()
变量名camelCase$userName, $totalAmount
布尔变量is/has/can + camelCase$isEnabled, $hasPermission
常量名UPPER_SNAKE_CASEMAX_RETRY_COUNT, API_VERSION
命名空间PascalCaseMyCompany\Domain\User
数据库表名snake_case 复数users, order_items
数据库列名snake_casecreated_at, first_name
环境变量UPPER_SNAKE_CASEDB_HOST, APP_ENV
配置文件kebab-caseapp-config.php, cache-driver.php

下一节

继续学习:代码风格工具

参考链接