PSR-12 编码规范扩充

编码风格扩充指南

文章中的关键词 MUSTMUST NOTREQUIREDSHALLSHALL NOTSHOULD

SHOULD NOTRECOMMENDEDMAY ,和 OPTIONAL 都在 RFC 2119 中进行来解释。

摘要

此规范起到继承,扩展和替换 [PSR-2][] 的作用, 同时编码风格遵守 [PSR-1][] 这个基础编码标准。

和 [PSR-2][] 一样, 此规范的目的是减少不同人在阅读代码时认知冲突。 它通过列举一套如何格式化 PHP 代码的公共的规则和期望来实现这个目标。 PSR 力图提供一套方法,编码风格工具可以利用,项目可以遵守,开发人员可以方便的在不同的项目中使用。当各个的开发人员在进行多项目合作的时候,它可以帮助在这些项目中提供一套通用的指导。所以,本指南的价值不是规则本身,而是这些规则的共享。

[PSR-2][] 在 2012 年被接受,随后 PHP 经历了很多变化,影响了编码风格。同时 [PSR-2] 是 PHP 编码时候的基础功能,被广泛的采用。因此,PSR 力图通过一种更加现代的方式说明 PSR-2 的内容和新功能,并对 PSR-2 进行更正。

以前的语言版本

在整个文档中,任何说明都可以被忽略,如果它们不存在于你项目所支持的 PHP 版本中。

例如

此示例包含以下一些规则作为快速概述:

  1. <?php
  2. declare(strict_types=1);
  3. namespace Vendor\Package;
  4. use Vendor\Package\{ClassA as A, ClassB, ClassC as C};
  5. use Vendor\Package\SomeNamespace\ClassD as D;
  6. use function Vendor\Package\{functionA, functionB, functionC};
  7. use const Vendor\Package\{ConstantA, ConstantB, ConstantC};
  8. class Foo extends Bar implements FooInterface
  9. {
  10. public function sampleFunction(int $a, int $b = null): array
  11. {
  12. if ($a === $b) {
  13. bar();
  14. } elseif ($a > $b) {
  15. $foo->bar($arg1);
  16. } else {
  17. BazClass::bar($arg2, $arg3);
  18. }
  19. }
  20. final public static function bar()
  21. {
  22. // 方法内容
  23. }
  24. }

2. 总则

2.1 基本编码标准

代码必须遵循[PSR-1]中列出的所有规则。

PSR-1中的术语 ‘StudlyCaps’ 必须解释为 PascalCase (帕斯卡命名法:大驼峰式命名法),其中每个单词的第一个字母大写,包括第一个字母。

2.2 文件

所有 PHP 文件只能使用Unix LF (换行符) 结尾。

所有的 PHP 文件都必须以非空行结尾,以一个 LF 结尾。

在仅包含 PHP 代码的文件中,必须省略结尾的 ?> 标记。

2.3 代码行

行长度不得有硬限制。

行长度的软限制必须为120个字符。

行的长度不应超过80个字符;超过该长度的行应拆分为多个后续行,每个行的长度不应超过80个字符。

行尾不能有尾随空格。

可以添加空行以提高可读性并指示相关的代码块,除非明确禁止。

每行不能有多个语句。

2.4 缩进

代码必须为每个缩进级别使用4个空格的缩进,并且不能使用缩进标签。

2.5 关键词和类型

PHP 的所有关键字和类型 [1][2]必须使用小写。

PHP未来版本中新加的所有关键字和类型也都必须使用小写。

类型关键字必须使用缩写。使用 bool 而不是 boolean,使用 int 而不是 integer 等等。

3. 声明、命名空间以及导入

一个PHP文件的头部可能会包含多个块。如果包含多个块,则每个块都必须用空白行和其他块分隔,并且块内不能包含空白行。所有的块都必须按照下面的顺序排列,如果不存在该块则忽略。

  • PHP文件开始标签:
  • 文件级文档块。
  • 一个或多个声明语句。
  • 命名空间声明语句。
  • 一个或多个基于类的 use 声明语句。
  • 一个或多个基于方法的 use 声明语句。
  • 一个或多个基于常量的 use 声明语句。
  • 其余代码。

