Skip to content

PSR-1 编码标准

PSR-1(PHP Standard Recommendation 1)是 PHP-FIG(PHP Framework Interop Group)制定的最基础的编码标准,定义了 PHP 源代码的基本规范。遵循 PSR-1 能确保不同项目、不同团队之间的代码具有一致的基本结构,提高代码的可读性和可维护性。本节将详细介绍 PSR-1 的每条规则,并提供正确与错误的代码示例对比。

前置知识

阅读本节前,建议先了解:

基础概念

什么是 PHP-FIG

PHP-FIG(PHP Framework Interop Group,PHP 框架互操作组织)是一个由多个知名 PHP 项目代表组成的联盟。它的目标是推动 PHP 生态系统的互操作性,通过制定和推广标准化接口(PSR)来减少不同项目之间的摩擦。

PSR-1 的定位

PSR-1 是最基础的编码规范,仅涵盖最核心、最不可争议的规则。更详细的格式规范由 PSR-12 提供。

PHP 编码规范层级:

PSR-1  → 基础编码标准(本节)
PSR-12 → 扩展编码样式(下一节)

详细说明

1. 文件编码

PHP 文件必须只使用 UTF-8 编码,不得包含 BOM(Byte Order Mark)。

php
<?php
// ✅ 正确:UTF-8 无 BOM
// 文件以 <?php 开头

// ❌ 错误:包含 BOM 头
// EF BB BF <?php ... (十六进制的 BOM 标记)

BOM 的影响

BOM 头会在 PHP 输出前产生不可见的字节,导致 header() 函数失败、Session 启动失败、JSON 输出格式错误等问题。确保编辑器设置为 "UTF-8 without BOM"。

2. 副作用(Side Effects)

PHP 文件应该只做一件事:要么定义符号(类、函数、常量等),要么产生副作用(输出内容、修改 ini 配置等),不应两者兼有。

php
<?php
// ❌ 错误:副作用与符号定义混在一起
$color = 'blue';                             // 副作用
class Foo                                     // 符号定义
{
    public function bar(): void
    {
        // ...
    }
}
echo $color;                                  // 副作用

// ✅ 正确:仅定义符号
class Foo
{
    public function bar(): void
    {
        // ...
    }
}

// ✅ 正确:仅产生副作用
ini_set('display_errors', '1');
echo 'Hello World';

什么算"副作用"

以下操作被认为是副作用:

  • 生成输出(echo、print、?> 标签外的 HTML)
  • 直接使用 require/include(除非用于条件加载)
  • 修改 ini 配置(ini_set)
  • 发送 HTTP 头(header)
  • 操作文件/网络/数据库
  • 定义自动加载器(spl_autoload_register)

3. 命名空间与类名

命名空间和类名必须遵循 PSR-4 自动加载规范。

php
<?php
// ✅ 正确:遵循 PSR-4
namespace Vendor\Package;

class ClassName
{
    // ...
}

// ❌ 错误:不符合 PSR-4
namespace Vendor_Package;

class class_name
{
    // ...
}
  • 类名必须使用 StudlyCaps(首字母大写的驼峰命名法)
  • PHP 5.3+ 必须使用命名空间

4. 类常量

类常量必须全部大写,单词之间用下划线分隔。

php
<?php
declare(strict_types=1);

namespace Vendor\Package;

class ClassName
{
    // ✅ 正确
    public const VERSION = '1.0.0';
    public const MAX_RETRIES = 5;
    public const DEFAULT_TIMEOUT = 30;

    // ❌ 错误
    public const version = '1.0.0';
    public const MaxRetries = 5;
    public const default_timeout = 30;
}

PHP 8.1+ 的 readonly 常量

PHP 8.1 引入了 readonly 属性和 final 类常量。常量始终是公开且不可变的。

5. 属性命名

属性名必须使用 $camelCase(小写驼峰命名法)。

php
<?php
declare(strict_types=1);

namespace Vendor\Package;

class ClassName
{
    // ✅ 正确
    public string $firstName;
    public int $maxRetries;
    private bool $isEnabled;
    protected array $userList = [];

    // ❌ 错误
    public string $first_name;       // 下划线命名
    public string $FirstName;        // 首字母大写
    public string $FIRSTNAME;         // 全大写
    public int $MAX_RETRIES;          // 全大写
}

6. 方法名

方法名必须使用 camelCase()(小写驼峰命名法)。

php
<?php
declare(strict_types=1);

namespace Vendor\Package;

class ClassName
{
    // ✅ 正确
    public function getUserById(int $id): ?User
    {
        return null;
    }

    public function processPayment(float $amount): bool
    {
        return true;
    }

    private function validateInput(array $data): void
    {
        // ...
    }

    // ❌ 错误
    public function get_user_by_id(int $id): ?User { }   // 下划线命名
    public function GetUserId(int $id): ?User { }         // 首字母大写
    public function GET_USER(int $id): ?User { }          // 全大写
}

实战示例

场景一:PSR-1 完全合规的类文件

php
<?php
declare(strict_types=1);

namespace App\Services;

use App\Models\User;
use App\Repositories\UserRepository;
use Psr\Log\LoggerInterface;

class UserService
{
    public const MAX_LOGIN_ATTEMPTS = 5;
    public const SESSION_LIFETIME = 3600;

    private UserRepository $userRepository;
    private LoggerInterface $logger;
    private int $attemptCount = 0;

