forrest79/phpcs

Forrest79 - PHP 编程规范 - PHP 代码检查规则

v1.6 2024-03-27 22:41 UTC

This package is auto-updated.

Last update: 2024-09-13 20:27:19 UTC


README

Latest Stable Version Monthly Downloads License Build

基于(已不再开发)https://github.com/consistence/coding-standardhttps://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 框架。如果某些演示者或其他对象使用注入功能,类应该是 finalinject 方法应该是 final

Nette\ParentCallSniff

Forrest79CodingStandard.Nette.ParentCallSniff.MissingParent

对于 Nette 框架。在演示者中,所有 beforeRender 方法应该调用其父类。

Nette\PresenterMethodsSniff

Forrest79CodingStandard.Nette.PresenterMethodsSniff.NotProtected

对于 Nette 框架。在演示者中,所有 startupbeforeRendercreateComponent 方法应该是 protected

PHP\CorrectCaseTypeSniff

Forrest79CodingStandard.PHP.CorrectCaseType

检查所有 PHP 类型都是小写,除了 NULLTRUEFALSE 必须是大写。基于 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开头。

标量类型

  • 在代码中使用简短类型名称(intbool)。这也适用于提供两种变体的PHP函数。
<?php

if (!is_int($foo)) {
	return (int) $foo;
}
  • 始终使用float而不是doublereal。这也适用于提供两种变体的PHP函数。

变量

  • 变量名以camelCase编写。
  • 从不使用global关键字声明全局变量。

属性

  • 所有变量规则适用。
  • 所有属性都有显式声明的可见性,使用privateprotectedpublic
  • 从不使用var
  • 每个语句只声明一个属性。

常量

  • 常量名以UPPER_CASE编写。
  • 常量仅在类中使用const定义,全局常量从不定义。
  • 常量有显式声明的可见性,使用privateprotectedpublic
  • 如果有更多“属于一起”的常量,可以省略它们之间的空行。
<?php

class Foo
{
	const FOO = 'foo';

	const VISIBILITY_PRIVATE = 'private';
	const VISIBILITY_PROTECTED = 'protected';
	const VISIBILITY_PUBLIC = 'public';

}

函数

  • 函数名以camelCase编写。
    • 对内置PHP函数的调用是此规则的例外,并以snake_case编写。
  • 函数名和开括号之间没有空格。
  • 类型的开大括号和闭大括号始终在单独一行。
    • 例外:匿名函数。
  • 从不声明全局函数,它们应在(静态)类内部定义。
  • 命名函数(非匿名)从不包含在其他函数内部。

参数列表

  • 在可能的情况下应定义类型提示(包括标量类型提示)。
    • 使用可空类型(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 {
	// ...
});

方法

  • 所有方法都明确声明了可见性,使用privateprotectedpublic
  • 声明中的关键字顺序
    1. final/abstract,
    2. private/protected/public,
    3. 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,但必须有解释情况的注释。

表达式

  • 在所有运算符之后有一个空格。在运算符之前也有一个空格,除非它在行首。
  • 逻辑运算符始终使用&&||,而不是使用andor
  • 所有关键字都是小写,例外是TRUEFALSENULL
  • 默认使用严格比较(===),如果需要使用==,通常应给出解释情况的注释。
    • 应避免使用魔法PHP类型转换 - 错误:($foo),正确:($foo !== NULL) - 只有包含布尔值的表达式应写成($foo)形式。
    • 同样适用于empty函数,因此它是禁止的。
  • 不应使用Yoda条件
  • 如果表达式需要写多行,运算符应位于行的开头。
<?php

if (
	($lorem >= 3 && $lorem <= 5)
	|| $ipsum !== NULL
) {
	// ...
}
  • 每行只有一个语句。
  • 可以使用一个空白行来分隔其他语句。
  • 如果连续有多个方法调用,并且需要将它们写多行,则所有方法调用都应缩进(包括第一个)。
<?php

$lorem
	->ipsum()
	->dolor()
	->sit()
	->amet();
  • new语句中的括号始终存在,即使构造函数没有参数。
<?php

new Foo();
  • 类型转换后有一个空格,括号内没有空格。
    • (binary)(unset)类型转换是禁止的。
  • 对于递增和递减,分别使用++/--运算符而不是“手动”加法/减法。
  • 所有静态符号都应该仅通过::访问,永远不要使用$this
  • echoprint等允许同时使用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等。

代码分析注解

应用程序注解

  • 可选。
  • 对应用程序具有功能意义的注解,例如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。