当文件包含HTML和PHP的混合代码时,可以使用上面列出的任何部分。如果是这种情况的话,即时代码的其他部分包含有PHP结束符,然后再包含HTML和PHP代码,声明、命名空间和导入语句块也必须放在文件的顶部。

什么时候开始 <?php 标签位于文件的第一行,它必须位于自己的行,没有其他语句,除非它是一个包含 PHP 之外的标记的文件打开和关闭标记。

import语句不能以前导反斜杠开头,因为它们必须始终完全合格。

以下示例演示了所有块的完整列表:

  1. <?php
  2. /**
  3. * This file contains an example of coding styles.
  4. */
  5. declare(strict_types=1);
  6. namespace Vendor\Package;
  7. use Vendor\Package\{ClassA as A, ClassB, ClassC as C};
  8. use Vendor\Package\SomeNamespace\ClassD as D;
  9. use Vendor\Package\AnotherNamespace\ClassE as E;
  10. use function Vendor\Package\{functionA, functionB, functionC};
  11. use function Another\Vendor\functionD;
  12. use const Vendor\Package\{CONSTANT_A, CONSTANT_B, CONSTANT_C};
  13. use const Another\Vendor\CONSTANT_D;
  14. /**
  15. * FooBar is an example class.
  16. */
  17. class FooBar
  18. {
  19. // ... 其他php代码 ...
  20. }

深度不能超过两层的复合名称空间,因此以下展示了允许的最大复合深度。

  1. <?php
  2. use Vendor\Package\SomeNamespace\{
  3. SubnamespaceOne\ClassA,
  4. SubnamespaceOne\ClassB,
  5. SubnamespaceTwo\ClassY,
  6. ClassZ,
  7. };

并且不允许以下内容:

  1. <?php
  2. use Vendor\Package\SomeNamespace\{
  3. SubnamespaceOne\AnotherNamespace\ClassA,
  4. SubnamespaceOne\ClassB,
  5. ClassZ,
  6. };

当希望在 PHP 外部包含标记的文件中声明严格类型时打开和关闭标签,声明必须写在文件的第一行并且包含在一个开始的 PHP 标签,以及严格的类型声明和结束标签。

例如:

  1. <?php declare(strict_types=1) ?>
  2. <html>
  3. <body>
  4. <?php
  5. // ... 其他 PHP 代码 ...
  6. ?>
  7. </body>
  8. </html>

声明语句不能包含空格,并且必须完全是 declare(strict_types=1) (带有可选的分号终止符)。

允许使用块声明语句,并且必须按照以下的格式设置。注意的位置括号和间距:

  1. declare(ticks=1) {
  2. // 一些代码
  3. }

4. 类,属性,和方法

这里的『类』指的是所有类,接口,以及 trait 。

任何注释和语句 不得 跟在其右花括号后的同一行。

当实例化一个类时,后面的圆括号 必须 写出来,即使没有参数传进其构造函数。

  1. new Foo();

4.1 继承和实现

关键字 继承实现 必须 在类名的同一行声明。

类的左花括号 必须 另起一行;右花括号 必须 跟在类主体的下一行。

类的左花括号 必须 独自成行,且 不得 在其上一行或下一行存在空行。

右花括号 必须 独自成行,且 不得 在其上一行存在空行。

  1. <?php
  2. namespace Vendor\Package;
  3. use FooClass;
  4. use BarClass as Bar;
  5. use OtherVendor\OtherPackage\BazClass;
  6. class ClassName extends ParentClass implements \ArrayAccess, \Countable
  7. {
  8. // 常量,属性,方法
  9. }

如果有接口, 实现 接口和 继承父类 可以 分为多行,前者每行需缩进一次。当这么做时,第一个接口 必须 写在下一行,且每行 必须 只能写一个接口。

  1. <?php
  2. namespace Vendor\Package;
  3. use FooClass;
  4. use BarClass as Bar;
  5. use OtherVendor\OtherPackage\BazClass;
  6. class ClassName extends ParentClass implements
  7. \ArrayAccess,
  8. \Countable,
  9. \Serializable
  10. {
  11. // 常量,属性,方法
  12. }

