命名规范
命名规范是代码可读性和可维护性的基石。统一的命名约定使团队成员能够快速理解代码意图,减少认知负担,并降低因命名不当而引入 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.phpPSR-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 = [];团队协作建议
命名一致性
团队中命名规范的一致性比选择哪种规范更重要。如果团队已有约定俗成的命名风格,优先遵循团队约定,而非强制更换为新规范。
- 建立命名规范文档:将团队的命名约定写入项目文档,新成员入职时必须阅读
- 使用自动化检查:配置 PHP-CS-Fixer 和 PHP_CodeSniffer 强制执行命名规范
- Code Review 重点检查:在 Code Review 中将命名合理性作为检查项
- 重构时不惜改名:发现不合理的命名应立即重命名,现代 IDE 提供了安全的重命名重构功能
最佳实践
命名规范速查表
| 标识符类型 | 命名风格 | 示例 |
|---|---|---|
| 类名 | PascalCase | UserService, OrderRepository |
| 接口名 | PascalCase | Renderable, UserRepositoryInterface |
| Trait 名 | PascalCase | HasTimestamps, SoftDeletes |
| 枚举类型名 | PascalCase | Status, UserRole |
| 枚举 Case | PascalCase | Status::Active, UserRole::Admin |
| 方法名 | camelCase | findById(), isActive() |
| 函数名 | camelCase | formatCurrency(), isValidEmail() |
| 变量名 | camelCase | $userName, $totalAmount |
| 布尔变量 | is/has/can + camelCase | $isEnabled, $hasPermission |
| 常量名 | UPPER_SNAKE_CASE | MAX_RETRY_COUNT, API_VERSION |
| 命名空间 | PascalCase | MyCompany\Domain\User |
| 数据库表名 | snake_case 复数 | users, order_items |
| 数据库列名 | snake_case | created_at, first_name |
| 环境变量 | UPPER_SNAKE_CASE | DB_HOST, APP_ENV |
| 配置文件 | kebab-case | app-config.php, cache-driver.php |
下一节
继续学习:代码风格工具