本篇规范制定基于PSR-1基本编码规范进行扩展。

通过一系列通用的规则和建议去规范PHP代码的书写,以降低在浏览不同作者开发的代码造成的不便。

本篇的规则总结各种不同的项目的通用特性。当不同的开发者在并行开发不同的项目时需要有一个统一的规范来指导开发。因此规范的作用并不在于规则本身,而在于所有开发人员一同对规范的遵守。

文档中关键词 “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” 的含义参见 RFC 2119

#1. 概述

  • 代码MUST遵守PSR-1规范
  • 代码缩进MUST使用4个空格,MUST NOT使用tab
  • MUST NOT强制限制单行代码长度,软性要求MUST在120字以内,通常情况SHOULD为80字(注:其他语言的规范通常也会做类似的限制,但HTML语言在制定规范时通常不做单行字符数限制)
  • namespace声明语句下面MUST有一个空行,use声明语句块下面MUST有一个空行
  • 类起始的花括号{必须在类声明语句的下一行,单独成行。结束花括号}MUST在内容结束后单独一行
  • 方法起始的花括号{必须在方法声明语句的下一行,单独成行。结束花括号}MUST在内容结束后单独一行
  • 类中的任何属性和方法MUST设置访问修饰符(private,public,protected),abstract,final必须在访问修饰符之前,staticMUST在访问修饰符之后(注:PHP不设置访问修饰的属性默认为是public的)
  • 控制结构的关键词后面MUST有一个空格,方法的调用MUST NOT
  • 控制结构的起始的花括号{MUST和控制语句在同一行,结束花括号}MUST在内容结束后另起一行
  • 控制结构的起始的括号(后面MUST NOT有空格,结束括号)前面MUST NOT有空格

##1.1 例

<?php
namespace Vendor\Package;

use FooInterface;
use BarClass as Bar;
use OtherVendor\OtherPackage\BazClass;

class Foo extends Bar implements FooInterface
{
    public function sampleFunction($a, $b = null)
    {
        if ($a === $b) {
            bar();
        } elseif ($a > $b) {
            $foo->bar($arg1);
        } else {
            BazClass::bar($arg2, $arg3);
        }
    }

    final public static function bar()
    {
        // method body
    }
}

#2. 通用规范 ##2.1 通用编码规范

代码MUST遵循PSR-1

##2.2 文件

所有PHP文件MUST使用 Unix LF(linefeed)换行符作为结尾(注:要了解更多Unix LF,点击这里) 所有PHP文件MUST已一行空行作为文本的结束(注:此处我个人的理解是,Unix把文件的处理视作流式的,也就是说可以任意拼接的) 所有PHP文件MUST删除结束符?>,只保留开始的<?php(注:PHP文件不加结束符,默认已文件结尾作为结束。如果加了结束符,当文件被include的时候,结束符后面的内容将会被当做标准输出)

##2.3 行

MUST NOT强制限制单行代码长度。

软性要求MUST在120字以内,如果超出,代码自动检测工具MUST报异常,但MUST NOT报错误。

单行字符长度SHOULD NOT超过80字,如果超过,SHOULD切换成多行,并且保持每行不超过80字。

空行中MUST NOT添加多余的空格。

添加空行MAY增加代码的可读性并指示出不同的代码块。

每行语句数MUST NOT多与一条

##2.4 缩进

代码缩进MUST使用4个空格,MUST NOT使用tab(注:也有的规范建议可以使用2个空格或4个空格,个人理解,2个空格更容易控制单行字符数不超过80,但可读性较差,目前绝大多数都建议使用4个字符)

备注:只用空格而不是空格和tab混合使用,可以避免文件diff、打补丁、浏览历史记录和注释造成的不便。只使用空格还可以使行内更细粒度的缩进变得更加简单。

(注:比如我想实现下面的效果,让所有的访问修饰符,变量的等号等对其,是不是使用空格更容易搞定)

<?php
namespace Vendor\Package;

class Foo
{
    public  $a     = 1;
    private $abc   = 2;
    private $abcd  = 3;
    private $b     = 4;
}

##2.5 关键词 和 Ture/False/Null

PHP 关键词 MUST 使用小写 PHP 常量 true,false,null MUST使用小写

#3 namespace 和 use 的声明

namespace声明语句下面MUST有一个空行

use的声明MUSTnamespace声明之后

每一个声明MUST只能有一个use

use声明语句块下面MUST有一个空行

例:

<?php
namespace Vendor\Package;

use FooClass;
use BarClass as Bar;
use OtherVendor\OtherPackage\BazClass;

// ... additional PHP code ...

#4. 类、属性和方法

这里提到了类表示所有 class, interfacetrait

##4.1 继承 和 实现

extendsimplements关键词MUST和类名声明在同一行

类起始的花括号{必须在类声明语句的下一行,单独成行。结束花括号}MUST在内容结束后单独一行

<?php
namespace Vendor\Package;

use FooClass;
use BarClass as Bar;
use OtherVendor\OtherPackage\BazClass;

class ClassName extends ParentClass implements \ArrayAccess, \Countable
{
    // constants, properties, methods
}

实现可以分割成多行,但随后每行MUST独立成行。一旦按多行方式去编写代码,第一个实现的接口MUST在类名的下一行,每个接口自成一行。

<?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 属性

类中的任何属性MUST设置访问修饰符(private,public,protected)

