Skip to content

原文: PER Coding Style翻译: Webman Design Guide 项目组 许可证: 本翻译基于 PER Coding Style (MIT License) 创作


目录

  1. 概述
  2. 通用规则
  3. 文件
  4. 缩进
  5. 关键字和类型
  6. 声明、命名空间和导入
  7. 类、属性和方法
  8. 控制结构
  9. 运算符
  10. 闭包
  11. 匿名类

1. 概述

1.1 什么是 PER

PER (PHP Evolving Recommendation) 是 PHP-FIG 推出的持续演进的编码规范,作为 PSR-12 的继任者。

关键特点:

  • 完全兼容 PSR-12
  • 支持 PHP 8.0+ 新特性
  • 持续更新以支持新的 PHP 语言特性
  • 社区驱动和维护

1.2 规范目标

本规范的目标是减少不同开发者在阅读代码时的认知摩擦。通过统一的代码风格规则,使代码更易于阅读和维护。


2. 通用规则

2.1 基础编码标准

代码必须遵循 PSR-1 的所有规则。

2.2 文件

  • 所有 PHP 文件必须使用 Unix LF (linefeed) 行尾
  • 所有 PHP 文件必须以一个空行结束
  • 纯 PHP 文件必须省略结束标签 ?>

2.3 行

  • 行长度不得有硬性限制
  • 行长度的软限制必须是 120 个字符
  • 行不应超过 80 个字符;超过的行应该被拆分成多个不超过 80 个字符的后续行

2.4 缩进

  • 代码必须使用 4 个空格的缩进
  • 不得使用制表符 (tab) 进行缩进

3. 文件

3.1 PHP 标签

PHP 代码必须使用长标签 <?php ?> 或短输出标签 <?= ?>;不得使用其他标签变体。

3.2 字符编码

PHP 代码必须只使用不带 BOM 的 UTF-8。

3.3 副作用

一个文件应该声明符号(类、函数、常量等)或者引起副作用(例如:生成输出、修改 .ini 设置等),但不应该两者都做。

不好的例子:

php
<?php
// 副作用:修改 ini 设置
ini_set('error_reporting', E_ALL);

// 副作用:加载文件
include "file.php";

// 副作用:生成输出
echo "<html>\n";

// 声明
function foo()
{
    // 函数体
}

好的例子:

php
<?php
// 只声明,没有副作用
function foo()
{
    // 函数体
}

// 条件声明不是副作用
if (!function_exists('bar')) {
    function bar()
    {
        // 函数体
    }
}

4. 行

4.1 行长度

  • 行长度不得有硬性限制
  • 行长度的软限制必须是 120 个字符
  • 行不应超过 80 个字符

4.2 空行

  • namespace 声明后必须有一个空行
  • use 声明块后必须有一个空行
  • 在类的开始大括号后不得有空行
  • 在类的结束大括号前不得有空行
  • 在方法的开始大括号后不得有空行
  • 在方法的结束大括号前不得有空行

5. 缩进

5.1 基本缩进

代码必须使用 4 个空格的缩进,不得使用制表符。

php
<?php

namespace Vendor\Package;

class ClassName
{
    public function fooBarBaz($arg1, &$arg2, $arg3 = [])
    {
        // 方法体使用 4 个空格缩进
        if ($arg1 === $arg2) {
            // if 体使用 8 个空格缩进
            return true;
        }

        return false;
    }
}

6. 关键字和类型

6.1 关键字

所有 PHP 保留关键字和类型必须使用小写。

php
<?php

// 正确
public function foo(): void
{
    // ...
}

// 错误
public function foo(): Void
{
    // ...
}

6.2 类型关键字

所有 PHP 类型关键字必须使用小写形式。

标量类型:

  • bool
  • int
  • float
  • string

复合类型:

  • array
  • object
  • callable
  • iterable

特殊类型:

  • mixed
  • void
  • null
  • true
  • false

6.3 类型声明

类型声明必须在类型关键字后有一个空格。

php
<?php

// 正确
function foo(int $a, string $b): bool
{
    return true;
}

// 错误
function foo(int$a, string$b):bool
{
    return true;
}

6.4 联合类型 (PHP 8.0+)

联合类型声明中,管道符 | 前后不得有空格。

php
<?php

// 正确
function foo(int|float $number): int|float
{
    return $number * 2;
}

// 错误
function foo(int | float $number): int | float
{
    return $number * 2;
}

6.5 交叉类型 (PHP 8.1+)

交叉类型声明中,与符号 & 前后不得有空格。

php
<?php

// 正确
function foo(Countable&Traversable $data): void
{
    // ...
}