4.2 使用 trait

在类里面用于实现 trait 的关键字 use 必须 在左花括号的下一行声明。

  1. <?php
  2. namespace Vendor\Package;
  3. use Vendor\Package\FirstTrait;
  4. class ClassName
  5. {
  6. use FirstTrait;
  7. }

每个导入类的 trait 必须 每行一个包含声明,且每个包含声明 必须 有其 use 导入语句。

  1. <?php
  2. namespace Vendor\Package;
  3. use Vendor\Package\FirstTrait;
  4. use Vendor\Package\SecondTrait;
  5. use Vendor\Package\ThirdTrait;
  6. class ClassName
  7. {
  8. use FirstTrait;
  9. use SecondTrait;
  10. use ThirdTrait;
  11. }

在类文件中,如果在使用’use Trait’之后没有其他内容了 ,类名右大括号必须另起一行。

  1. <?php
  2. namespace Vendor\Package;
  3. use Vendor\Package\FirstTrait;
  4. class ClassName
  5. {
  6. use FirstTrait;
  7. }

如有其他内容,两者之间需空一行。

  1. <?php
  2. namespace Vendor\Package;
  3. use Vendor\Package\FirstTrait;
  4. class ClassName
  5. {
  6. use FirstTrait;
  7. private $property;
  8. }

当使用’ insteadof ‘和’ as ‘运算符时,它们必须如图所示使用,注意缩进、间距和另起一行。

  1. <?php
  2. class Talker
  3. {
  4. use A, B, C {
  5. B::smallTalk insteadof A;
  6. A::bigTalk insteadof C;
  7. C::mediumTalk as FooBar;
  8. }
  9. }

4.3 属性和常量

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

如果你的项目 PHP 最小版本支持常量可见性( PHP 7.1 或以上),所有常量 必须 声明可见性。

关键字 var 不得 用于声明属性。

每条声明语句 不得 声明多于一个属性。

属性名 不得 用单个下划线开头表明其受保护的或私有的可见性。也就是说,一个下划线开头显然是没有意义的。

类型声明和属性名之间 必须 有一个空格。

一个属性声明看上去如下所示:

  1. <?php
  2. namespace Vendor\Package;
  3. class ClassName
  4. {
  5. public $foo = null;
  6. public static int $bar = 0;
  7. }

4.4 方法和函数

所有的方法 必须 事先声明类型。

方法命名 一定不可 用单个下划线来区分是 protectedprivate 类型。也就是说,不要用一个没有意义的下划线开头。

方法和函数名称中,方法命名后面 一定不可 使用空格。方法开始的花括号 必须 写在方法声明后自成一行, 结束花括号也 必须 写在方法后面自成一行。开始左括号后和结束右括号前,都 一定不可 有空格符。

一个方法的声明应该如下所示。注意括号,逗号,空格和花括号的位置:

  1. <?php
  2. namespace Vendor\Package;
  3. class ClassName
  4. {
  5. public function fooBarBaz($arg1, &$arg2, $arg3 = [])
  6. {
  7. // 方法主体
  8. }
  9. }

一个函数的声明应该如下所示。注意括号,逗号,空格和花括号的位置:

  1. <?php
  2. function fooBarBaz($arg1, &$arg2, $arg3 = [])
  3. {
  4. // 函数主体
  5. }

4.5 方法和函数参数

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

方法和函数中带有默认值的参数 必须 放在参数列表的最后。

  1. <?php
  2. namespace Vendor\Package;
  3. class ClassName
  4. {
  5. public function foo(int $arg1, &$arg2, $arg3 = [])
  6. {
  7. // 方法主体
  8. }
  9. }

参数列表 可以 分为多行,每行参数缩进一次。当这么做时,第一个参数 必须 放在下一行,且每行 必须 只能有一个参数。

当参数列表分成多行时,右圆括号和左花括号 必须 放在同一行且单独成行,两者之间存在一个空格。

  1. <?php
  2. namespace Vendor\Package;
  3. class ClassName
  4. {
  5. public function aVeryLongMethodName(
  6. ClassTypeHint $arg1,
  7. &$arg2,
  8. array $arg3 = []
  9. ) {
  10. // 方法主体
  11. }
  12. }

