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
允许的类型
允许类型的列表(使用长变量)
int
bool
string
float
resource
null
object
mixed
- 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。