# PSR-12:扩展编码风格
本文件中的关键词“必须”,“不得”,“必须”,“应该”,“不应该”,“应该”,“不应该”,“推荐”,“可以”和“可选”按照[RFC 2119中的](http://tools.ietf.org/html/rfc2119)描述进行解释。
## [概观](http://phpfig.p2hp.com/psr/psr-12/#overview)
该规范扩展,扩充和替换[PSR-2](http://phpfig.p2hp.com/psr/psr-2/)编码风格指南,并要求遵守基本编码标准[PSR-1](http://phpfig.p2hp.com/psr/psr-1/)。
### [以前的语言版本](http://phpfig.p2hp.com/psr/psr-12/#previous-language-versions)
在本文档中,如果项目支持的PHP版本中不存在任何指令,则可以忽略这些指令。
### [例子](http://phpfig.p2hp.com/psr/psr-12/#example)
此示例包含以下一些规则作为快速概述:
~~~
<?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()
{
// method body
}
}
~~~
## [2.一般](http://phpfig.p2hp.com/psr/psr-12/#2-general)
### [2.1基本编码标准](http://phpfig.p2hp.com/psr/psr-12/#21-basic-coding-standard)
代码必须遵循[PSR-1中](http://phpfig.p2hp.com/psr/psr-1/)列出的所有规则。
PSR-1中的“StudlyCaps”一词必须解释为PascalCase,其中每个单词的首字母大写,包括第一个字母。
### [2.2文件](http://phpfig.p2hp.com/psr/psr-12/#22-files)
所有PHP文件必须仅使用Unix LF(换行)行结尾。
所有PHP文件必须以非空白行结束,以单个LF结束。
`?>`必须从仅包含PHP的文件中省略结束标记。
### [2.3行](http://phpfig.p2hp.com/psr/psr-12/#23-lines)
行长度不得有硬性限制。
行长度的软限制必须是120个字符。
行不应超过80个字符;超过的行应该被分成多个后续行,每行不超过80个字符。
在行的末尾不得有尾随空格。
可以添加空行以提高可读性并指示相关的代码块,除非明确禁止。
每行不得超过一个语句。
### [2.4缩进](http://phpfig.p2hp.com/psr/psr-12/#24-indenting)
代码必须为每个缩进级别使用4个空格的缩进,并且不得使用制表符进行缩进。
### [2.5关键字和类型](http://phpfig.p2hp.com/psr/psr-12/#25-keywords-and-types)
所有PHP保留的关键字和类型[\[1\]](http://php.net/manual/en/reserved.keywords.php)[\[2\]](http://php.net/manual/en/reserved.other-reserved-words.php)必须是小写的。
添加到未来PHP版本的任何新类型和关键字必须是小写的。
类型的关键字的简短形式必须是即使用`bool`代替`boolean`,`int`而不是`integer`等
## [3.声明语句,命名空间和导入语句](http://phpfig.p2hp.com/psr/psr-12/#3-declare-statements-namespace-and-import-statements)
PHP文件的标头可能包含许多不同的块。如果存在,下面的每个块必须用一个空行分隔,并且不得包含空行。尽管可以省略不相关的块,但每个块必须按下面列出的顺序排列。
* 打开`<?php`标签。
* 文件级docblock。
* 一个或多个声明语句。
* 文件的名称空间声明。
* 一个或多个基于类的`use`import语句。
* 一个或多个基于函数的`use`import语句。
* 一个或多个基于常量的`use`import语句。
* 文件中的其余代码。
当文件包含HTML和PHP的混合时,仍可以使用上述任何部分。如果是这样,它们必须出现在文件的顶部,即使代码的其余部分包含一个结束的PHP标记,然后是HTML和PHP的混合。
当开始`<?php`标记位于文件的第一行时,它必须在它自己的行上而没有其他语句,除非它是包含PHP开始和结束标记之外的标记的文件。
导入语句绝不能以前导反斜杠开头,因为它们必须始终是完全限定的。
以下示例说明了所有块的完整列表:
~~~
<?php
/**
* This file contains an example of coding styles.
*/
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 is an example class.
*/
class FooBar
{
// ... additional PHP code ...
}
~~~
绝不能使用深度超过2的复合名称空间。因此,以下是允许的最大复合深度:
~~~
<?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标记,严格类型声明和结束标记。
例如:
~~~
<?php declare(strict_types=1) ?>
<html>
<body>
<?php
// ... additional PHP code ...
?>
</body>
</html>
~~~
声明语句必须不包含空格,并且必须完全`declare(strict_types=1)`(使用可选的分号终止符)。
允许块声明语句,并且必须格式化如下。注意括号和间距的位置:
~~~
declare(ticks=1) {
// some code
}
~~~
## [4.类,属性和方法](http://phpfig.p2hp.com/psr/psr-12/#4-classes-properties-and-methods)
术语“类”指的是所有类,接口和traits。
任何结束括号不得在同一行上跟随任何注释或声明。
在实例化一个新类时,即使没有传递给构造函数的参数,也必须始终存在括号。
~~~
new Foo();
~~~
### [4.1继承和实现](http://phpfig.p2hp.com/psr/psr-12/#41-extends-and-implements)
在`extends`和`implements`关键字必须在同一行类名来声明。
类的左括号必须转到自己的行;类的右括号必须在正文之后的下一行上。。
左大括号必须位于自己的行上,并且不得在空白行的前面或后面加上空白行。 。
右括号必须位于自己的行上,并且不能前面有一张空白行。 。
~~~
<?php
namespace Vendor\Package;
use FooClass;
use BarClass as Bar;
use OtherVendor\OtherPackage\BazClass;
class ClassName extends ParentClass implements \ArrayAccess, \Countable
{
// constants, properties, methods
}
~~~
`implements`在接口的情况下,列表`extends`可以分为多行,每行后续行缩进一次。这样做时,列表中的第一项必须在下一行,并且每行必须只有一个接口。
~~~
<?php
namespace Vendor\Package;
use FooClass;
use BarClass as Bar;
use OtherVendor\OtherPackage\BazClass;
class ClassName extends ParentClass implements
\ArrayAccess,
\Countable,
\Serializable
{
// constants, properties, methods
}
~~~
### [4.2使用traits](http://phpfig.p2hp.com/psr/psr-12/#42-using-traits)
在`use`实现traits的类中使用的关键字必须在左括号后的下一行声明。
~~~
<?php
namespace Vendor\Package;
use Vendor\Package\FirstTrait;
class ClassName
{
use FirstTrait;
}
~~~
导入到类中的每个单独的Trait必须包含在每行一个,每个包含必须有自己的`use`import语句。
~~~
<?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`import语句之后没有任何内容时,类右括号必须在`use`import语句之后的下一行。
~~~
<?php
namespace Vendor\Package;
use Vendor\Package\FirstTrait;
class ClassName
{
use FirstTrait;
}
~~~
否则,它必须在`use`import语句后面有一个空行。
~~~
<?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属性和常量](http://phpfig.p2hp.com/psr/psr-12/#43-properties-and-constants)
必须在所有属性上声明可见性。
如果您的项目PHP最低版本支持持续可见性(PHP 7.1或更高版本),则必须在所有常量上声明可见性。
该`var`关键字不能被用于声明属性。
每个声明不得超过一个属性。
属性名称不得以单个下划线为前缀,以指示受保护或私有可见性。也就是说,下划线前缀明确没有任何意义。
类型声明和属性名称之间必须有一个空格。
属性声明如下所示:
~~~
<?php
namespace Vendor\Package;
class ClassName
{
public $foo = null;
public static int $bar = 0;
}
~~~
### [4.4方法和函数](http://phpfig.p2hp.com/psr/psr-12/#44-methods-and-functions)
必须在所有方法上声明可见性。
方法名称不得以单个下划线为前缀,以指示受保护或私有可见性。也就是说,下划线前缀明确没有任何意义。
不得在方法名称后用空格声明方法和函数名称。左大括号必须位于自己的行上,而右大括号必须位于正文之后的下一行上。。在左括号后面不能有空格,并且在右括号之前不能有空格。
方法声明如下所示。请注意括号,逗号,空格和大括号的位置:
~~~
<?php
namespace Vendor\Package;
class ClassName
{
public function fooBarBaz($arg1, &$arg2, $arg3 = [])
{
// method body
}
}
~~~
函数声明如下所示。请注意括号,逗号,空格和大括号的位置:
~~~
<?php
function fooBarBaz($arg1, &$arg2, $arg3 = [])
{
// function body
}
~~~
### [4.5方法和函数参数](http://phpfig.p2hp.com/psr/psr-12/#45-method-and-function-arguments)
在参数列表中,每个逗号之前不得有空格,每个逗号后必须有一个空格。
具有默认值的方法和函数参数必须位于参数列表的末尾。
~~~
<?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 = []
) {
// method body
}
}
~~~
如果存在返回类型声明,则冒号后面必须有一个空格,后跟类型声明。冒号和声明必须与参数列表右括号位于同一行,两个字符之间没有空格。
~~~
<?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';
}
}
~~~
在`&`参数之前使用引用运算符时,其后面不能有空格,就像前面的例子一样。
在可变三点运算符和参数名称之间不能有空格:
~~~
public function process(string $algorithm, ...$parts)
{
// processing
}
~~~
当组合引用运算符和可变三点运算符时,它们之间不能有任何空格:
~~~
public function process(string $algorithm, &...$parts)
{
// processing
}
~~~
### [4.6abstract,final和static](http://phpfig.p2hp.com/psr/psr-12/#46-abstract-final-and-static)
如果存在,`abstract`并`final`声明必须先于可见性声明。
如果存在,`static`声明必须在可见性声明之后。
~~~
<?php
namespace Vendor\Package;
abstract class ClassName
{
protected static $foo;
abstract protected function zim();
final public static function bar()
{
// method body
}
}
~~~
### [4.7方法和函数调用](http://phpfig.p2hp.com/psr/psr-12/#47-method-and-function-calls)
在进行方法或函数调用时,方法或函数名称与左括号之间不能有空格,在左括号后面不能有空格,并且在右括号之前不能有空格。在参数列表中,每个逗号之前不得有空格,每个逗号后必须有一个空格。
~~~
<?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.控制结构](http://phpfig.p2hp.com/psr/psr-12/#5-control-structures)
控制结构的一般样式规则如下:
* 控制结构关键字后面必须有一个空格
* 在左括号后面不能有空格
* 在右括号之前不能有空格
* 在右括号和左括号之间必须有一个空格
* 结构体必须缩进一次
* 主体必须在左大括号后的下一行
* 右括号必须位于正文之后的下一行
每个结构的主体必须用大括号括起来。这标准化了结构的外观,并降低了新行添加到正文中时引入错误的可能性。
### [5.1 if,elseif,else](http://phpfig.p2hp.com/psr/psr-12/#51-if-elseif-else)
一个`if`结构如下所示。注意括号,空格和大括号的位置;并且`else`与`elseif`与上一正文中的右括号位于同一行。。
~~~
<?php
if ($expr1) {
// if body
} elseif ($expr2) {
// elseif body
} else {
// else body;
}
~~~
应该使用`elseif`关键字而不是`else if`使所有控制关键字看起来像单个单词。
括号中的表达式可以分为多行,其中每个后续行至少缩进一次。这样做时,第一个条件必须在下一行。右括号和左括号必须放在一起,它们之间有一个空格。条件之间的布尔运算符必须始终位于行的开头或结尾,而不是两者的混合。
~~~
<?php
if (
$expr1
&& $expr2
) {
// if body
} elseif (
$expr3
&& $expr4
) {
// elseif body
}
~~~
### [5.2 switch, case](http://phpfig.p2hp.com/psr/psr-12/#52-switch-case)
一个`switch`结构如下所示。请注意括号,空格和大括号的位置。`case`语句必须从`switch`缩进一次,并且`break`关键字(或其他终止关键字)必须缩进与`case`正文相同的级别。必须有一个注释,例如`// no break`在非空`case`体中有意识地跳过时。
~~~
<?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
) {
// structure body
}
~~~
### [5.3while, do while](http://phpfig.p2hp.com/psr/psr-12/#53-while-do-while)
一个`while`声明如下所示。请注意括号,空格和大括号的位置。
~~~
<?php
while ($expr) {
// structure body
}
~~~
括号中的表达式可以分为多行,其中每个后续行至少缩进一次。这样做时,第一个条件必须在下一行。右括号和左括号必须放在一起,它们之间有一个空格。条件之间的布尔运算符必须始终位于行的开头或结尾,而不是两者的混合。
~~~
<?php
while (
$expr1
&& $expr2
) {
// structure body
}
~~~
同样,`do while`语句如下所示。请注意括号,空格和大括号的位置。
~~~
<?php
do {
// structure body;
} while ($expr);
~~~
括号中的表达式可以分为多行,其中每个后续行至少缩进一次。这样做时,第一个条件必须在下一行。条件之间的布尔运算符必须始终位于行的开头或结尾,而不是两者的混合。
~~~
<?php
do {
// structure body;
} while (
$expr1
&& $expr2
);
~~~
### [5.4 for](http://phpfig.p2hp.com/psr/psr-12/#54-for)
一个`for`声明如下所示。请注意括号,空格和大括号的位置。
~~~
<?php
for ($i = 0; $i < 10; $i++) {
// for body
}
~~~
括号中的表达式可以分为多行,其中每个后续行至少缩进一次。这样做时,第一个表达式必须在下一行。右括号和左括号必须放在一起,它们之间有一个空格。
~~~
<?php
for (
$i = 0;
$i < 10;
$i++
) {
// for body
}
~~~
### [5.5 foreach](http://phpfig.p2hp.com/psr/psr-12/#55-foreach)
一个`foreach`声明如下所示。请注意括号,空格和大括号的位置。
~~~
<?php
foreach ($iterable as $key => $value) {
// foreach body
}
~~~
### [5.6try, catch, finally](http://phpfig.p2hp.com/psr/psr-12/#56-try-catch-finally)
一个`try-catch-finally`块如下所示。请注意括号,空格和大括号的位置。
~~~
<?php
try {
// try body
} catch (FirstThrowableType $e) {
// catch body
} catch (OtherThrowableType | AnotherThrowableType $e) {
// catch body
} finally {
// finally body
}
~~~
## [6.运算符](http://phpfig.p2hp.com/psr/psr-12/#6-operators)
运算符的样式规则按arity(它们采用的操作数的数量)进行分组。
当运算符周围允许空间时,可以使用多个空格用于可读性目的。
此处未描述的所有运算符都未定义。
### [6.1。单一运算符](http://phpfig.p2hp.com/psr/psr-12/#61-unary-operators)
递增/递减运算符在运算符和操作数之间不能有任何空格。
类型转换运算符在括号内不能有任何空格:
~~~
$intValue = (int) $input;
~~~
### [6.2。二元运算符](http://phpfig.p2hp.com/psr/psr-12/#62-binary-operators)
所有二元[算术](http://php.net/manual/en/language.operators.arithmetic.php),[比较](http://php.net/manual/en/language.operators.comparison.php),[赋值](http://php.net/manual/en/language.operators.assignment.php),[按位](http://php.net/manual/en/language.operators.bitwise.php),[逻辑](http://php.net/manual/en/language.operators.logical.php),[字符串](http://php.net/manual/en/language.operators.string.php)和[类型](http://php.net/manual/en/language.operators.type.php)运算符必须至少前后一个空格:
~~~
if ($a === $b) {
$foo = $bar ?? $a ?? $b;
} elseif ($a > $b) {
$foo = $a + $b * $c;
}
~~~
### [6.3。三元运算符](http://phpfig.p2hp.com/psr/psr-12/#63-ternary-operators)
条件运算符,也简称为三元运算符,必须在`?`和`:`字符周围至少有一个空格:
~~~
$variable = $foo ? 'foo' : 'bar';
~~~
当省略条件运算符的中间操作数时,运算符必须遵循与其他二元[比较运算符](http://php.net/manual/en/language.operators.comparison.php)相同的样式规则:
~~~
$variable = $foo ?: 'bar';
~~~
## [7.闭包](http://phpfig.p2hp.com/psr/psr-12/#7-closures)
闭包必须在`function`关键字之后用空格声明,并在关键字之前和之后用空格声明`use`。
左大括号必须位于同一行上,而右大括号必须位于正文之后的下一行上。
在参数列表或变量列表的左括号之后不能有空格,并且在参数列表或变量列表的右括号之前不能有空格。
在参数列表和变量列表中,每个逗号前不能有空格,每个逗号后必须有一个空格。
具有默认值的闭包参数必须位于参数列表的末尾。
如果存在返回类型,则必须遵循与正常函数和方法相同的规则;如果`use`关键字存在,冒号必须遵循`use`列表右括号,两个字符之间没有空格。
闭包声明如下所示。请注意括号,逗号,空格和大括号的位置:
~~~
<?php
$closureWithArgs = function ($arg1, $arg2) {
// body
};
$closureWithArgsAndVars = function ($arg1, $arg2) use ($var1, $var2) {
// body
};
$closureWithArgsVarsAndReturn = function ($arg1, $arg2) use ($var1, $var2): bool {
// body
};
~~~
参数列表和变量列表可以分为多行,每行后续行缩进一次。这样做时,列表中的第一项必须在下一行,并且每行必须只有一个参数或变量。
当结束列表(无论是参数还是变量)被分割成多行时,右括号和左括号必须放在一起,在它们自己的行上,它们之间有一个空格。
以下是包含和不包含参数列表的闭包的示例,以及跨多行分割的变量列表。
~~~
<?php
$longArgs_noVars = function (
$longArgument,
$longerArgument,
$muchLongerArgument
) {
// body
};
$noArgs_longVars = function () use (
$longVar1,
$longerVar2,
$muchLongerVar3
) {
// body
};
$longArgs_longVars = function (
$longArgument,
$longerArgument,
$muchLongerArgument
) use (
$longVar1,
$longerVar2,
$muchLongerVar3
) {
// body
};
$longArgs_shortVars = function (
$longArgument,
$longerArgument,
$muchLongerArgument
) use ($var1) {
// body
};
$shortArgs_longVars = function ($arg) use (
$longVar1,
$longerVar2,
$muchLongerVar3
) {
// body
};
~~~
请注意,当函数或方法调用中的闭包直接用作参数时,格式设置规则也适用。
~~~
<?php
$foo->bar(
$arg1,
function ($arg2) use ($var1) {
// body
},
$arg3
);
~~~
## [8.匿名类](http://phpfig.p2hp.com/psr/psr-12/#8-anonymous-classes)
匿名类必须遵循与上一节中的闭包相同的准则和原则。
~~~
<?php
$instance = new class {};
~~~
`class`只要`implements`接口列表不换行,左大括号就可以与关键字位于同一行。如果接口列表换行,则必须将括号放在紧接最后一个接口后面的行上。
~~~
<?php
// Brace on the same line
$instance = new class extends \Foo implements \HandleableInterface {
// Class content
};
// Brace on the next line
$instance = new class extends \Foo implements
\ArrayAccess,
\Countable,
\Serializable
{
// Class content
};
~~~