当你定义一个返回值类型声明时,冒号后面的类型声明 必须 用空格符隔开。冒号和声明 必须 在同一行,且跟参数列表后的结束括号之间没有空格。

  1. <?php
  2. declare(strict_types=1);
  3. namespace Vendor\Package;
  4. class ReturnTypeVariations
  5. {
  6. public function functionName(int $arg1, $arg2): string
  7. {
  8. return 'foo';
  9. }
  10. public function anotherFunction(
  11. string $foo,
  12. string $bar,
  13. int $baz
  14. ): string {
  15. return 'foo';
  16. }
  17. }

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

  1. <?php
  2. declare(strict_types=1);
  3. namespace Vendor\Package;
  4. class ReturnTypeVariations
  5. {
  6. public function functionName(?string $arg1, ?int &$arg2): ?string
  7. {
  8. return 'foo';
  9. }
  10. }

当在参数之前使用引用运算符 & 时,引用运算符之后不能有空格,例如上面的示例。

可变参数声明的三个点和参数名称之间不能有空格:

  1. public function process(string $algorithm, ...$parts)
  2. {
  3. // 函数体
  4. }

当同时使用引用运算符和可变参数运算符时,它们之间不能有任何空格:

  1. public function process(string $algorithm, &...$parts)
  2. {
  3. // 函数体
  4. }

4.6 abstract, final, and static

如果是 abstract and final ,那么申明的时候必须是可见性声明。

如果是 static ,声明必须位于可见性声明之后。

  1. <?php
  2. namespace Vendor\Package;
  3. abstract class ClassName
  4. {
  5. protected static $foo;
  6. abstract protected function zim();
  7. final public static function bar()
  8. {
  9. // 请求体
  10. }
  11. }

4.7 方法和函数的调用

当我们在进行方法或者函数调用的时候,方法或函数与左括号之间不能出现空格,在右括号之后也不能出现空格,并且在右括号之前也一定不能有空格。在参数列表中,每个逗号前面一定不能有空格,每个逗号后面也一定不能有空格。

  1. <?php
  2. bar();
  3. $foo->bar($arg1);
  4. Foo::bar($arg2, $arg3);

参数列表可以分为多行,每行后面缩进一次。这个做之后,列表中的第一行必须位于下一行,并且每一行必须只有一个参数。跨多个行拆分单个参数(就像匿名函数或者数组那样)并不构成拆分参数列表本身。

  1. <?php
  2. $foo->bar(
  3. $longArgument,
  4. $longerArgument,
  5. $muchLongerArgument
  6. );
  1. <?php
  2. somefunction($foo, $bar, [
  3. // ...
  4. ], $baz);
  5. $app->get('/hello/{name}', function ($name) use ($app) {
  6. return 'Hello ' . $app->escape($name);
  7. });

5. 流程控制

如下是主要的流程控制风格规则:

  • 流程控制关键词之后 必须 要有一个空格
  • 左括号后面 不能 有空格
  • 右括号前面 不能 有空格
  • 右括号与左大括号之间 必须 要有一个空格
  • 流程主体 必须 要缩进一次
  • 流程主体 必须 在左大括号之后另起一行
  • 右大括号 必须 在流程主体之后另起一行

每个流程控制主体 必须 以封闭的括号结束。这将标准化流程结构,同时减少由于流程中添加新的内容而引入错误的可能性。

5.1 if, elseif, else

if 结构如下。注意括号,空格,和大括号的位置;elseelseif 都在同一行,和右大括号一样在主体的前面。

  1. <?php
  2. if ($expr1) {
  3. // if body
  4. } elseif ($expr2) {
  5. // elseif body
  6. } else {
  7. // else body;
  8. }

关键词 elseif 应该 替换 else if,这样控制关键词看起来像一个词。