// 错误
function foo(Countable & Traversable $data): void
{
    // ...
}

6.6 可空类型

可空类型声明中,问号 ? 和类型之间不得有空格。

php
<?php

// 正确
function foo(?string $name): ?int
{
    return null;
}

// 错误
function foo(? string $name): ? int
{
    return null;
}

7. 声明、命名空间和导入

7.1 声明语句

declare 语句必须不包含空格,格式为 declare(strict_types=1);

php
<?php

declare(strict_types=1);

namespace Vendor\Package;

7.2 命名空间声明

  • 命名空间声明后必须有一个空行
  • 当存在时,所有 use 声明必须在 namespace 声明之后
  • 每个声明必须使用一个 use 关键字
  • use 声明块后必须有一个空行
php
<?php

declare(strict_types=1);

namespace Vendor\Package;

use FooClass;
use BarClass as Bar;
use OtherVendor\OtherPackage\BazClass;

// 这里必须有一个空行
class ClassName
{
    // ...
}

7.3 导入语句分组

导入语句可以分组,每组之间用空行分隔。

php
<?php

namespace Vendor\Package;

use Vendor\Package\{ClassA as A, ClassB, ClassC as C};
use Vendor\Package\SomeNamespace\ClassD as D;
use Vendor\Package\AnotherNamespace\ClassE as E;

use function Vendor\Package\{functionA, functionB, functionC};
use function Another\Vendor\functionD;

use const Vendor\Package\{CONSTANT_A, CONSTANT_B, CONSTANT_C};
use const Another\Vendor\CONSTANT_D;

class ClassName
{
    // ...
}

8. 类、属性和方法

8.1 扩展和实现

extendsimplements 关键字必须与类名在同一行。

php
<?php

namespace Vendor\Package;

use FooClass;
use BarClass as Bar;
use OtherVendor\OtherPackage\BazClass;

class ClassName extends ParentClass implements \ArrayAccess, \Countable
{
    // 类内容
}

如果实现的接口列表很长,可以拆分成多行,每个接口占一行。

php
<?php

namespace Vendor\Package;

class ClassName extends ParentClass implements
    \ArrayAccess,
    \Countable,
    \Serializable
{
    // 类内容
}

8.2 使用 Traits

use 关键字用于导入 traits 必须在开始大括号后的下一行声明。

php
<?php

namespace Vendor\Package;

class ClassName
{
    use FirstTrait;
    use SecondTrait;
    use ThirdTrait;
}

多个 traits 可以在一行声明。

php
<?php

namespace Vendor\Package;

class ClassName
{
    use FirstTrait, SecondTrait, ThirdTrait;
}

Trait 冲突解决和别名。

php
<?php

namespace Vendor\Package;

class ClassName
{
    use FirstTrait;
    use SecondTrait;
    use ThirdTrait {
        ThirdTrait::bigTalk insteadof SecondTrait;
        SecondTrait::bigTalk as talk;
    }
}

8.3 属性和常量

可见性

所有属性必须声明可见性。

php
<?php

class ClassName
{
    public string $foo;
    protected int $bar;
    private array $baz;
}

类型声明

属性应该声明类型。

php
<?php

class ClassName
{
    public string $name;
    public int $age;
    public ?string $email = null;
}

只读属性 (PHP 8.1+)

php
<?php

class User
{
    public function __construct(
        public readonly string $name,
        public readonly int $age,
    ) {
    }
}

常量

类常量必须声明可见性。

php
<?php

class ClassName
{
    public const VERSION = '1.0.0';
    protected const API_KEY = 'secret';
    private const INTERNAL = 'internal';
}

8.4 方法和函数

可见性

所有方法必须声明可见性。

php
<?php

class ClassName
{
    public function foo(): void
    {
        // ...
    }

    protected function bar(): int
    {
        return 1;
    }

    private function baz(): string
    {
        return 'baz';
    }
}

方法参数

参数列表中,每个逗号前不得有空格,每个逗号后必须有一个空格。

php
<?php

class ClassName
{
    public function foo(int $arg1, string $arg2, array $arg3 = []): void
    {
        // 方法体
    }
}

参数列表拆分

参数列表可以拆分成多行,每个参数占一行。

php
<?php

class ClassName
{
    public function aVeryLongMethodName(
        ClassTypeHint $arg1,
        &$arg2,
        array $arg3 = []
    ): void {
        // 方法体
    }
}

构造器属性提升 (PHP 8.0+)

php
<?php

class Point
{
    public function __construct(
        public float $x = 0.0,
        public float $y = 0.0,
        public float $z = 0.0,
    ) {
    }
}

返回类型

返回类型声明中,冒号后必须有一个空格。

