netherphp / standards
Nether 表记法的文档和 PHPCS 标准
Requires
- php: ^8.0
- squizlabs/php_codesniffer: ^3.5
README
位于事物底部或较远部分。 -- Merriam-Webster 词典
Nether 标准 项目
也称为 Nether 表记法,NN,N2,或 N2
这是一个记录 Nether 项目使用代码约定的项目。截至本项目编写时,有 99% 的可能性您之前从未见过这种格式的代码 - 这很正常 - 并非所有内容都需要按照 1972 年我们使用的规则来切割。这些规则通常取决于 PHP 的灵活性,但一些通用格式可能可以移植到其他语言,取决于它们的解析器。
这里的大部分规则都基于明确的理念。开发者将始终明确表达他们的意图,绝不允许默认行为。这样做有几个原因。首先,这表明开发者已经实际思考过他们的代码在做什么。其次,它有助于减少将来默认行为改变时向后兼容性问题。是的,这种情况确实会发生。最后,整个格式和规则都是基于这样的理念:代码可以尽可能地自说明,以最小化描述各种实体的元数据量。
自动格式化
此存储库包含一个 phpcs
标准,用于测试和自动重新格式化源代码以符合标准。有关安装和针对此标准的自动化测试说明,请参阅 Wiki 页面
https://github.com/netherphp/standards/wiki/Nether-Notation-Coding-Standard-for-PHP_CodeSniffer
通用标准
- 除了以下情况外,所有内容都使用 PascalCase
- 布尔常量(TRUE,FALSE)和 NULL 使用大写。
- 核心类型(int,float 等)使用小写。
- 缩进使用制表符。
- 换行符(\n)用于新行。
- 在使用之前在作用域中初始化变量。
- 在函数末尾明确使用 "return"。
- 设计时考虑严格的类型。
- 尝试将行长度保持在 80 个字符以内。
- 在不使用字符串评估的情况下优先使用单引号。
- 任何可以具有类型的东西都应该有类型。
- 没有多余的逗号(数组的末尾等处的尾随逗号)。
内联文档。
所有符号文档都使用 Nether Senpai 格式文档。与典型的 docblocks 不同,这些文档位于它们定义的符号之后,并在同一缩进级别。Senpai 块以 /*// 开头,以 //*/ 结尾 - 关于文档的更多信息,请参阅我在编写此文档并开始编写那个文档时的说明。
Nether Senpai 会从代码本身生成尽可能多的文档,然后才会注意到描述它的注释。这减少了您需要手动编写在文档中的垃圾内容。利用 PHP 7.0+ 7.1 中添加的功能,大多数代码可以完全自说明。
NN 将使用 Senpai 表记法,直到 PHP 具有真正的注解支持,而且不是通过您不想在生产项目中使用的缓慢的反射系统。
这意味着典型的方法将看起来像这样
<?php class Project { public function DoSomething(int $Count): void { /*// this method does something. we do not know exactly because this example is not important enough to fill with an implementation. //*/ $Cur = 0; while($Cur < $Count) { // ... } return; } }
当代码在像 Sublime Text 这样的编辑器中折叠时...
<?php class Project { public function DoSomething(int $Count): void { /*// this method does something. we do not know exactly because this example is not important enough to fill with an implementation. //*/ [...] } }
逻辑控制块
IF或WHERE等语句可以带括号或不带括号来表示代码块。这是您个人的选择。如果控制块非常简单,则代码可以省略括号。省略括号时,代码应与控制结构保持相同的缩进级别。当包含括号时,开括号将在控制结构之后,代码将在下一个缩进级别。省略括号的代码结构将通过空行隔开。
省略括号的WHILE语句。
<?php $List = []; while($Row = $Query->Next()) $List[] = $Row;
包含括号的WHILE语句。
<?php $List = []; while($Row = $Query->Next()) { $Row->Cached = FALSE; $List[] = $Row; }
参数列表和数组定义
如果调用函数或方法需要多个参数,且参数列表可能很长或难以阅读,则参数将在下一个缩进级别的新行上定义。
<?php preg_match( '/^https?:\/\/([^\/]+)/', $InputURL, $Matches );
定义包含数据的数组时,同样适用上述规则,额外特点是建议通过额外的空格字符对分隔符进行对齐,对齐应与该定义中最长的元素匹配。
<?php $Dataset = [ 'Something' => 1, 'Else' => 2, 'MoreData' => 3 ];
如果数组定义期间对项目进行分组,则对于每个组可以接受有多个级别的填充。
<?php $Dataset = [ // these are the main system options. 'Something' => 1, 'Else' => 2, 'MoreData' => 3, // mostly optional for whatever. 'Four' => 4, 'Five' => 5, 'Six' => 6 ];
字符串
如果字符串不需要数据评估,则使用单引号。如果字符串需要数据评估,则可以使用双引号,如果生成的字符串不会使行长度变得难以管理。构建或连接长字符串的首选方法是通过sprintf
函数。
<?php $Straight = 'some straight string without eval'; $Evaluated = "not {$Straight}"; $PageURL = sprintf( '%s://%s/%s', $RequestProtocol, $RequestDomain, $RequestPath );
即使在简单的情况下,当难以抉择时,sprintf
的开销也是为了可读性而首选。评估和sprintf
都是可接受的,因此作者应根据代码需要执行的操作来决定最佳选择。
<?php $String = "User: {$Name}"; $String = sprintf('User: %s',$Name);
然而,在需要执行类似于调用方法来构建字符串等操作时,sprintf
是唯一可接受的选择。
<?php $String = sprintf( 'User: %s', $User->GetName() );
使用点操作符进行字面量连接在任何情况下都不被认为是可以接受的。
<?php $String = 'User: ' . $Name; // no.
文件和类自动加载
每个类将放在自己的文件中。类的完全限定名,包括命名空间,将与磁盘上的文件路径相匹配。这允许使用PSR-0或PSR-4风格的自动加载。文件名区分大小写,应与命名空间和类定义完全匹配。
- 文件:Nether/OneScript/Project.php
- 定义:Nether\OneScript\Project
命名空间和类定义。
所有内容都应使用命名空间。命名空间使用声明应与命名空间定义一起分组,而类使用声明应单独分组,距离一行。随后是文件提供的类定义。类将以PascalCase定义。扩展和实现将在下面描述。开括号将在定义的最后一行。
<?php namespace Nether\OneScript; use ThirdParty1; use ThirdParty2; use ThirdParty3\Somespace\SomeClass; use ThirdParty3\Filterspace\SomeInterface; class Project extends SomeClass implements SomeInterface { // ... }
应避免在主执行代码中使用前导反斜杠以降低从PHP根命名空间拉取的能力。相反,建议根据所需访问设置相应的use
语句。
使用use
绑定的别名应仅包含每个use
的一个引用。
<?php namespace MyApp\Subspace; use Nether;
为了在不每次访问Nether命名空间中的类时都包含\
的情况下提供对Nether命名空间的访问。
方法定义。
方法将以PascalCase定义。方法本身将在访问关键字下方定义。开括号将在方法标识符之后。方法始终有一个访问关键字。如果打算省略访问关键字,则将使用关键字public
。
方法应尽可能明确地声明。它们的参数应声明接受的类型以及方法的返回类型。返回类型应放在方法下方,开括号紧随其后。
返回空值(非显式NULL)的方法,其类型将被声明为Void。Void方法和函数不会“仅仅结束”,它们将在完成后包含显式返回。在可能使API更容易检查结果的情况下,鼓励使用可空类型。
<?php class Project { public function SetSomething(): void { // ... return; } static public function GetFromFile(string $Filename): string { // ... return $Contents; } }
带有可变或可选参数的方法
如果方法需要许多参数,或者有一些可选参数,则更倾向于该方法接受一个对象或数组,而不是一个长的参数列表。可以使用类似Nether\Object
的东西来帮助确保可选参数的强制默认值填充。这消除了不断 nagging的感觉,即忘记了参数应该以什么顺序呈现,只要你能回忆起它需要什么。
<?php class Project { public function Search(array|object $Input=NULL): SearchResult { /*// @argv object Input @argv array Input //*/ $Input = new Nether\Object($Input,[ 'Query' => NULL, 'Page' => 1, 'Limit' => 25, 'Owner' => NULL ]); // ... return $Result; } }
关注点减少的方法
如果拆分的方法将被许多不同的进程重用,则不会使用this命名约定。本节主要涉及关注点的分离,其中分离的操作本身是无效的。
为了将长方法拆分为更小的代码单元,减少关注点的方法应在其名称前加上它们设计要与之一起工作的方法名称,描述性动作在方法名称中通过下划线分隔。
<?php class Project { public function GetFileContents(string $Filename) { /*// @return ?StdClass given a filename return the object built from the contents of that file. //*/ $Data = NULL; $Obj = NULL; $Error = NULL; try { $Data = $this->GetFileContents_ReadFile($Filename); $Obj = $this->GetFileContents_ParseData($Data); } catch(Exception $Error) { // log error or something. return NULL; } return $Obj; } protected function GetFileContents_ReadFile(string $Filename): string { /*// check that the file is readable from the filesystem. //*/ if(!file_exists($Filename) || !is_readable($Filename) throw new Exception("{$Filename} not found or unreadable."); return file_get_contents($Filename); } protected function GetFileContents_ParseData(string $Data): StdClass { /*// check that the file was parsable. //*/ $Obj = json_decode($Data); if(!is_object($Data)) throw new Exception("Unable to parse data."); return $Obj; } }
方法链
当使用链式方法且行可能变得难以控制时,则链中的每个链接将放置在新的一行上,其缩进级别与链的起始符号相同。这种调用将被空行隔离。
<?php $DB = new Nether\Database; $Query = NULL; ($Query = $DB->NewVerse()) ->Select('Table') ->Fields(['One','Two','Three']) ->Where(['Five=:InputFive','Six=:InputSix']) ->Limit(25); $Result = $DB->Query($Query,$Input);
在链式调用时,更喜欢传达上下文意识。在上面的示例中,所有链式方法都返回原始对象。当返回的对象不是同一个对象时,链式调用应停止。这个查询链的第一行的括号表达了作者对上下文的理解。这是对象,接下来的链式调用跟随。
变量作用域初始化。
虽然PHP不需要,但变量将在其父作用域的起始处声明,这意味着所有变量都应在函数和方法的顶部声明,而不是在逻辑中间创建。如果它们的值在声明时无法确定,则应初始化为NULL,直到那时。
这包括,甚至特别针对那些通常在for
或foreach
循环、catch
等中临时创建的变量。
<?php class Project { public function DetermineThisValue(): int { $Output = 0; $Child = NULL; foreach($this->TotallyAnArrayProperty as $Child) { if($Child->TotallyAnBoolProperty === TRUE) $Output++; } return $Output; } }
HTML模板
当在HTML模板文件的范围内工作时,代码结构将使用它们的替代语法编写。不会使用简短标签echo。
<?php if($Stuff): ?> <h1><a href="<?php echo $URL ?>"><?php echo $Title ?></a></h1> <?php endif; ?>
变更日志
2021-04-09
- 已经决定撤销关于内置核心类型(int、float等)的大小写决定的决策,即它们不再使用PascalCased。所有其他PascalCased规则仍然适用。
public function AddTwo(Int $Input): Int; public function AddTwo(int $Input): int;
2020-12-04
- 已经决定,不再遵循关于命名空间/类的
use
调用周围的规则。现在更喜欢use
调用仅单行且没有前导斜杠。
use \Nether as Nether, \Local as Local; use Nether; use Local;
2020-05-13
- 添加规则,拒绝在数组和函数参数列表中放置derp逗号