    public function __construct(
        UserRepository $userRepository,
        LoggerInterface $logger
    ) {
        $this->userRepository = $userRepository;
        $this->logger = $logger;
    }

    public function findUserById(int $id): ?User
    {
        return $this->userRepository->findById($id);
    }

    public function authenticate(string $email, string $password): bool
    {
        $user = $this->userRepository->findByEmail($email);

        if ($user === null || !$user->verifyPassword($password)) {
            $this->attemptCount++;
            $this->logger->warning('Login failed', [
                'email' => $email,
                'attempt' => $this->attemptCount,
            ]);

            return false;
        }

        $this->attemptCount = 0;
        return true;
    }

    private function validateEmail(string $email): void
    {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new \InvalidArgumentException('Invalid email address');
        }
    }
}

场景二:自动检测 PSR-1 违规

bash
# 使用 PHP_CodeSniffer 检测 PSR-1 违规
composer require --dev squizlabs/php_codesniffer

vendor/bin/phpcs --standard=PSR1 src/

# 输出示例:
# FILE: src/Services/UserService.php
# --------------------------------------------------------------------------------
# FOUND 2 ERRORS AFFECTING 2 LINES
# --------------------------------------------------------------------------------
#  15 | ERROR | [x] Constant MAX_LOGIN_ATTEMPTS must be uppercase; expected MAX_LOGIN_ATTEMPTS
#  25 | ERROR | [x] Method name "get_user" is not in camelCase format
# --------------------------------------------------------------------------------
bash
# 自动修复(仅部分可修复)
vendor/bin/phpcbf --standard=PSR1 src/

场景三:PSR-1 常见违规与修正

php
<?php
// 常见违规 #1:文件中混合副作用和类定义
$cache = new Redis();
class CacheService { }

// 修正:拆分为两个文件
// file1.php
$cache = new Redis();

// file2.php
class CacheService { }
php
<?php
// 常见违规 #2:类名使用下划线
class user_service
{
    public function get_user_by_id($id) { }
}

// 修正:遵循 StudlyCaps 和 camelCase
class UserService
{
    public function getUserById(int $id): ?User { }
}
php
<?php
// 常见违规 #3:类常量使用小写
class Config
{
    const db_host = 'localhost';
    const db_name = 'myapp';
}

// 修正:类常量全部大写
class Config
{
    public const DB_HOST = 'localhost';
    public const DB_NAME = 'myapp';
}

场景四:PSR-1 速查表

┌──────────────────────┬──────────────────────────┐
│ 规则                 │ 要求                      │
├──────────────────────┼──────────────────────────┤
│ 文件编码              │ UTF-8 无 BOM              │
│ 副作用               │ 仅定义符号 或 仅产生副作用  │
│ 类名                 │ StudlyCaps               │
│ 类常量               │ UPPER_SNAKE_CASE         │
│ 属性名               │ camelCase                 │
│ 方法名               │ camelCase()               │
│ 命名空间             │ 遵循 PSR-4                │
│ PHP 关键字           │ 全部小写                  │
│ PHP 内置类型         │ 全部小写                  │
│ PHP 8.1+             │ 支持 readonly/enum        │
└──────────────────────┴──────────────────────────┘

场景五:团队编码规范检查脚本

json
{
    "scripts": {
        "cs:check": "phpcs --standard=PSR1 --colors --report=full src/",
        "cs:fix": "phpcbf --standard=PSR1 src/",
        "cs:check-psr12": "phpcs --standard=PSR12 --colors --report=full src/"
    }
}

注意事项

1. PSR-1 是最低标准

PSR-1 仅规定了最基础的规则,实际项目中通常使用 PSR-12(或更严格的框架规范)作为代码风格标准。PSR-1 是所有 PHP 开发者都应该遵守的底线。

2. 工具自动化

手动检查 PSR-1 合规性既耗时又容易遗漏,推荐使用自动化工具:

bash
# PHP_CodeSniffer(最常用)
vendor/bin/phpcs --standard=PSR1 src/

# PHP-CS-Fixer
vendor/bin/php-cs-fixer fix --rules=@PSR1 src/

# PHPStan(静态分析)
vendor/bin/phpstan analyse --level=1 src/

3. 与 PSR-12 的关系

规范范围说明
PSR-1基础编码标准最小公约数,所有 PHP 项目都应遵循
PSR-12扩展编码样式基于PSR-1,增加了格式化、缩进等详细规则

实际开发建议

直接使用 PSR-12 作为代码风格标准,因为它包含 PSR-1 的所有规则并增加了更详细的格式规范。

最佳实践

1. 在 CI/CD 中强制检查

yaml
# .github/workflows/ci.yml
- name: Check PSR-1
  run: vendor/bin/phpcs --standard=PSR1 src/

2. IDE 自动格式化

配置 IDE 使用 PSR-12 格式化规则(自动包含 PSR-1):

  • VS Code:安装 PHP-CS-FixerIntelephense 扩展
  • PhpStorm:Settings → Editor → Code Style → PHP → Set from... → PSR-12

3. pre-commit 钩子

bash
# .git/hooks/pre-commit
#!/bin/bash
vendor/bin/phpcs --standard=PSR1 src/
if [ $? -ne 0 ]; then
    echo "PSR-1 检查失败,请修复后再提交"
    exit 1
fi

下一节

继续学习:PSR-12 扩展编码样式 — 了解更详细的 PHP 编码格式规范,包括缩进、空白行、大括号位置等。

参考链接