括号中的表达式 可能 会被分开为多行,每一行至少缩进一次。如果这样做,第一个条件 必须 在新的一行。右括号和左大括号 必须 在同一行,而且中间有一个空格。条件中间的布尔控制符 必须 在每一行的开头或者结尾,而不是混在一起。

  1. <?php
  2. if (
  3. $expr1
  4. && $expr2
  5. ) {
  6. // if body
  7. } elseif (
  8. $expr3
  9. && $expr4
  10. ) {
  11. // elseif body
  12. }

5.2 switch, case

switch 结构如下。注意括号,空格和大括号的位置。case 必须 缩进一次从switch开始, break 关键词 (或者其他终止关键词) 必须 缩进和 care 主体保持一致。必须 要有一个像 // 不终止 这样的注释在不为空且继续的 care 主体之中。

  1. <?php
  2. switch ($expr) {
  3. case 0:
  4. echo 'First case, with a break';
  5. break;
  6. case 1:
  7. echo 'Second case, which falls through';
  8. // no break
  9. case 2:
  10. case 3:
  11. case 4:
  12. echo 'Third case, return instead of break';
  13. return;
  14. default:
  15. echo 'Default case';
  16. break;
  17. }

括号中的表达式 可能 会被分开多行,每一行至少要缩进一次。如果这样做,第一个条件 必须 在新的一行。右括号和左大括号 必须 在同一行,而且中间有一个空格。条件中间的布尔控制符 必须 在没一行的开头或者结尾,而不是混在一起。

  1. <?php
  2. switch (
  3. $expr1
  4. && $expr2
  5. ) {
  6. // structure body
  7. }

5.3 while, do while

while 结构如下。注意括号,空格和大括号的位置。

  1. <?php
  2. while ($expr) {
  3. // structure body
  4. }

括号中的表达式 可能 会被分开多行,没一行至少要缩进一次。如果这样做,第一个条件 必须 在新的一行。右括号和左大括号 必须 在同一行,而且中间有一个空格。条件中间的布尔控制符 必须 在没一行的开头或者结尾,而不是混在一起。

  1. <?php
  2. while (
  3. $expr1
  4. && $expr2
  5. ) {
  6. // structure body
  7. }

同样的, do while 申明如下。注意括号,空格和大括号的位置。

  1. <?php
  2. do {
  3. // structure body;
  4. } while ($expr);

括号中的表达式 可能 会被分开多行,没一行至少要缩进一次。如果这样做,第一个条件 必须 在新的一行。条件中间的布尔控制符 必须 在每一行的开头或者结尾,而不是混在一起。

  1. <?php
  2. do {
  3. // structure body;
  4. } while (
  5. $expr1
  6. && $expr2
  7. );

5.4 for

for 申明如下。注意括号,空格和大括号的位置。

  1. <?php
  2. for ($i = 0; $i < 10; $i++) {
  3. // for body
  4. }

括号中的表达式 可能 会被分开多行,每一行至少要缩进一次。如果这样做,第一个条件 必须 在新的一行。右括号和左大括号 必须 在同一行,而且中间有一个空格。

  1. <?php
  2. for (
  3. $i = 0;
  4. $i < 10;
  5. $i++
  6. ) {
  7. // for body
  8. }

5.5 foreach

foreach 语句的写法如下所示。请注意它的圆括号、空格和花括号。

  1. <?php
  2. foreach ($iterable as $key => $value) {
  3. // 迭代主体
  4. }

5.6 try , catch , finally

一个 try-catch-finally 模块包含下面这些内容。请注意它的圆括号、空格和花括号。

  1. <?php
  2. try {
  3. // try 主体
  4. } catch (FirstThrowableType $e) {
  5. // 捕获异常主体
  6. } catch (OtherThrowableType | AnotherThrowableType $e) {
  7. // 捕获异常主体
  8. } finally {
  9. // finally 主体
  10. }

6. 运算符

运算符的样式规则按元数分组(其接受的操作数个数)。

当运算符周围允许出现空格时, 可以 出于可读性目的打多个空格。

所有这里没描述的运算符暂不作限定。

6.1. 一元运算符

递增 / 递减运算符和操作数之间 不得 有任何空格。

  1. $i++;
  2. ++$j;

