PSR 12 编码风格指南补充 「审阅中」
本篇规范中的 必须,不得,需要,应,不应,应该,不应该,推荐,可能 和 可选 等词按照 RFC 2119 中的描述进行解释
概览
本规范扩充和延展并替代了 PSR-2 编码风格指南,并要求遵守 PSR-1,即基本编码标准
跟 PSR-2 一样,本规范的目的是为阅读来自不同作者的代码时减少认知摩擦,通过罗列一组共享的规则和期望来了解如何格式化 PHP 代码。
PSR-12 寻求提供一种编码风格工具可以实现的设置方式,项目可以声明遵守这些编码风格从而减少认知摩擦,然后开发人员可以轻松地协调不同项目
当多个开发者在多个项目中进行协作时,有助于在所有这些项目中使用一套准则。 因此,本指南的好处不在于规则本身,而在于分享这些规则
PSR-2 于 2012 年被接受,自那时起,PHP 语言做出做了一系列的更改,这些更改对编码风格指南产生了一些影响。 虽然 PSR-2 非常全面地介绍了编写规范时已经存在的 PHP 功能,但新功能非常易于解释。 因此,此 PSR 试图在更现代的背景下阐述 PSR-2 的内容、提供新功能,并对 PSR-2 进行纠错
先前的语言版本
如果你的项目的 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\{ConstantA, ConstantB, ConstantC}; class Foo extends Bar implements FooInterface { public function sampleFunction(int $a, int $b = null): array { if ($a === $b) { bar(); } elseif ($a > $b) { $foo->bar($arg1); } else { BazClass::bar($arg2, $arg3); } } final public static function bar() { // 方法内容 } }
2. 通用
2.1 基本代码标准
代码 必须 遵循 PSR-1 中列出的所有规则
PSR-1 中的术语 「 StudlyCaps 」 必须 解释为帕斯卡命名法,及所有单词的首字母大写,包括第一个单词
2.2 文件
所有的文件 必须 只能使用 Unix LF ( 换行符) ,也就是 \n
所有的 PHP 文件 应该 以非空白行结尾,并以单个 LF 结尾
如果文件只包含了 PHP 代码,那么可以省略结尾的 ?>
标记
2.3 行
绝对不能 强制限制行的长度 ( 强制换行 )
软限制行的长度 必须 是 120 个字符
每行 不应该 长于 80 个字符,如果有比这更长的行,则 应该 分拆成多个不长于 80 个字符的行
每行结尾 绝对不能 空白符
除非明确禁止,可以添加一些空白行来提高代码可读性和标识相关的代码块
每行 绝对不能 多余一条语句
2.4 缩进
代码中的每个缩进级别 必须 使用 4 个空格,且 绝对不能 使用 「 Tab 」 来缩进
一些编辑器可以将 「 Tab 」 转换成 4 个空格
2.5 关键字和类型
未来 PHP 版本中添加的任何关键字和新类型 必须 使用小写字母
必须 使用类型关键字的短写形式,例如 bool
代替 boolean
和 int
代替 integer
3. 声明语句、命名空间和导入语句
PHP 文件的头部可以包含多个不同的块。如果块存在,每个块 必须 由一个空白行分隔开,且 绝对不能 包含空白行。
每个块必须按照下面列出的顺序,不相关的块可以省略
- PHP 开始标记
<?php
- 文件级别的文档注释
- 一个或多个
declare
语句 - 命名空间声明
- 一个或多个类级别的
use
导入语句 - 一个或多个函数级别的
use
导入语句 - 一个或多个常量级别的
use
导入语句 - 剩余的代码
当一个文件混合了 HTML 和 PHP 代码的时候,上述的所有规则仍然适用。如果是这样,它们 必须 出现在文件的顶部,即使代码的其余部分包含 PHP 结束标记,最后才是 HTML 和 PHP 的混合代码
当 PHP 的开始标记 <?php
位于文件的第一行时,它 必须 独占一行,且不能添加其它语句,除非文件中包含了其它标记语言 ( 如 HTML )
导入语句 绝对不能 以前导的正斜杠 ( \
) 开始,且必须是全限定的
下面是一个完整的所有块结构的风格的范例
<?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 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; /** * FooBar 是一个范例类 */ class FooBar { // ... 其它 PHP 代码 ... }
绝对不能 使用超过两个深度的复合命名空间。
因此以下是允许的最大复合深度
<?php use Vendor\Package\SomeNamespace\{ SubnamespaceOne\ClassA, SubnamespaceOne\ClassB, SubnamespaceTwo\ClassY, ClassZ, };
下面这种用法则是不允许的
<?php use Vendor\Package\SomeNamespace\{ SubnamespaceOne\AnotherNamespace\ClassA, SubnamespaceOne\ClassB, ClassZ, };
在包含标记语言的文件中,如果想要在 PHP 开始标记和结束标记之外声明严格模式,那么就 必须 在文件顶部第一行进行声明
例如:
<?php declare(strict_types=1) ?> <html> <body> <?php // ... 其它的 PHP 代码 ... ?> </body> </html>
declare
语句 绝对不能 包含空格且 必须 明确的使用 declare(strict_types=1)
( 与以一个可选的分号结束 )
块 declare
语句是允许的且 必须 按照如下所示格式化,请留意大括号和空白
declare(ticks=1) { // 相同的代码 }
4. 类、属性和方法
此处的 「类」泛指所有的 「 class 类 」、「 接口 」以及「 traits 可复用代码块 」
任何结束大括号之后的同一行 绝对不能 紧跟着任何注释或语句
当实例化一个新类时,即使没有传递任何参数给构造函数,也始终 必须 添加圆括号
new Foo();
4.1 扩展和实现
extends
和 implements
声明 必须 和类名在同一行
开始大括号 必须 独占一行,且结束大括号 必须 在主体之后独占一行
开始大括号 必须 独占一行且之前或之后 绝对不能 有一个空白行
结束大括号 必须 独占一行,且之前 绝对不能 有一个空白行
<?php namespace Vendor\Package; use FooClass; use BarClass as Bar; use OtherVendor\OtherPackage\BazClass; class ClassName extends ParentClass implements \ArrayAccess, \Countable { // 常量、属性、方法 }
implements
和 extends
列表可以拆分成多行,每个后续行都必须缩进一次。当这样做时,列表中的第一项 必须 在下一行,并且每行 必须 只能有一个接口
<?php namespace Vendor\Package; use FooClass; use BarClass as Bar; use OtherVendor\OtherPackage\BazClass; class ClassName extends ParentClass implements \ArrayAccess, \Countable, \Serializable { // 常量、属性、方法 }
4.2 使用特质 ( trait )
在类的内部使用 use
关键字来实现特质 ( trait ) 的时候,use
声明 必须 在左大括号之后独占一行
<?php namespace Vendor\Package; use Vendor\Package\FirstTrait; class ClassName { use FirstTrait; }
导入到类中的每个特质 ( trait
) 都 必须 独占一行且添加 use
关键字。
<?php namespace Vendor\Package; use Vendor\Package\FirstTrait; use Vendor\Package\SecondTrait; use Vendor\Package\ThirdTrait; class ClassName { use FirstTrait; use SecondTrait; use ThirdTrait; }
如果类中的 use
导入语句之后没有任何东西,那么结束大括号必须在 use
导入语句之后独占一行
<?php namespace Vendor\Package; use Vendor\Package\FirstTrait; class ClassName { use FirstTrait; }
否则,在 use
导入语句之后 必须 有一个空白行
<?php namespace Vendor\Package; use Vendor\Package\FirstTrait; class ClassName { use FirstTrait; private $property; }
当使用 insteadof
和 as
操作符时,必须按照以下的方式使用它们
请留意缩进、空格和空行
<?php class Talker { use A, B, C { B::smallTalk insteadof A; A::bigTalk insteadof C; C::mediumTalk as FooBar; } }
4.3 属性和常量
所有的属性都 必须 声明可见性
所有的常量都必须声明可见性,如果你的项目支持的最低版本的 PHP 支持常量可见性声明的话 ( PHP 7.1 及以上版本 )
属性声明 绝对不能 使用 var
关键字
每个语句 绝对不能 有多于一个的属性声明
属性名 绝对不能 以下划线 ( _
) 开始来表示受保护的或私有的可见性声明。也就说,前缀的下划线根本没有任何特殊意义。
一个属性声明看起来如下所示
<?php namespace Vendor\Package; class ClassName { public $foo = null; }
4.4 方法和函数
所有方法都 必须 存在可见性声明
方法名 绝对不能 以下划线 ( _
) 开始来表示受保护的或私有的可见性声明。也就说,前缀的下划线根本没有任何特殊意义。
方法名和函数名之后 绝对不能 有空格。左大括号 必须 独占一行,右大括号 必须 在方法主体之后独占一行。左圆括号之后 绝对不能 有空格,右圆括号之前 绝对不能 有空格。
一个方法声明看起来如下所示
请留意括号、逗号、空格和大括号的位置
<?php namespace Vendor\Package; class ClassName { public function fooBarBaz($arg1, &$arg2, $arg3 = []) { // method body } }
一个函数声明看起来如下所示,请留意括号、逗号、空格和大括号的位置
<?php function fooBarBaz($arg1, &$arg2, $arg3 = []) { // 函数主体 }
4.5 方法和函数参数
在参数列表中,每个之前 绝对不能 有空格,每个逗号之后 必须 有一个空格
方法或函数中有默认值的参数 必须 放在参数列表的尾部
<?php namespace Vendor\Package; class ClassName { public function foo(int $arg1, &$arg2, $arg3 = []) { // method body } }
参数列表可以拆分成多行,每个后续行都必须缩进一次。当这样做时,列表中的第一项 必须 在下一行,并且每行 必须 只能有一个参数,右圆括号和开始大括号 必须 一起放在一个独立的行上,且它们之间要有一个空格分隔
<?php namespace Vendor\Package; class ClassName { public function aVeryLongMethodName( ClassTypeHint $arg1, &$arg2, array $arg3 = [] ) { // 方法内容 } }
当存在一个返回类型声明时,必须 在冒号之后有一个空格,然后紧跟一个类型声明。 冒号和声明 必须 与参数列表的右括号在同一行,且右括号和冒号之间没有任何空格
<?php declare(strict_types=1); namespace Vendor\Package; class ReturnTypeVariations { public function functionName(int $arg1, $arg2): string { return 'foo'; } public function anotherFunction( string $foo, string $bar, int $baz ): string { return 'foo'; } }
在可空的类型声明中,问号和类型之间 绝对不能 有空格
<?php declare(strict_types=1); namespace Vendor\Package; class ReturnTypeVariations { public function functionName(?string $arg1, ?int $arg2): ?string { return 'foo'; } }
4.6 abstract
、final
和 static
当存在时, abstract
和 final
声明 必须 在可见性声明之前
当存在时, static
声明 必须 在可见性声明之后
<?php namespace Vendor\Package; abstract class ClassName { protected static $foo; abstract protected function zim(); final public static function bar() { // 方法主体 } }
4.7 方法和函数调用
在调用一个方法或函数时,方法或函数名称与左括号之间 绝对不能 有空格,右括号之后 绝对不能 有空格,并且在右括号之前 绝对不能 有空格。 在参数列表中,每个逗号前面 绝对不能 有空格,每个逗号后面 必须 有一个空格
<?php bar(); $foo->bar($arg1); Foo::bar($arg2, $arg3);
参数列表可以分成多行。当这样做时,每个后续行都要缩进一次,列表中的第一项必须在下一行,并且每行 必须 只能有一个参数。
将一个参数拆分成多行 ( 可能是匿名函数或数组 ) ,并不是一个参数列表拆分
<?php $foo->bar( $longArgument, $longerArgument, $muchLongerArgument );
<?php somefunction($foo, $bar, [ // ... ], $baz); $app->get('/hello/{name}', function ($name) use ($app) { return 'Hello ' . $app->escape($name); });
5. 控制结构
控制结构的通用规则如下:
- 控制结构的关键字之后 必须 有一个空格
- 左小括号之后 绝对不能 有一个空格
- 右小括号之前 绝对不能 有一个空格
- 左括号和右括号之间 必须 有一个空格
- 结构主体 必须 缩进一次
- 结束大括号 必须 在主体之后独占一行
- 每个结构的主体 必须 用大括号包围
这标准化了结构的外观,并减少了新行增加到主体时的出错的可能性
5.1 if
、 elseif
、 else
一个 if
结构看起来如下所示
请留意括号、空格和大括号的位置,并且 else
和 elseif
与前一个内容的右大括号位于同一行。
<?php if ($expr1) { // if 内容 } elseif ($expr2) { // elseif 内容 } else { // else 内容; }
如果所有控制关键字都看起来像单个单词,那么 应该 使用关键字 else if
代替 else if
括号中的表达式 可以 分成多行。 这样做时,每个后续行至少缩进一次。第一个条件 必须 在下一行。 右圆括号和左大括号 必须 一起放在一个独立行上。 条件之间的布尔运算符 必须 始终在行的开始或结尾,而不是两者的混合。
<?php if ( $expr1 && $expr2 ) { // if 内容 } elseif ( $expr3 && $expr4 ) { // elseif 内容 }
5.2 switch
、 case
一个 switch
结构看起来如下所示
请留意括号、空格和大括号的位置
when fall-through is intentional 在一个非空的 case
内部。
case
语句 必须 从 switch
中缩进一次,并且 break
关键字 ( 或其它终止关键字 ) 必须 在与 case
主体相同的级别上缩进。 当一个非空的 case
要 fall-through 时 必须在其内部结尾添加有一个注释,例如 //no break
<?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 switch ( $expr1 && $expr2 ) { // 结构内容 }
5.3 while
、 do while
while
语句如下所示
请留意括号,空格和大括号的位置
<?php while ($expr) { // 结构内容 }
括号中的表达式 可以 分成多行。 这样做时,每个后续行至少缩进一次。第一个条件 必须 在下一行。 右圆括号和左大括号 必须 一起放在一个独立行上。 条件之间的布尔运算符 必须 始终在行的开始或结尾,而不是两者的混合。
<?php while ( $expr1 && $expr2 ) { // 结构内容 }
同样, do while
语句如下所示
请留意括号,空格和大括号的位置
<?php do { // 结构内容; } while ($expr);
括号中的表达式 可以 分成多行。 这样做时,每个后续行至少缩进一次。第一个条件 必须 在下一行。 右圆括号和左大括号 必须 一起放在一个独立行上。 条件之间的布尔运算符 必须 始终在行的开始或结尾,而不是两者的混合。
<?php do { // structure body; } while ( $expr1 && $expr2 );
5.4 for
一个 for
语句看起来如下所示
请留意括号,空格和大括号的位置
<?php for ($i = 0; $i < 10; $i++) { // 内容 }
括号中的表达式可以分成多行
当这样做时:
- 每个后续行至少缩进一次
- 第一个表达式必须在下一行
- 右圆括号和左大括号必须一起放在独立的行上,并在它们之间留出一个空格
<?php for ( $i = 0; $i < 10; $i++ ) { // 内容 }
5.5 foreach
一个 foreach
语句如下所示
请留意括号,空格和大括号的位置
<?php foreach ($iterable as $key => $value) { // foreach body }
5.6 try
、 catch
、finally
try-catch-finally
块看起来如下所示
请留意括号,空格和大括号的位置
<?php try { // try body } catch (FirstThrowableType $e) { // catch body } catch (OtherThrowableType $e) { // catch body } finally { // finally body }
6. 操作符
所有二元和三元 ( 不包括一元 ) 运算符之前和之后 必须 至少有一个空格。
包括所有的 算术运算符, 比较运算符, 赋值运算符, 位运算符,
逻辑运算符 ( 不包括 !
,它是一元元算符 ), 字符串连接符, 类型运算符,
特征运算符 (insteadof
和 as
),和单管道运算符 ( 例如,
ExceptionType1 | ExceptionType2 $e
)
其它操作符则是未定义的
例如:
<?php if ($a === $b) { $foo = $bar ?? $a ?? $b; } elseif ($a > $b) { $variable = $foo ? 'foo' : 'bar'; }
7. 闭包
闭包声明中 必须 在 function
关键字之后添加一个空格,并且在 use
关键字之前和之后都 必须 添加一个空格
开始大括号 必须 在同一行上,并且结束大括号 必须 在主体后面的下一行上。
参数列表或变量列表的左括号之后 绝对不能 有空格,参数列表或变量列表的右括号之前 绝对不能 有空格。
在参数列表和变量列表中,每个逗号前面 绝对不能 有空格,并且每个逗号后面 必须 有一个空格
闭包中所有具有默认值的参数 必须 放在没有默认值的后面
闭包声明如下所示。请留意括号,逗号,空格和大括号的位置:
<?php $closureWithArgs = function ($arg1, $arg2) { // 内容 }; $closureWithArgsAndVars = function ($arg1, $arg2) use ($var1, $var2) { // 内容 };
参数列表和变量列表 可以 分成多行,每个后续行都缩进一次。这样做时,列表中的第一项 必须 在下一行,并且每行 必须 只能一个参数或变量
当结尾列表 ( 无论是参数还是变量 ) 被分割成多行时,右圆括号和左大括号 必须 一起放一个单独的行上,并且使用一个空格分隔。
以下是关于在闭包中使用和不使用分割多行的参数列表和变量列表的范例
<?php // 参数列表 $longArgs_noVars = function ( $longArgument, $longerArgument, $muchLongerArgument ) { // 内容 }; // 变量列表 $noArgs_longVars = function () use ( $longVar1, $longerVar2, $muchLongerVar3 ) { // 内容 }; // 参数列表和变量列表 $longArgs_longVars = function ( $longArgument, $longerArgument, $muchLongerArgument ) use ( $longVar1, $longerVar2, $muchLongerVar3 ) { // 内容 }; // 参数列表和变量 $longArgs_shortVars = function ( $longArgument, $longerArgument, $muchLongerArgument ) use ($var1) { // 内容 }; // 参数和变量列表 $shortArgs_longVars = function ($arg) use ( $longVar1, $longerVar2, $muchLongerVar3 ) { // 内容 };
请注意,格式规则也适用于直接在函数或方法调用中使用闭包作为参数的情况
<?php $foo->bar( $arg1, function ($arg2) use ($var1) { // 内容 }, $arg3 );
8. 匿名类
匿名类 必须 遵循与上一节中的闭包相同的准则和原则
<?php $instance = new class {};
只要实现 接口
列表不换行,开头括号可以和 class
关键字在同一行
如果接口列表换行,括号 必须 放在紧跟在最后一个接口之后的行上
<?php // 括号在同一行 $instance = new class extends \Foo implements \HandleableInterface { // 类内容 }; // 下一行的括号 $instance = new class extends \Foo implements \ArrayAccess, \Countable, \Serializable { // 类内容 };