php
<?php

function foo(): string
{
    return 'foo';
}

class ClassName
{
    public function bar(): int
    {
        return 1;
    }
}

引用返回

php
<?php

function &getReference(): array
{
    return $this->data;
}

9. 控制结构

9.1 通用规则

控制结构的通用规则:

  • 控制结构关键字后必须有一个空格
  • 开始括号后不得有空格
  • 结束括号前不得有空格
  • 结束括号和开始大括号之间必须有一个空格
  • 结构体必须缩进一次
  • 结束大括号必须在结构体后的下一行

9.2 if, elseif, else

php
<?php

if ($expr1) {
    // if body
} elseif ($expr2) {
    // elseif body
} else {
    // else body
}

应该使用 elseif 而不是 else if,这样所有控制关键字看起来像单个词。

括号中的表达式可以拆分成多行。

php
<?php

if (
    $expr1
    && $expr2
) {
    // if body
} elseif (
    $expr3
    && $expr4
) {
    // elseif body
}

9.3 switch, case

php
<?php

switch ($expr) {
    case 0:
        echo 'First case, with a break';
        break;
    case 1:
        echo 'Second case, which falls through';
        // no break
    case 2:
    case 3:
    case 4:
        echo 'Third case, return instead of break';
        return;
    default:
        echo 'Default case';
        break;
}

括号中的表达式可以拆分成多行。

php
<?php

switch (
    $expr1
    && $expr2
) {
    // 结构体
}

9.4 match (PHP 8.0+)

php
<?php

$result = match ($expr) {
    0 => 'First case',
    1 => 'Second case',
    2, 3, 4 => 'Multiple cases',
    default => 'Default case',
};

多行 match 表达式。

php
<?php

$result = match ($expr) {
    0 => 'First case',
    1 => 'Second case',
    default => 'Default case',
};

9.5 while, do while

php
<?php

while ($expr) {
    // 结构体
}
php
<?php

do {
    // 结构体
} while ($expr);

括号中的表达式可以拆分成多行。

php
<?php

while (
    $expr1
    && $expr2
) {
    // 结构体
}

do {
    // 结构体
} while (
    $expr1
    && $expr2
);

9.6 for

php
<?php

for ($i = 0; $i < 10; $i++) {
    // for 体
}

for 语句中的表达式可以拆分成多行。

php
<?php

for (
    $i = 0;
    $i < 10;
    $i++
) {
    // for 体
}

9.7 foreach

php
<?php

foreach ($iterable as $key => $value) {
    // foreach 体
}

9.8 try, catch, finally

php
<?php

try {
    // try 体
} catch (FirstThrowableType $e) {
    // catch 体
} catch (OtherThrowableType | AnotherThrowableType $e) {
    // catch 体
} finally {
    // finally 体
}

10. 运算符

10.1 一元运算符

一元运算符和操作数之间不得有空格。

php
<?php

$i++;
++$j;
!$value;
~$bits;

10.2 二元运算符

所有二元算术、比较、赋值、位运算、逻辑、字符串和类型运算符前后必须至少有一个空格。

php
<?php

// 算术运算符
$result = $a + $b;
$result = $a - $b;
$result = $a * $b;
$result = $a / $b;
$result = $a % $b;
$result = $a ** $b;

// 比较运算符
if ($a == $b) { }
if ($a === $b) { }
if ($a != $b) { }
if ($a !== $b) { }
if ($a < $b) { }
if ($a > $b) { }
if ($a <= $b) { }
if ($a >= $b) { }
if ($a <=> $b) { }

// 赋值运算符
$a = $b;
$a += $b;
$a -= $b;
$a *= $b;
$a /= $b;

// 位运算符
$result = $a & $b;
$result = $a | $b;
$result = $a ^ $b;
$result = $a << $b;
$result = $a >> $b;

// 逻辑运算符
if ($a && $b) { }
if ($a || $b) { }
if ($a and $b) { }
if ($a or $b) { }
if ($a xor $b) { }

// 字符串运算符
$result = $a . $b;
$result .= $b;

// 类型运算符
if ($a instanceof $b) { }

// Null 合并运算符
$result = $a ?? $b;
$result ??= $b;

10.3 三元运算符

php
<?php

$result = $condition ? $ifTrue : $ifFalse;

三元运算符可以拆分成多行。

php
<?php

$result = $condition
    ? $ifTrue
    : $ifFalse;

嵌套三元运算符必须使用括号。

php
<?php

$result = $condition1
    ? ($condition2 ? $value1 : $value2)
    : $value3;

11. 闭包

11.1 基本格式

闭包必须在 function 关键字后有一个空格,在 use 关键字前后各有一个空格。