MUST NOTvar关键词进行属性声明(注:使用var效果等同于public

每条语句MUST NOT声明超过一个属性

属性命名SHOULD NOT通过在用一个下划线开始来标识是protectedprivate

一个属性声明应该入下面所示:

<?php
namespace Vendor\Package;

class ClassName
{
    public $foo = null;
}

##4.3 方法

类中任何方法MUST设置访问修饰符(private,public,protected)

方法命名SHOULD NOT通过在用一个下划线开始来标识是protectedprivate

方法命后面MUST NOT有空格,起始的花括号{MUST单独一行,结束花括号}MUST在内容结束后另起一行。起始的括号(后面MUST NOT有空格,结束括号)前面MUST NOT有空格

一个方法的声明如下所示,注意(,,, ,{的位置:

<?php
namespace Vendor\Package;

class ClassName
{
    public function fooBarBaz($arg1, &$arg2, $arg3 = [])
    {
        // method body
    }
}

##4.4 方法参数

参数列表中每个逗号后面MUST NOT有空格,逗号前面MUST有空格。

参数有默认MUST在参数列表的最后。

<?php
namespace Vendor\Package;

class ClassName
{
    public function foo($arg1, &$arg2, $arg3 = [])
    {
        // method body
    }
}

参数列表MAY分成多行书写,每行要进行一次缩进。当按照多行书写参数,第一个参数MUST在方法名下一行且每行只能写一个参数。

当参数分成多行书写,结束)和起始{MUST独占一行,并且两个符号之间有一个空格。

<?php
namespace Vendor\Package;

class ClassName
{
    public function aVeryLongMethodName(
        ClassTypeHint $arg1,
        &$arg2,
        array $arg3 = []
    ) {
        // method body
    }
}

##4.5. abstract、final 和 static

当使用abstractfinal修饰符时,MUST在权限修饰符之前。

当使用static修饰符时,MUST在权限修饰符之后。

<?php
namespace Vendor\Package;

abstract class ClassName
{
    protected static $foo;

    abstract protected function zim();

    final public static function bar()
    {
        // method body
    }
}

##4.6. 方法调用

当发生方法调用,方法名和起始(之间MUST NOT有空格,开始(MUST NOT有空格。结束)MUST NOT有空格。参数列表中逗号前MUST NOT有空格,逗号后MUST有空格。

<?php
bar();
$foo->bar($arg1);
Foo::bar($arg2, $arg3);

参数列表MAY分成多行书写,每行要进行一次缩进。当按照多行书写参数,第一个参数MUST在方法名下一行且每行只能写一个参数。

<?php
$foo->bar(
    $longArgument,
    $longerArgument,
    $muchLongerArgument
);

#5. 控制结构

对控制结构通用的规范如下:

  • 每个控制结构关键词后MUST有一个空格
  • 起始(后面MUST NOT有空格
  • 结束)前面MUST NOT有空格
  • 结束)和起始{之间MUST有空格
  • 控制结构体内容MUST进行一次缩进
  • 结束}MUST在内容结束后单独一行
  • 结构体内容MUST{}包裹。这使得结构体看起来更加标准化,减少新添加行时引入错误的可能性。

##5.1. if, elseif, else

if结构体如下示例。注意( {的位置;elseelseif与之前内容的结束}在同一行。

<?php
if ($expr1) {
    // if body
} elseif ($expr2) {
    // elseif body
} else {
    // else body;
}

SHOULD使用elseif代替else if,以便使所有的关键词看起来都是一个独立的词。

##5.2. switch, case

switch结构体如下示例。注意( {的位置;caseMUSTswitch多缩进一次,break(或者其他表示结束的关键词)MUSTcase下的内容保持统一缩进。当不需要break直接穿透到后面case的时候,MUST加一行注释:// no break

<?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;
}

##5.3. while, do while

while结构体如下示例。注意( {的位置;

<?php
while ($expr) {
    // structure body
}

do while结构体如下示例。注意( {的位置;

<?php
do {
    // structure body;
} while ($expr);

##5.4. for

for结构体如下示例。注意( {的位置;

<?php
for ($i = 0; $i < 10; $i++) {
    // for body
}

##5.5. foreach

foreach结构体如下示例。注意( {的位置;

<?php
foreach ($iterable as $key => $value) {
    // foreach body
}

##5.6. try, catch

try catch结构体如下示例。注意( {的位置;

<?php
try {
    // try body
} catch (FirstExceptionType $e) {
    // catch body
} catch (OtherExceptionType $e) {
    // catch body
}

#6. 闭包

闭包的声明在function关键词后MUST有一个空格,在use关键词的前后要各有一个空格。

开始{MUST在声明语句同一行,结束}必须在内容结束后单独一行。

开始参数列表和变量列表的开始(MUST NOT有空格,在结束)MUST NOT有空格

在参数和变量列表中,在逗号前MUST NOT有空格,逗号后MUST有空格。

闭包参数有默认值的MUST在参数列表的最后。

闭包的声明如下面所示。注意(, {的位置:

<?php
$closureWithArgs = function ($arg1, $arg2) {
    // body
};

$closureWithArgsAndVars = function ($arg1, $arg2) use ($var1, $var2) {
    // body
};

参数和变量列表MAY分成多行书写,每行要进行一次缩进。当按照多行书写参数,第一个参数或变量MUST在方法名下一行且每行只能写一个参数或变量。

在被分成多行的列表(参数或变量)结尾,结束) 和 开始{MUST在同一行且中间要有空格分隔。

下面示例分别展示了当存在或不存在参数列表和变量列表时多行列表的代码样式。

<?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
);

#7. 总结

在这份规范中,有许多编码风格和实践都刻意的删掉了。其中包含但不仅限于:

  • 全局变量和常量的声明
  • 方法的声明
  • 操作符和赋值
  • 行内对其
  • 注释和文档描述
  • 类名前缀和后缀
  • 最佳实践

后续的规范建议MAY针对这些或其他的方面进行修改和扩展。