forrest79 / phpcs
Forrest79 - PHP 编程规范 - PHP 代码检查规则
v1.6
2024-03-27 22:41 UTC
Requires
- php: ^8.0
- slevomat/coding-standard: ^8.15
Requires (Dev)
- forrest79/phpcs-ignores: ^0.3
- nette/tester: ^2.5
- phpstan/phpstan: ^1.10
- phpstan/phpstan-strict-rules: ^1.5
README
基于(已不再开发)https://github.com/consistence/coding-standard 和 https://github.com/klapuch 的工作。谢谢!
安装
推荐通过 Composer 安装 Forrest79 PHP 编程规范。
composer require --dev forrest79/phpcs
Forrest79 PHP 编程规范需要 PHP 8.0。
如何使用它
- 创建您的 CS XML 并包含此 Forrest79CodingStandard
<?xml version="1.0"?> <ruleset name="MyOwnCS"> <rule ref="./Forrest79CodingStandard/ruleset.xml"/> </ruleset>
- 或者您可以使用带有完全限定全局函数(全局函数前有
\前缀,例如\is_array($x))和常量(全局常量前有\前缀,例如\FILE_APPEND)的版本
<?xml version="1.0"?> <ruleset name="MyOwnCS"> <rule ref="./Forrest79CodingStandard/ruleset-fully-qualified-global.xml"/> </ruleset>
- 您可以使用它并忽略一些规则。
<?xml version="1.0"?> <ruleset name="MyOwnCS"> <rule ref="./Forrest79CodingStandard/ruleset.xml"> <exclude name="Forrest79CodingStandard.Exceptions.ExceptionDeclaration.NotChainable"/> </rule> </ruleset>
- 或者具体文件。
<rule ref="Forrest79CodingStandard.Exceptions.ExceptionDeclaration.NotChainable"> <exclude-pattern>tests/Sniffs/TestCase.php</exclude-pattern> </rule>
PSR-4 设置
- 建议通过以下设置正确设置 PSR-4
<rule ref="SlevomatCodingStandard.Files.TypeNameMatchesFileName"> <properties> <property name="rootNamespaces" type="array" value=" apps/home/app/src=>Home\App, apps/home/tests/src=>Home\Tests, packages/internals/src=>Forrest79, tools/src=>Tools, "/> </properties> </rule>
- 键是目录,值是命名空间
自定义嗅探
Exceptions\ExceptionDeclarationSniff
Forrest79CodingStandard.Exceptions.ExceptionDeclarationSniff.NotEndingWithException
或者异常应该有 Exception 后缀。
Forrest79CodingStandard.Exceptions.ExceptionDeclarationSniff.NotChainable
异常应该是可链式的,最后一个构造函数参数必须是 \Throwable。
Forrest79CodingStandard.Exceptions.ExceptionDeclarationSniff.IncorrectExceptionDirectory
所有异常都应该在 Exceptions 子目录中。您可以通过设置更改子目录名称。
<rule ref="Forrest79CodingStandard.Exceptions.ExceptionDeclaration"> <properties> <property name="exceptionsDirectoryName" type="string" value="exceptions"/> </properties> </rule>
Methods\MethodStructureSniff
Forrest79CodingStandard.Methods.MethodStructureSniff.AlphabeticalOrder
检查类是否有按字母顺序的公共方法。通过设置指定文件
<rule ref="Forrest79CodingStandard.Methods.MethodStructure"> <properties> <property name="checkFiles" type="array" value=" apps/home/app/src/Configurator.php, "/> </properties> </rule>
NamingConventions\MethodStructureSniff
Forrest79CodingStandard.NamingConventions.ValidVariableNameSniff.NotCamelCaps
检查驼峰命名变量名。
Nette\FinalInjectMethodSniff
Forrest79CodingStandard.Nette.FinalInjectMethodSniff.MissingFinal
对于 Nette 框架。如果某些演示者或其他对象使用注入功能,类应该是 final 或 inject 方法应该是 final。
Nette\ParentCallSniff
Forrest79CodingStandard.Nette.ParentCallSniff.MissingParent
对于 Nette 框架。在演示者中,所有 beforeRender 方法应该调用其父类。
Nette\PresenterMethodsSniff
Forrest79CodingStandard.Nette.PresenterMethodsSniff.NotProtected
对于 Nette 框架。在演示者中,所有 startup、beforeRender 和 createComponent 方法应该是 protected。
PHP\CorrectCaseTypeSniff
Forrest79CodingStandard.PHP.CorrectCaseType
检查所有 PHP 类型都是小写,除了 NULL、TRUE 和 FALSE 必须是大写。基于 Generic.PHP.LowerCaseTypeSniff。
通用命名约定
- 避免缩写,仅在长名称不太可读时使用它们。
- 对于 2 个字母的快捷方式使用
UPPERCASE,对于更长的使用PascalCase。
<?php use Foo\IP\Bar; use Foo\Php\Bar; use Foo\UI\Bar; use Foo\Xml\Bar;
通用格式化约定
- 使用制表符缩进。
- 文件以一个空行结束
\n。 - 使用 Unix 风格(LF)行结束符
\n。 - 如果有一个信息列表,其中排序没有语义意义,则按字母顺序排序列表。
- 排序连接的单词(例如
PascalCase)考虑原始单词
- 排序连接的单词(例如
<?php use LogAware; use LogFactory; use LogLevel; use LogStandard; use LogableTrait; use LoggerInterface;
PHP 文件
- 只包含 PHP 代码(没有内联 HTML 等)。
- 文件没有关闭标签
?>。 - 在 PHP 开头标签之前没有字符(包括 BOM)。
- 使用长开头标签(总是
<?php, never<?)。 - 在开标签的行后有一个空行。
- 文件要么声明新的符号(类、函数、常量等)并导致没有其他副作用,要么执行具有副作用的逻辑,但不应该两者兼而有之。
- 通过启用
declare(strict_types = 1);使用严格的类型。- 这个声明放在开标签后面。
=操作符两侧没有空格。
<?php declare(strict_types=1); namespace Forrest79;
字符串
- 常用字符串使用撇号(
')编写。只有包含控制字符(如\n)的字符串可以使用双引号(")。 - 混合字符串和变量的连接使用
sprintf()。如果不需要原地字符串,则仅使用连接运算符(.)进行连接。.两侧各有一个空格,除非它在行的开头。- 字符串不包含变量(
"Hello $name!")。
<?php sprintf('%s/%s', $dir, $fileName); // vs $foo . $bar;
'SELECT `id`, `name` FROM `people`' . 'WHERE `role` = 1' . 'ORDER BY `name` ASC';
数组
- 使用简短数组语法(
[,])而不是array()语言结构。
命名空间
- 命名空间以
PascalCase编写。 - 每个文件只包含一个应用于整个文件的
namespace声明。- 在
namespace行之前有一个空行。
- 在
- 使用
use导入其他命名空间中的类型。use声明与namespace声明之间用一个空行分隔。- 每个
use声明只导入一个类型(每行一个导入)。 use声明按字母顺序排序。use声明永不以反斜杠(\)开头。
<?php namespace Forrest79; use Forrest79\Bar; use Forrest79\Foo; use DateTime; use Lorem\Amet; use Lorem\Ipsum\Dolor\Foo as DolorFoo; use Lorem\Sit; use ReflectionClass; use ReflectionMethod;
类型
- 类型名以
PascalCase编写。 - 类型名是名词。
- 类型放在命名空间中(不是全局空间)。
- 类型的开大括号和闭大括号始终在单独一行。
- 类型的所有部分都缩进一个制表符。
- 每个文件只定义一个类型,此文件的名称与类型的名称相同。
implements中引用的类型用逗号和空格分隔,并按字母顺序排序。- 在代码中引用的类型始终使用
Foo::class语法,而不是使用字符串。 - 如果静态访问中引用的类型是“当前”的,则使用
self/static而不是类型名称。 - 如果类型可以是null,则始终使用
|NULL而不是?-string|NULL而不是?string。
接口
- 接口从不以
I开头。
标量类型
- 在代码中使用简短类型名称(
int,bool)。这也适用于提供两种变体的PHP函数。
<?php if (!is_int($foo)) { return (int) $foo; }
- 始终使用
float而不是double或real。这也适用于提供两种变体的PHP函数。
变量
- 变量名以
camelCase编写。 - 从不使用
global关键字声明全局变量。
属性
- 所有变量规则适用。
- 所有属性都有显式声明的可见性,使用
private、protected或public。 - 从不使用
var。 - 每个语句只声明一个属性。
常量
- 常量名以
UPPER_CASE编写。 - 常量仅在类中使用
const定义,全局常量从不定义。 - 常量有显式声明的可见性,使用
private、protected或public。 - 如果有更多“属于一起”的常量,可以省略它们之间的空行。
<?php class Foo { const FOO = 'foo'; const VISIBILITY_PRIVATE = 'private'; const VISIBILITY_PROTECTED = 'protected'; const VISIBILITY_PUBLIC = 'public'; }
函数
- 函数名以
camelCase编写。- 对内置PHP函数的调用是此规则的例外,并以
snake_case编写。
- 对内置PHP函数的调用是此规则的例外,并以
- 函数名和开括号之间没有空格。
- 类型的开大括号和闭大括号始终在单独一行。
- 例外:匿名函数。
- 从不声明全局函数,它们应在(静态)类内部定义。
- 命名函数(非匿名)从不包含在其他函数内部。
参数列表
- 在可能的情况下应定义类型提示(包括标量类型提示)。
- 使用可空类型(
string|NULL)允许将null传递给类型提示参数。
- 使用可空类型(
- 括号后面没有空格,括号前面也没有空格。
- 函数声明和函数调用中的参数用逗号分隔,逗号后面跟一个空格(
,)。
<?php class X { public function __construct(Foo $foo, string $string) { // ... } }
- 多行参数的函数声明和调用
- 每行只有一个参数。
<?php class X { public function __construct( Foo $foo, string $string, ) { // ... } } new X( $foo, $string, );
- 仅在需要时使用默认参数值,用于表示可选参数(仅在列表末尾)或允许将null传递给类型提示参数。
- 使用可空类型(
string|NULL)允许将null传递给类型提示参数,因此默认参数仅用于可选参数。 - 对于标量参数,默认参数仅用于可选参数,不允许传递null(见下文详细示例)。
- 使用可空类型(
<?php class X { /** * @param \Foo $a required type argument * @param \Foo|NULL $b required argument, but nullable type needed * @param string $c required scalar argument * @param string|NULL $d required argument with nullable scalar * @param string $e optional nullable scalar argument * @param string|NULL $f optional nullable scalar argument */ public function __construct( Foo $a, Foo|NULL $b, string $c, string|NULL $d, string $e = '', string|NULL $f = NULL ) { // ... } }
- 可变参数的格式为:
@param \Foo ...$foo。
返回类型
- 在可能的情况下应定义类型提示(包括标量类型提示)。
- 使用可空类型(
string|NULL)允许返回null。
- 使用可空类型(
- 括号后面没有空格,冒号紧随其后,然后冒号和类型之间有一个空格。
<?php class X { public function getFoo(): Foo { // ... } }
- 如果没有返回值,必须指定
void返回类型。
<?php class X { public function process(Foo $foo): void { $foo->bar(); } }
- 当方法中断脚本时,必须指定
never返回类型。
<?php class X { public function process(Foo $foo): never { exit(1); } }
匿名函数
function关键字和左括号之间有一个空格。- 左大括号不在下一行。
use关键字前后有一个空格。
<?php array_walk($foo, function (Item $item) use ($bar): string { // ... });
方法
- 所有方法都明确声明了可见性,使用
private、protected或public。 - 声明中的关键字顺序
final/abstract,private/protected/public,static
- 构造函数始终使用
__construct名称定义,永远不使用旧的PHP行为——使用与类名相同的名称。 - 每个函数前后都有一个新行(因此两个函数之间有两个新行)。
<?php class X { final public static function foo(): void { // ... } abstract public static function bar(): void { // ... } }
控制结构
- 条件语句被括号包围。
- 语句中的括号前后没有空格。
- 语句周围的括号前后有一个空格。
- 左大括号放置在条件语句的同一行。
- 使用
else if而不是elseif。 switch中的case语句缩进一个制表符,其内容在下一行再次缩进一个制表符。switch中的case语句以冒号:结尾。
<?php if ($foo) { // ... } if ( $foo && $bar ) { // ... } switch ($foo) { case 1: case 2: // ... break; default: // ... }
- 在非空case体中故意执行穿透时,在
switch中必须有一个注释,例如// no break。 - 禁止控制结构的空体。
- 异常是
catch,但必须有解释情况的注释。
- 异常是
表达式
- 在所有运算符之后有一个空格。在运算符之前也有一个空格,除非它在行首。
- 逻辑运算符始终使用
&&和||,而不是使用and和or。 - 所有关键字都是小写,例外是
TRUE、FALSE和NULL。 - 默认使用严格比较(
===),如果需要使用==,通常应给出解释情况的注释。- 应避免使用魔法PHP类型转换 - 错误:
($foo),正确:($foo !== NULL)- 只有包含布尔值的表达式应写成($foo)形式。 - 同样适用于
empty函数,因此它是禁止的。
- 应避免使用魔法PHP类型转换 - 错误:
- 不应使用Yoda条件。
- 如果表达式需要写多行,运算符应位于行的开头。
<?php if ( ($lorem >= 3 && $lorem <= 5) || $ipsum !== NULL ) { // ... }
- 每行只有一个语句。
- 可以使用一个空白行来分隔其他语句。
- 如果连续有多个方法调用,并且需要将它们写多行,则所有方法调用都应缩进(包括第一个)。
<?php $lorem ->ipsum() ->dolor() ->sit() ->amet();
- 在
new语句中的括号始终存在,即使构造函数没有参数。
<?php new Foo();
- 类型转换后有一个空格,括号内没有空格。
(binary)和(unset)类型转换是禁止的。
- 对于递增和递减,分别使用
++/--运算符而不是“手动”加法/减法。 - 所有静态符号都应该仅通过
::访问,永远不要使用$this。 echo、print等允许同时使用echo('...')和echo ''语法的语句始终不使用括号,并在关键字后有一个空格。
闭包和可调用对象
- 优先使用闭包而不是可调用对象(实现第三方接口时可能需要可调用对象)。
- 使用
$closure()而不是使用函数来调用闭包。call_user_func永远不会需要。- 从PHP 5.6开始不需要
call_user_func_array- 因为引入了参数解包。
<?php function foo($foo, Closure $callback): void { // ... $callback($bar); // ... } foo('foo', function (Bar $bar): void { // ... });
<?php function foo($foo, Closure $callback): void { // ... $callback(...$barArray); // ... } foo('foo', function (Bar ...$bars): void { // ... });
异常
- 类型名始终以Exception结尾。
- 异常放置在名为
Exceptions的单独目录中。 - 类名描述使用案例,应该非常具体。
- 继承用于实现目的(不是用于创建层次结构) - 例如
Forrest79\PhpException,其中跳过了$code参数。 - 构造函数只需要需要的参数,其余的消息在构造函数中组成。
- 所有异常都应该支持异常链(允许将
\Throwable作为可选的最后参数)。 - 参数应存储在私有属性中,并通过公共方法提供,以便异常处理可以使用这些数据。
- 所有异常都应该支持异常链(允许将
<?php namespace Forrest79\Foo; use Forrest79; class LoremException extends Forrest79\PhpException { private strinf $lorem; public function __construct(string $lorem, \Throwable $previous = NULL) { parent::__construct(sprintf('%s ipsum dolor sit amet', $lorem), $previous); $this->lorem = $lorem; } public function getLorem(): string { return $this->lorem; } }
##注释
- 使用
//内联样式注释,永远不要使用#。 - 内联注释在
//和文本之间有一个空格。 - 如果没有明确的需要写某事,应省略它 - 应优先使用命名良好的类、变量、方法和参数。
- 永远不会出现“注释掉”的代码。
PHPDoc
类型和方法的结构
<?php /** * Short description - one line (optional) * * Long description (optional) * * Documentation annotations (optional) * * Code analysis annotations (optional) * * Application annotations (optional) * * @param string $foo only if some optional description is needed * @param int $bar only if some optional description is needed * @return bool only if some optional description is needed * @throws \MyException\BarException * @throws \MyException\FooException */ public function myMethod(string $foo, int $bar): bool;
属性和常量的结构
<?php /** * Short description - one line (optional) * * Long description (optional) * * Documentation annotations (optional) * * Code analysis annotations (optional) * * Application annotations (optional) * * @var string optional description */ private $foo;
注解块
- 不同类型的注解一起分组(与其他块由空行分隔)。
- 参见上面的结构。
简短+长描述
- 可选。
- 如果没有明确的需要写某事,应省略它 - 应优先使用命名良好的类、变量、方法和参数。
- 仅重述方法名称(等)的描述永远不会写。
- 清晰的代码比长的解释更好。
- 简短描述只有一行(不包含句号)。
- 长描述可以包含示例用法。
- 在长描述中可以使用格式化(例如,使用HTML)。
文档注解
- 可选。
- 带有文档元数据的PHPDoc注解,如
@author、@copyright、@see、@link等。
代码分析注解
- 永远不会使用,在需要时使用PHPCS-Ignores。
应用程序注解
- 可选。
- 对应用程序具有功能意义的注解,例如Symfony、Doctrine和自定义注解。
多行注解
- 遵循处理将语句分隔到多行的通用格式化规则。
- 缩进使用两个空格。
/** * @ManyToMany(targetEntity="Phonenumber") * @JoinTable( * name="users_phonenumbers", * joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")}, * inverseJoinColumns={@JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)}, * ) * @Foo **/
@param、@return、@var允许的类型
允许类型的列表(使用长变量)
intboolstringfloatresourcenullobjectmixed- array/collection(见下文)
- type(见下文)
不同的类型用|分隔。
Mixed
- 当不知道类型时使用。
Array/collection
- 写为
array<int, type>、array<string, type>或list<type>。 - 如果值是多种类型,则使用
array<int, mixed>(即使不知道类型也可以使用)。 - 如果预期是关联数组(或Map),在描述中应包含使用格式的描述,例如:
array<string, string> $names 格式:lastName(string) => firstName (string)。 - 如果有更多嵌套的数组/集合,则使用更多的
array<>来表示,例如:array<int, array<string, integer>>表示整数数组的数组。
类型
- 带有前导反斜杠的全限定名。
- 如果引用的类型是“当前”的类型,则使用
self/static而不是类型名称。
<?php use DateTime; use DateTimeImmutable; /** * @param DateTimeImmutable $date calendar date * @param array<string> $events * @param int|NULL $interval * @return DateTime */ public function myMethod(DateTimeImmutable $date, array $events, int $interval = NULL): DateTime { // ... }
@param
- 当类型提示与注解相同时,可以省略。
- 注解的顺序与参数列表中定义的顺序相同。
<?php use DateTime; /** * @param string $foo optional description * @param int $bar optional description * @param DateTime ...$dates optional description */ public function myMethod(string $foo, int $bar, DateTime ...$dates) { // ... }
@return
- 如果方法中没有
return语句,则不存在@return。 - 当类型提示与注解相同时,可以省略。
@var
- 如果
@var注解是唯一的注解且没有 PHPDoc 的长描述,则使用一行格式。
<?php /** @var string optional description */ private $foo;
- 内联
@var用于定义变量的类型,其中类型不明确。- 使用文档块注释
/** ... */。 - 首先定义类型,然后是变量名称。
- 使用文档块注释
<?php /** @var Foo $foo optional description */ $foo = $container->getService('foo');
@throws
- 对于实现的方法,从不使用
@throws。 @throws仅在接口或抽象方法中作为定义合同的一部分使用。@throws注解按字母顺序排序(根据异常名称)。
常量
@var不用于常量(类型由其值定义)。- 如果没有需要其他注解或描述,则省略 PHPDoc。