PHP 零基础教程

PHP 编程规范:PSR-1 与 PSR-12

PHP 标准推荐规范(PHP Standard Recommendations,简称 PSRs)是由 PHP 框架互操作性小组(PHP-FIG)制定的一套指南和规范。这些建议旨在标准化 PHP 编码的各个方面,使代码在不同的项目和开发者之间更加一致、兼容且易于理解。

遵循 PSR 规范可以帮助你在切换项目时减少认知负担,提高代码质量,并极大地方便团队协作。

1. 理解 PSR-1:基础编码标准

PSR-1 规定了 PHP 代码的基础要求,重点关注文件结构、命名空间声明、类名、方法名以及可见性等基础内容。它为所有 PHP 代码提供了一个通用的底座。

1.1 PHP 标签

PSR-1 强制要求使用 <?php<?= 标签来编写 PHP 代码。完整的 <?php 标签用于包含逻辑和多行代码的区块,而 <?= 则是短输出标签,专门用于直接输出变量的值。这保证了一致性,并避免了与 XML 处理指令或其他可能在某些服务器环境中被禁用的短标签配置发生冲突。

<?php
// 这是一个完整的 PHP 标签,用于编写逻辑和多行代码
$name = "Alice";
echo "你好," . $name . "!";

// 这是一个短输出标签,专门用于输出值
?>
<p>欢迎,<?= $name ?>!</p>

1.2 无 BOM 的 UTF-8 编码

所有的 PHP 文件都必须使用无 BOM(字节顺序标记)的 UTF-8 编码。BOM 是文件开头的一个不可见字符,它经常会引起问题,特别是在引入文件或发送 HTTP 头信息时,会导致意外的输出或错误。使用无 BOM 的 UTF-8 编码可以确保最大的兼容性并避免这些与编码相关的问题。

1.3 副作用 (Side Effects)

一个文件应该要么声明符号(类、函数、常量),要么产生副作用(例如:生成输出、修改全局 INI 设置、执行读写操作),但绝不能同时包含两者

这种分离使得代码更容易测试、理解和调试,因为它明确了文件的主要用途。在这里,“副作用”指的是任何在预期声明范围之外影响程序状态的操作。

<?php
// 良好 (GOOD):这个文件只声明了一个类。没有副作用。
namespace App\Model;

class User
{
    public function __construct(private string $name) {}

    public function getName(): string
    {
        return $this->name;
    }
}
<?php
// 糟糕 (BAD):这个文件既声明了函数,又产生了副作用(输出了文本)。
namespace App\Helper;

function greet(string $name): string
{
    return "你好," . $name . "!";
}

echo greet("Bob"); // 副作用:直接输出

1.4 命名空间和类

PSR-1 要求类必须定义在独立的文件中,并使用完全限定的命名空间。命名空间声明必须是紧跟在 <?php 标签之后的第一个语句。

类名必须使用 StudlyCaps(也称为 PascalCase,大驼峰命名法)声明,即类名中每个单词的首字母都要大写,不能包含下划线或连字符。

<?php
// 文件路径: src/App/Model/Product.php
namespace App\Model;

class Product
{
    private string $name;
    private float $price;

    public function __construct(string $name, float $price)
    {
        $this->name = $name;
        $this->price = $price;
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function getPrice(): float
    {
        return $this->price;
    }
}

在这里,App\Model 是命名空间,而 Product 是类名,遵循了 StudlyCaps 规范。

1.5 类的常量、属性和方法

  • 类常量:必须全部使用大写字母,单词之间用下划线分隔(UPPER_SNAKE_CASE)。
  • 属性(类中的变量):通常建议使用 camelCase(小驼峰命名法)。
  • 方法(类中的函数):必须使用 camelCase(小驼峰命名法)声明,即第一个单词首字母小写,后续单词首字母大写。
<?php
// 文件路径: src/App/Utility/Calculator.php
namespace App\Utility;

class Calculator
{
    public const DEFAULT_PRECISION = 2; // 常量:全部大写加下划线
    private float $result; // 属性:小驼峰
    
    public function __construct(float $initialValue = 0.0) // 方法:小驼峰
    {
        $this->result = $initialValue;
    }
    
    public function add(float $number): self // 方法:小驼峰
    {
        $this->result += $number;
        return $this;
    }
    
    public function getCurrentResult(): float // 方法:小驼峰
    {
        return round($this->result, self::DEFAULT_PRECISION);
    }
}

2. 理解 PSR-12:扩展编码风格标准

PSR-12 建立在 PSR-2(它本身是 PSR-1 的扩展)的基础之上并取代了它,提供了一套全面的代码风格规则。它涵盖了缩进、行长度、控制结构语法、函数和方法声明等多个方面,这些细节共同构成了一个风格一致且易读的代码库。

2.1 基础元素