类型转换运算符的圆括号内部 不得 有任何空格:

  1. $intValue = (int) $input;

6.2. 二元运算符

所有二进制 算术比较赋值按位逻辑字符串类型运算符必须在前后跟至少一个空格:

  1. if ($a === $b) {
  2. $foo = $bar ?? $a ?? $b;
  3. } elseif ($a > $b) {
  4. $foo = $a + $b * $c;
  5. }

6.3. 三元运算符

条件运算符,也称为三元运算符,必须在 ?:这两个字符之间:

  1. $variable = $foo ? 'foo' : 'bar';

如果省略条件运算符的中间操作数,运算符必须遵循与其他二进制比较运算符相同的样式规则:

  1. $variable = $foo ?: 'bar';

7. 闭包(Closures)

闭包声明时必须在 function 关键字后留有1个空格,并且在 use 关键字前后各留有1个空格。

左花括号必须跟随前文写在同一行,右花括号必须在函数体后换行放置。

不能在参数和变量的左括号后和右括号前放置空格。

不能在参数和变量的逗号前放置空格,但必须在逗号后放置1个空格。

闭包参数如果有默认值,该参数必须放在参数列表末尾。

如果声明了返回类型,它必须遵循普通函数和方法相同的规则;如果使用 use 关键字,冒号必须在 use 右括号后且冒号前后不能有空格。

闭包的声明方式如下,留意括号,逗号,空格和花括号:

  1. <?php
  2. $closureWithArgs = function ($arg1, $arg2) {
  3. // 函数体
  4. };
  5. $closureWithArgsAndVars = function ($arg1, $arg2) use ($var1, $var2) {
  6. // 函数体
  7. };
  8. $closureWithArgsVarsAndReturn = function ($arg1, $arg2) use ($var1, $var2): bool {
  9. // 函数体
  10. };

参数和变量可以分多行放置,每个后续行缩进一次。执行此操作时,列表中的第一项必须放在下一行,并且每行只能有一个参数或变量。

结束多行列表(或者参数,变量)的时候,右括号和左大括号 必须 要放在一行,而且中间有一个空格。

下面是有和没有多行参数列表与变量列表的闭包示例。

  1. <?php
  2. $longArgs_noVars = function (
  3. $longArgument,
  4. $longerArgument,
  5. $muchLongerArgument
  6. ) {
  7. // body
  8. };
  9. $noArgs_longVars = function () use (
  10. $longVar1,
  11. $longerVar2,
  12. $muchLongerVar3
  13. ) {
  14. // body
  15. };
  16. $longArgs_longVars = function (
  17. $longArgument,
  18. $longerArgument,
  19. $muchLongerArgument
  20. ) use (
  21. $longVar1,
  22. $longerVar2,
  23. $muchLongerVar3
  24. ) {
  25. // body
  26. };
  27. $longArgs_shortVars = function (
  28. $longArgument,
  29. $longerArgument,
  30. $muchLongerArgument
  31. ) use ($var1) {
  32. // body
  33. };
  34. $shortArgs_longVars = function ($arg) use (
  35. $longVar1,
  36. $longerVar2,
  37. $muchLongerVar3
  38. ) {
  39. // body
  40. };

注意格式化规则也适用一个闭包在一个方法或者操作中作为参数被直接引用。

  1. <?php
  2. $foo->bar(
  3. $arg1,
  4. function ($arg2) use ($var1) {
  5. // body
  6. },
  7. $arg3
  8. );

8. 匿名类

匿名类 必须 遵循上面章节中和闭包一样的方针和准则。

  1. <?php
  2. $instance = new class {};

只要 implements 接口列表不换行,左花括号 可以 和关键字 class 在同一行。如果接口列表换行,花括号 必须 放在最后一个接口的下一行。

  1. <?php
  2. // 花括号在同一行
  3. $instance = new class extends \Foo implements \HandleableInterface {
  4. // 类内容
  5. };
  6. // 花括号在下一行
  7. $instance = new class extends \Foo implements
  8. \ArrayAccess,
  9. \Countable,
  10. \Serializable
  11. {
  12. // 类内容
  13. };