PSR-12 编码规范扩充
概览
此规范起到继承,扩展和替换 PSR-2 的作用, 同时编码风格遵守 PSR-1 这个基础编码标准。
与 PSR-2 一样, 此规范的目的是减少不同人在阅读代码时认知冲突。 它通过列举一套如何格式化 PHP
代码的公共的规则和期望来实现这个目标。 PSR
力图提供一套方法,编码风格工具可以利用,项目可以遵守,开发人员可以方便的在不同的项目中使用。当各个的开发人员在进行多项目合作的时候,它可以帮助在这些项目中提供一套通用的指导。所以,本指南的价值不是规则本身,而是这些规则的共享。 PSR-2 在 2012 年被接受,随后 PHP
经历了很多变化,影响了编码风格。同时 PSR-2 是 PHP
编码时候的基础功能,被广泛的采用。因此,PSR
力图通过一种更加现代的方式说明 PSR-2 的内容和新功能,并对 PSR-2 进行更正。
以前的语言版本
在整个文档中,任何说明都可以被忽略,如果它们不存在于你项目所支持的 PHP
版本中。
例如
此示例包含以下一些规则作为快速概述:
代码语言:javascript复制 1<?php
2
3declare(strict_types=1);
4
5namespace VendorPackage;
6
7use VendorPackage{ClassA as A, ClassB, ClassC as C};
8use VendorPackageSomeNamespaceClassD as D;
9
10use function VendorPackage{functionA, functionB, functionC};
11
12use const VendorPackage{ConstantA, ConstantB, ConstantC};
13
14class Foo extends Bar implements FooInterface
15{
16 public function sampleFunction(int $a, int $b = null): array
17 {
18 if ($a === $b) {
19 bar();
20 } elseif ($a > $b) {
21 $foo->bar($arg1);
22 } else {
23 BazClass::bar($arg2, $arg3);
24 }
25 }
26
27 final public static function bar()
28 {
29 // 方法内容
30 }
31}
总则
基本编码标准
代码必须遵循 PSR-1 中列出的所有规则。 PSR-1 中的术语 StudlyCaps
必须解释为 PascalCase
(帕斯卡命名法:大驼峰式命名法),其中每个单词的第一个字母大写,包括第一个字母。
文件
- 所有
PHP
文件只能使用Unix LF (换行符)
结尾。 - 所有的
PHP
文件都必须以非空行结尾,以一个LF
结尾。 - 在仅包含
PHP
代码的文件中,必须省略结尾的?>
标记。
代码行
- 行长度不得有硬限制。
- 行长度的软限制必须为
120
个字符。 - 行的长度不应超过
80
个字符;超过该长度的行应拆分为多个后续行,每个行的长度不应超过80
个字符。 - 行尾不能有尾随空格。
- 可以添加空行以提高可读性并指示相关的代码块,除非明确禁止。
- 每行不能有多个语句。
缩进
代码必须为每个缩进级别使用 4
个空格的缩进,并且不能使用缩进标签。
关键词和类型
PHP
的所有关键字和类型 12 都必须使用小写。PHP
未来版本中新加的所有关键字和类型也都必须使用小写。 类型关键字必须使用缩写。使用bool
而不是boolean
,使用int
而不是integer
等等。
声明、命名空间以及导入
一个 PHP
文件的头部可能会包含多个块。如果包含多个块,则每个块都必须用空白行和其他块分隔,并且块内不能包含空白行。所有的块都必须按照下面的顺序排列,如果不存在该块则忽略。
- 开始标签: <?php。
- 文件级文档块。
- 一个或多个声明语句。
- 命名空间声明语句。
- 一个或多个基于类的 use 声明语句。
- 一个或多个基于方法的 use 声明语句。
- 一个或多个基于常量的 use 声明语句。
- 其余代码。
当文件包含 HTML
和 PHP
的混合代码时,可以使用上面列出的任何部分。如果是这种情况的话,即时代码的其他部分包含有 PHP
结束符,然后再包含 HTML
和 PHP
代码,声明、命名空间和导入语句块也必须放在文件的顶部。
什么时候开始 <?php
标签位于文件的第一行,它必须位于自己的行,没有其他语句,除非它是一个包含 PHP
之外的标记的文件打开和关闭标记。
import
语句不能以前导反斜杠开头,因为它们必须始终完全合格。 以下示例演示了所有块的完整列表:
1<?php
2
3/**
4 * This file contains an example of coding styles.
5 */
6
7declare(strict_types=1);
8
9namespace VendorPackage;
10
11use VendorPackage{ClassA as A, ClassB, ClassC as C};
12use VendorPackageSomeNamespaceClassD as D;
13use VendorPackageAnotherNamespaceClassE as E;
14
15use function VendorPackage{functionA, functionB, functionC};
16use function AnotherVendorfunctionD;
17
18use const VendorPackage{CONSTANT_A, CONSTANT_B, CONSTANT_C};
19use const AnotherVendorCONSTANT_D;
20
21/**
22 * FooBar is an example class.
23 */
24class FooBar
25{
26 // ... additional PHP code ...
27}
不得使用深度超过两层的复合名称空间,因此以下展示了允许的最大复合深度。
代码语言:javascript复制1<?php
2
3use VendorPackageSomeNamespace{
4 SubnamespaceOneClassA,
5 SubnamespaceOneClassB,
6 SubnamespaceTwoClassY,
7 ClassZ,
8};
并且不允许以下内容:
代码语言:javascript复制1<?php
2
3use VendorPackageSomeNamespace{
4 SubnamespaceOneAnotherNamespaceClassA,
5 SubnamespaceOneClassB,
6 ClassZ,
7};
当希望在 PHP
外部包含标记的文件中声明严格类型时打开和关闭标签,声明必须写在文件的第一行并且包含在一个开始的 PHP
标签,以及严格的类型声明和结束标签。 例如:
1<?php declare(strict_types=1) ?>
2<html>
3<body>
4 <?php
5 // ... additional PHP code ...
6 ?>
7</body>
8</html>
声明语句不能包含空格,并且必须完全是 declare(strict_types=1)
(带有可选的分号终止符)。 允许使用块声明语句,并且必须按照以下的格式设置。注意的位置括号和间距:
1declare(ticks=1) {
2 // 一些代码
3}
类,属性,和方法
这里的 类
指的是所有类
,接口
,以及 trait
。
任何注释和语句 不得 跟在其右花括号后的同一行。
当实例化一个类时,后面的圆括号 必须 写出来,即使没有参数传进其构造函数。
代码语言:javascript复制1new Foo();
继承和实现
- 关键字
extends
和implments
必须 在类名的同一行声明。 - 类的左花括号 必须 另起一行;右花括号 必须 跟在类主体的下一行。
- 类的左花括号 必须 独自成行,且 不得 在其上一行或下一行存在空行。
- 右花括号 必须 独自成行,且 不得 在其上一行存在空行。
1<?php
2
3namespace VendorPackage;
4
5use FooClass;
6use BarClass as Bar;
7use OtherVendorOtherPackageBazClass;
8
9class ClassName extends ParentClass implements ArrayAccess, Countable
10{
11 // 常量,属性,方法
12}
如果有接口, implements
接口和 extends
父类 可以 分为多行,前者每行需缩进一次。当这么做时,第一个接口 必须 写在下一行,且每行 必须 只能写一个接口。
1<?php
2
3namespace VendorPackage;
4
5use FooClass;
6use BarClass as Bar;
7use OtherVendorOtherPackageBazClass;
8
9class ClassName extends ParentClass implements
10 ArrayAccess,
11 Countable,
12 Serializable
13{
14 // 常量,属性,方法
15}
使用 trait
在类里面用于实现 trait
的关键字 use
必须 在左花括号的下一行声明。
1<?php
2
3namespace VendorPackage;
4
5use VendorPackageFirstTrait;
6
7class ClassName
8{
9 use FirstTrait;
10}
每个导入类的 trait 必须 每行一个包含声明,且每个包含声明 必须 有其 use
导入语句。
1<?php
2
3namespace VendorPackage;
4
5use VendorPackageFirstTrait;
6use VendorPackageSecondTrait;
7use VendorPackageThirdTrait;
8
9class ClassName
10{
11 use FirstTrait;
12 use SecondTrait;
13 use ThirdTrait;
14}
在类文件中,如果在使用 use Trait
之后没有其他内容了 ,类名右大括号必须另起一行。
1<?php
2
3namespace VendorPackage;
4
5use VendorPackageFirstTrait;
6
7class ClassName
8{
9 use FirstTrait;
10}
如有其他内容,两者之间需空一行。
代码语言:javascript复制 1<?php
2
3namespace VendorPackage;
4
5use VendorPackageFirstTrait;
6
7class ClassName
8{
9 use FirstTrait;
10
11 private $property;
12}
当使用 insteadof
和 as
运算符时,它们必须如图所示使用,注意缩进、间距和另起一行。
1<?php
2
3class Talker
4{
5 use A, B, C {
6 B::smallTalk insteadof A;
7 A::bigTalk insteadof C;
8 C::mediumTalk as FooBar;
9 }
10}
属性和常量
- 所有属性 必须 声明可见性。
- 如果你的项目
PHP
最小版本支持常量可见性( PHP 7.1 或以上),所有常量 必须 声明可见性。 - 关键字
var
不得 用于声明属性。 - 每条声明语句 不得 声明多于一个属性。
- 属性名 不得 用单个下划线开头表明其受保护的或私有的可见性。也就是说,一个下划线开头显然是没有意义的。
- 类型声明和属性名之间 必须 有一个空格。
一个属性声明看上去如下所示:
代码语言:javascript复制1<?php
2
3namespace VendorPackage;
4
5class ClassName
6{
7 public $foo = null;
8 public static int $bar = 0;
9}
方法和函数
- 所有的方法 必须 事先声明类型。
- 方法命名 不得 用单个下划线来区分是
protected
或private
类型。也就是说,不要用一个没有意义的下划线开头。 - 方法和函数名称中,方法命名后面 不得 使用空格。方法开始的花括号 必须 写在方法声明后自成一行, 结束花括号也 必须 写在方法后面自成一行。开始左括号后和结束右括号前,都 不得 有空格符。
一个方法的声明应该如下所示。注意括号,逗号,空格和花括号的位置:
代码语言:javascript复制 1<?php
2
3namespace VendorPackage;
4
5class ClassName
6{
7 public function fooBarBaz($arg1, &$arg2, $arg3 = [])
8 {
9 // 方法主体
10 }
11}
一个函数的声明应该如下所示。注意括号,逗号,空格和花括号的位置:
代码语言:javascript复制1<?php
2
3function fooBarBaz($arg1, &$arg2, $arg3 = [])
4{
5 // 函数主体
6}
方法和函数参数
在参数列表中, 不得 在每个逗号前存在空格,且 必须 在每个逗号后有一个空格。 方法和函数中带有默认值的参数 必须 放在参数列表的最后。
代码语言:javascript复制 1<?php
2
3namespace VendorPackage;
4
5class ClassName
6{
7 public function foo(int $arg1, &$arg2, $arg3 = [])
8 {
9 // 方法主体
10 }
11}
参数列表 可以 分为多行,每行参数缩进一次。当这么做时,第一个参数 必须 放在下一行,且每行 必须 只能有一个参数。
当参数列表分成多行时,右圆括号和左花括号 必须 放在同一行且单独成行,两者之间存在一个空格。
代码语言:javascript复制 1<?php
2
3namespace VendorPackage;
4
5class ClassName
6{
7 public function aVeryLongMethodName(
8 ClassTypeHint $arg1,
9 &$arg2,
10 array $arg3 = []
11 ) {
12 // 方法主体
13 }
14}
当你定义一个返回值类型声明时,冒号后面的类型声明 必须 用空格符隔开。冒号和声明 必须 在同一行,且跟参数列表后的结束括号之间没有空格。
代码语言:javascript复制 1<?php
2
3declare(strict_types=1);
4
5namespace VendorPackage;
6
7class ReturnTypeVariations
8{
9 public function functionName(int $arg1, $arg2): string
10 {
11 return 'foo';
12 }
13
14 public function anotherFunction(
15 string $foo,
16 string $bar,
17 int $baz
18 ): string {
19 return 'foo';
20 }
21}
在可空类型声明中,问号和类型声明之间不能有空格。
代码语言:javascript复制 1<?php
2
3declare(strict_types=1);
4
5namespace VendorPackage;
6
7class ReturnTypeVariations
8{
9 public function functionName(?string $arg1, ?int &$arg2): ?string
10 {
11 return 'foo';
12 }
13}
当在参数之前使用引用运算符 &
时,引用运算符之后不能有空格,例如上面的示例。
可变参数声明的三个点和参数名称之间不能有空格:
代码语言:javascript复制1public function process(string $algorithm, ...$parts)
2{
3 // 函数体
4}
当同时使用引用运算符和可变参数运算符时,它们之间不能有任何空格:
代码语言:javascript复制1public function process(string $algorithm, &...$parts)
2{
3 // 函数体
4}
abstract
, final
, static
如果是 abstract
, final
,那么申明的时候必须是可见性声明。 如果是 static
,声明必须位于可见性声明之后。
1<?php
2
3namespace VendorPackage;
4
5abstract class ClassName
6{
7 protected static $foo;
8
9 abstract protected function zim();
10
11 final public static function bar()
12 {
13 // 请求体
14 }
15}
方法和函数的调用
当我们在进行方法或者函数调用的时候,方法名或函数名与左括号之间不能出现空格,在右括号之后也不能出现空格,并且在右括号之前也不能有空格。在参数列表中,每个逗号前面不能有空格,每个逗号后面必须有一个空格。
代码语言:javascript复制1<?php
2
3bar();
4$foo->bar($arg1);
5Foo::bar($arg2, $arg3);
参数列表可以分为多行,每行后面缩进一次。这样做时,列表中的第一项必须位于下一行,并且每一行必须只有一个参数。跨多个行拆分单个参数 (就像匿名函数或者数组那样) 并不构成拆分参数列表本身。
代码语言:javascript复制1<?php
2
3$foo->bar(
4 $longArgument,
5 $longerArgument,
6 $muchLongerArgument
7);
代码语言:javascript复制1<?php
2
3somefunction($foo, $bar, [
4 // ...
5], $baz);
6
7$app->get('/hello/{name}', function ($name) use ($app) {
8 return 'Hello ' . $app->escape($name);
9});
流程控制
如下是主要的流程控制风格规则:
- 流程控制关键词之后 必须 要有一个空格
- 左括号后面 不能 有空格
- 右括号前面 不能 有空格
- 右括号与左大括号之间 必须 要有一个空格
- 流程主体 必须 要缩进一次
- 流程主体 必须 在左大括号之后另起一行
- 右大括号 必须 在流程主体之后另起一行
每个流程控制主体 必须 以封闭的括号结束。这将标准化流程结构,同时减少由于流程中添加新的内容而引入错误的可能性。
if, elseif, else
if
结构如下。注意括号,空格,和大括号的位置;else
和 elseif
都在同一行,和右大括号一样在主体的前面。
1<?php
2
3if ($expr1) {
4 // if body
5} elseif ($expr2) {
6 // elseif body
7} else {
8 // else body;
9}
应该 使用关键词 elseif
替换 else if
,这样所有的控制关键词看起来都像单个词。
括号中的表达式 可能 会被分开为多行,每一行至少缩进一次。如果这样做,第一个条件 必须 在新的一行。右括号和左大括号 必须 在同一行,而且中间有一个空格。条件中间的布尔控制符 必须 在每一行的开头或者结尾,而不是混在一起。
代码语言:javascript复制 1<?php
2
3if (
4 $expr1
5 && $expr2
6) {
7 // if body
8} elseif (
9 $expr3
10 && $expr4
11) {
12 // elseif body
13}
switch, case
switch
结构如下。注意括号,空格和大括号的位置。case 必须 缩进一次从 switch
开始, break
关键词 (或者其他终止关键词) 必须 缩进和 case
主体保持一致。必须 要有一个像 // no break
这样的注释在不为空且不需要中断的 case 主体之中。
1<?php
2
3switch ($expr) {
4 case 0:
5 echo 'First case, with a break';
6 break;
7 case 1:
8 echo 'Second case, which falls through';
9 // no break
10 case 2:
11 case 3:
12 case 4:
13 echo 'Third case, return instead of break';
14 return;
15 default:
16 echo 'Default case';
17 break;
18}
括号中的表达式 可能 会被分开多行,每一行至少要缩进一次。如果这样做,第一个条件 必须 在新的一行。右括号和左大括号 必须 在同一行,而且中间有一个空格。条件中间的布尔控制符 必须 在一行的开头或者结尾,而不是混在一起。
代码语言:javascript复制1<?php
2
3switch (
4 $expr1
5 && $expr2
6) {
7 // structure body
8}
while, do while
while
结构如下。注意括号,空格和大括号的位置。
1<?php
2
3while ($expr) {
4 // structure body
5}
括号中的表达式 可能 会被分开多行,每一行至少要缩进一次。如果这样做,第一个条件 必须 在新的一行。右括号和左大括号 必须 在同一行,而且中间有一个空格。条件中间的布尔控制符 必须 在每一行的开头或者结尾,而不是混在一起。
代码语言:javascript复制1<?php
2
3while (
4 $expr1
5 && $expr2
6) {
7 // structure body
8}
同样的, do while
申明如下。注意括号,空格和大括号的位置。
1<?php
2
3do {
4 // structure body;
5} while ($expr);
括号中的表达式 可能 会被分开多行,每一行至少要缩进一次。如果这样做,第一个条件 必须 在新的一行。条件中间的布尔控制符 必须 在每一行的开头或者结尾,而不是混在一起。
代码语言:javascript复制1<?php
2
3do {
4 // structure body;
5} while (
6 $expr1
7 && $expr2
8);
for
for
申明如下。注意括号,空格和大括号的位置。
1<?php
2
3for ($i = 0; $i < 10; $i ) {
4 // for body
5}
括号中的表达式 可能 会被分开多行,每一行至少要缩进一次。如果这样做,第一个条件 必须 在新的一行。右括号和左大括号 必须 在同一行,而且中间有一个空格。
代码语言:javascript复制1<?php
2
3for (
4 $i = 0;
5 $i < 10;
6 $i
7) {
8 // for body
9}
foreach
foreach
语句的写法如下所示。请注意它的圆括号、空格和花括号。
1<?php
2
3foreach ($iterable as $key => $value) {
4 // 迭代主体
5}
try, catch, finally
一个 try-catch-finally
模块包含下面这些内容。请注意它的圆括号、空格和花括号。
1<?php
2
3try {
4 // try 主体
5} catch (FirstThrowableType $e) {
6 // 捕获异常主体
7} catch (OtherThrowableType | AnotherThrowableType $e) {
8 // 捕获异常主体
9} finally {
10 // finally 主体
11}
运算符
- 运算符的样式规则按元数分组(其接受的操作数个数)。
- 当运算符周围允许出现空格时, 可以 出于可读性目的打多个空格。
- 所有这里没描述的运算符暂不作限定。
一元运算符
递增
/ 递减
运算符和操作数之间 不得 有任何空格。
1$i ;
2 $j;
类型转换运算符的圆括号内部 不得 有任何空格:
代码语言:javascript复制1$intValue = (int) $input;
二元运算符
所有二进制 算术,比较,赋值,按位,逻辑、字符串和类型运算符必须在前后跟至少一个空格:
代码语言:javascript复制1if ($a === $b) {
2 $foo = $bar ?? $a ?? $b;
3} elseif ($a > $b) {
4 $foo = $a $b * $c;
5}
三元运算符
条件运算符,也称为三元运算符,必须在 ?
和 :
这两个字符之间:
1$variable = $foo ? 'foo' : 'bar';
如果省略条件运算符的中间操作数,运算符必须遵循与其他二进制比较运算符相同的样式规则:
代码语言:javascript复制1$variable = $foo ?: 'bar';
闭包(Closures)
- 闭包声明时 必须 在
function
关键字后留有1
个空格,并且在use
关键字前后各留有1
个空格。 - 左花括号 必须 跟随前文写在同一行,右花括号必须在函数体后换行放置。
- 不能在参数和变量的左括号后和右括号前放置空格。
- 不能在参数和变量的逗号前放置空格,但必须在逗号后放置 1 个空格。
- 闭包参数如果有默认值,该参数必须放在参数列表末尾。
- 如果声明了返回类型,它必须遵循普通函数和方法相同的规则;如果使用
use
关键字,冒号必须在use
右括号后且冒号前后不能有空格。
闭包的声明方式如下,留意括号,逗号,空格和花括号:
代码语言:javascript复制 1<?php
2
3$closureWithArgs = function ($arg1, $arg2) {
4 // 函数体
5};
6
7$closureWithArgsAndVars = function ($arg1, $arg2) use ($var1, $var2) {
8 // 函数体
9};
10
11$closureWithArgsVarsAndReturn = function ($arg1, $arg2) use ($var1, $var2): bool {
12 // 函数体
13};
参数和变量可以分多行放置,每个后续行缩进一次。执行此操作时,列表中的第一项 必须 放在下一行,并且每行只能有一个参数或变量。
结束多行列表(或者参数,变量)的时候,右括号和左大括号 必须 要放在一行,而且中间有一个空格。
下面是有和没有多行参数列表与变量列表的闭包示例。
代码语言:javascript复制 1<?php
2
3$longArgs_noVars = function (
4 $longArgument,
5 $longerArgument,
6 $muchLongerArgument
7) {
8 // body
9};
10
11$noArgs_longVars = function () use (
12 $longVar1,
13 $longerVar2,
14 $muchLongerVar3
15) {
16 // body
17};
18
19$longArgs_longVars = function (
20 $longArgument,
21 $longerArgument,
22 $muchLongerArgument
23) use (
24 $longVar1,
25 $longerVar2,
26 $muchLongerVar3
27) {
28 // body
29};
30
31$longArgs_shortVars = function (
32 $longArgument,
33 $longerArgument,
34 $muchLongerArgument
35) use ($var1) {
36 // body
37};
38
39$shortArgs_longVars = function ($arg) use (
40 $longVar1,
41 $longerVar2,
42 $muchLongerVar3
43) {
44 // body
45};
注意格式化规则也适用一个闭包在一个方法或者操作中作为参数被直接引用。
代码语言:javascript复制1<?php
2
3$foo->bar(
4 $arg1,
5 function ($arg2) use ($var1) {
6 // body
7 },
8 $arg3
9);
匿名类
匿名类 必须 遵循上面章节中和闭包一样的方针和准则。
代码语言:javascript复制1<?php
2
3$instance = new class {};
只要 implements
接口列表不换行,左花括号 可以 和关键字 class 在同一行。如果接口列表换行,花括号 必须 放在最后一个接口的下一行。
1<?php
2
3// 花括号在同一行
4$instance = new class extends Foo implements HandleableInterface {
5 // 类内容
6};
7
8// 花括号在下一行
9$instance = new class extends Foo implements
10 ArrayAccess,
11 Countable,
12 Serializable
13{
14 // 类内容
15};