  • 文件:所有 PHP 文件必须以一个空行结尾。这可以防止在拼接文件时出现行不完整的问题,这也是许多编程语言的通用做法。
  • :非空行的末尾不能有尾随空格。这可以防止在版本控制系统中出现难以察觉的错误,并提高可读性。单行代码的最大长度应该是 120 个字符,理想的“软限制”是 80 个字符。如果超过 80 个字符,建议为了可读性进行换行。
<?php
// 一行代码
$veryLongVariableName = new SomeVeryLongClassName(
    $parameterOne,
    $parameterTwo,
    $parameterThree
); // 为了保证在120个字符以内,这行代码被折成了多行以提高可读性。

// ... (文件末尾,需留空行)

2.2 缩进

PSR-12 明确规定使用 4 个空格进行缩进,绝对不能使用制表符 (Tab)。一致的缩进对代码的可读性至关重要,推荐使用空格是因为它们在不同的文本编辑器和环境中渲染出的宽度都是完全一样的,而 Tab 键的宽度可能会有所不同。

<?php
namespace App\Service;

class AuthService
{
    public function authenticate(string $username, string $password): bool
    {
        if (empty($username) || empty($password)) {
            return false;
        }
        
        // ... 身份验证逻辑
        return true;
    }
}

上面的代码中,每一层缩进都精确使用了 4 个空格。

2.3 类、方法和属性的定义

类和接口

类和接口的左花括号 { 必须放在单独的一行,右花括号 } 也必须在主体内容之后的单独一行extendsimplements 关键字应该与类名在同一行;如果太长,implements 也可以换行并缩进一次(4个空格)。当换行时,每个被实现的接口都应该占据单独的一行。

<?php
namespace App\Service;

use App\Model\User;
use App\Repository\UserRepositoryInterface;

class UserManagementService implements
    UserRepositoryInterface,
    OtherServiceInterface
{
    public function __construct(private UserRepositoryInterface $userRepository)
    {
    }

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

属性

所有的属性都必须声明可见性(publicprotectedprivate)。严禁使用 var 关键字。每条语句只能声明一个属性。属性的类型声明应该放在属性名之前。

<?php
class Product
{
    public string $name; // 带有类型声明的公共属性
    protected float $price; // 带有类型声明的受保护属性
    private int $stockQuantity; // 带有类型声明的私有属性
    // ...
}

方法

所有的方法都必须声明可见性。方法的左花括号 { 必须放在单独的一行。带有默认值的参数必须放在参数列表的末尾。返回类型声明应该放在参数列表右括号之后,用冒号和空格隔开(: )。

<?php
class Logger
{
    public function log(string $message, string $level = 'info'): void
    {
        // 记录指定级别的日志信息
        echo "[" . strtoupper($level) . "] " . $message . "\n";
    }

    private function formatMessage(string $rawMessage): string
    {
        return date('Y-m-d H:i:s') . " - " . $rawMessage;
    }
}

2.4 控制结构

if, elseif, else

if 关键字之后、左括号 ( 之前必须有一个空格。左花括号 { 必须与 if 条件语句在同一行,并用一个空格隔开。右花括号 } 必须在单独的一行。elseifelse 遵循相同的规则。else 关键字应该与上一段的右花括号在同一行,并用空格隔开。

<?php
if ($conditionA) {
    // 代码 A
} elseif ($conditionB) {
    // 代码 B
} else {
    // 代码 C
}

switch, case

switch 关键字和左括号 ( 之间有一个空格。左花括号 { 在同一行。case 语句相对 switch 缩进一次(4个空格)。case 块内的代码再缩进一次(共8个空格)。break 应该与它上面的代码保持相同的缩进。default 的缩进规则与 case 相同。

<?php
switch ($status) {
    case 'pending':
        echo "订单待处理。";
        break;
    case 'completed':
        echo "订单已完成。";
        // 明确允许贯穿 (Fall-through),但必须加上注释说明
        // no break
    case 'shipped':
        echo "订单已发货或已完成。";
        break;
    default:
        echo "未知状态。";
        break;
}

while, do-while, for, foreach

这几种循环结构在空格和括号位置上的规则,与 if 语句完全一致。

<?php
$i = 0;
while ($i < 5) {
    echo $i++ . "\n";
}

$items = ['apple', 'banana', 'cherry'];
foreach ($items as $item) {
    echo $item . "\n";
}

try-catch-finally

trycatchfinally 块的左花括号同样必须与对应的关键字在同一行,并用空格隔开。catchfinally 关键字应该与前一个代码块的右花括号在同一行。

<?php
try {
    // 可能会抛出异常的代码
    throw new \Exception("出错了!");
} catch (\Exception $e) {
    echo "捕获到异常:" . $e->getMessage() . "\n";
} finally {
    echo "这里的代码始终会执行。\n";
}

2.5 运算符

  • 二元运算符:像 +-======&&|| 这些运算符,两边必须各保留一个空格。这能在视觉上区分操作数,提高可读性。
  • 一元运算符:像 !++-- 这类运算符,运算符和操作数之间不能有空格
<?php
// 二元运算符
$sum = $a + $b;
$isEqual = ($x === $y);
$canProceed = $isValid && $hasPermissions;

// 一元运算符
$notTrue = !$isTrue;
$count++;

3. PSR 规范的实际应用

始终如一地应用 PSR 规范可以极大地简化大型项目的管理。想象一下,如果一个公司开发了多个 PHP 应用程序和组件库。如果没有统一的标准,开发者在不同项目间切换时就会面临不同的缩进风格、命名习惯和代码结构,这会导致他们浪费时间去适应,并增加犯错的几率。

通过采用 PSR-1 和 PSR-12,公司可以保证代码库的统一,显著降低认知负担,让新团队成员的入职培训变得顺畅无比。例如,你在任何符合规范的项目中看到 camelCase(小驼峰),你立刻就能反应过来这是一个方法或属性,而不需要去猜测。