php
<?php

$closureWithArgs = function ($arg1, $arg2) {
    // 体
};

$closureWithArgsAndVars = function ($arg1, $arg2) use ($var1, $var2) {
    // 体
};

$closureWithArgsVarsAndReturn = function ($arg1, $arg2) use ($var1, $var2): bool {
    // 体
};

11.2 参数和变量列表拆分

参数列表和变量列表可以拆分成多行。

php
<?php

$longArgs_noVars = function (
    $longArgument,
    $longerArgument,
    $muchLongerArgument
) {
    // 体
};

$noArgs_longVars = function () use (
    $longVar1,
    $longerVar2,
    $muchLongerVar3
) {
    // 体
};

$longArgs_longVars = function (
    $longArgument,
    $longerArgument,
    $muchLongerArgument
) use (
    $longVar1,
    $longerVar2,
    $muchLongerVar3
) {
    // 体
};

11.3 箭头函数 (PHP 7.4+)

php
<?php

$arrow = fn($arg1, $arg2): int => $arg1 + $arg2;

箭头函数可以拆分成多行。

php
<?php

$arrow = fn($arg1, $arg2): int =>
    $arg1 + $arg2 + $arg3;

12. 匿名类

12.1 基本格式

匿名类必须遵循与类和闭包相同的准则和原则。

php
<?php

$instance = new class {
    // 类内容
};

12.2 实现接口

php
<?php

$instance = new class extends \Foo implements \HandleableInterface {
    // 类内容
};

接口列表可以拆分成多行。

php
<?php

$instance = new class extends \Foo implements
    \ArrayAccess,
    \Countable,
    \Serializable
{
    // 类内容
};

12.3 构造函数参数

php
<?php

$instance = new class($arg1, $arg2) extends \Foo {
    // 类内容
};

构造函数参数可以拆分成多行。

php
<?php

$instance = new class(
    $arg1,
    $arg2,
    $arg3
) extends \Foo implements
    \ArrayAccess,
    \Countable
{
    // 类内容
};

完整代码示例

示例 1: 完整的类定义

php
<?php

declare(strict_types=1);

namespace Vendor\Package;

use Vendor\Package\{ClassA as A, ClassB, ClassC as C};
use Vendor\Package\SomeNamespace\ClassD as D;

use function Vendor\Package\{functionA, functionB, functionC};

use const Vendor\Package\{CONSTANT_A, CONSTANT_B, CONSTANT_C};

/**
 * 示例类
 */
class ClassName extends ParentClass implements
    \ArrayAccess,
    \Countable,
    \Serializable
{
    use FirstTrait;
    use SecondTrait;
    use ThirdTrait {
        ThirdTrait::bigTalk insteadof SecondTrait;
        SecondTrait::bigTalk as talk;
    }

    // 常量
    public const VERSION = '1.0.0';
    protected const API_KEY = 'secret';
    private const INTERNAL = 'internal';

    // 属性
    public string $publicProperty;
    protected int $protectedProperty;
    private array $privateProperty;

    /**
     * 构造函数
     */
    public function __construct(
        private string $name,
        private int $age,
        private readonly string $email,
    ) {
    }

    /**
     * 示例方法
     */
    public function sampleMethod(int $a, int $b = null): array
    {
        if ($a === $b) {
            bar();
        } elseif ($a > $b) {
            $foo->bar($arg1);
        } else {
            BazClass::bar($arg2, $arg3);
        }

        return [
            'result' => $a + $b,
        ];
    }

    /**
     * 静态方法
     */
    final public static function bar(): void
    {
        // 方法体
    }
}

示例 2: 枚举 (PHP 8.1+)

php
<?php

declare(strict_types=1);

namespace App\Domain\Order;

enum OrderStatus: string
{
    case PENDING = 'pending';
    case PAID = 'paid';
    case PROCESSING = 'processing';
    case SHIPPED = 'shipped';
    case DELIVERED = 'delivered';
    case CANCELLED = 'cancelled';

    public function label(): string
    {
        return match ($this) {
            self::PENDING => '待支付',
            self::PAID => '已支付',
            self::PROCESSING => '处理中',
            self::SHIPPED => '已发货',
            self::DELIVERED => '已送达',
            self::CANCELLED => '已取消',
        };
    }

    public function canBeCancelled(): bool
    {
        return match ($this) {
            self::PENDING, self::PAID => true,
            default => false,
        };
    }
}

相关资源


注意: 本文档是 PER Coding Style 的中文翻译和解读,旨在帮助中文开发者理解和应用该规范。完整的官方规范请参考 PER Coding Style 官方文档