php代码风格规范
php代码风格规范
本规范融合了 PSR-1(中文) 和 PSR-2(中文),根据现有项目的状态制定。
本规范希望通过制定一系列规范化 PHP 代码的规则,以减少在浏览不同作者的代码时,因代码风格的不同而造成不便。
当多名工程师在多个项目中合作时,就需要一个共同的编码规范, 而本文中的风格规范源自于多个不同项目代码风格的共同特性, 因此,本规范的价值在于我们都遵循这个编码风格,而不是在于它本身。
关键词 “必须”("MUST")、“一定不可/一定不能”("MUST NOT")、“需要”("REQUIRED")、 “将会”("SHALL")、“不会”("SHALL NOT")、“应该”("SHOULD")、“不该”("SHOULD NOT")、 “推荐”("RECOMMENDED")、“可以”("MAY")和”可选“("OPTIONAL")的详细描述可参见 RFC 2119。
目录
^
1. 概览PHP代码文件必须以
<?php
或<?=
标签开始。PHP代码文件必须以
不带 BOM 的 UTF-8
编码。PHP代码文件必须以
LF(\n)
作为换行符。代码必须使用 4 个空格而不是 Tab 键进行缩进。
每行的字符数应该保持在 80 个之内,理论上一定不可多于 100 个,超过限制的折成多行。
PHP代码中应该只定义类、函数、常量等声明,或其他会产生
从属效应
的操作(如:生成文件输出以及修改.ini配置文件等),二者只能选其一。类的命名必须遵循
StudlyCaps
大写开头的驼峰或用下划线_
连接小写单词的命名规范。方法名称必须符合
camelCase
式的小写开头驼峰或用下划线_
连接小写单词的命名规范。类中的常量必须用下划线
_
连接大写单词的命名规范。类中的属性必须用下划线
_
连接小写单词的命名规范。函数名称必须用下划线
_
连接小写单词的命名规范。全局常量必须用下划线
_
连接大写单词的命名规范。变量名称必须用下划线
_
连接小写单词的命名规范。每个
namespace
命名空间声明语句和use
声明语句块后面,必须插入一个空白行。类的开始花括号(
{
)必须写在函数声明后自成一行,结束花括号(}
)也必须写在函数主体后自成一行。方法的开始花括号(
{
)必须写在函数声明后自成一行,结束花括号(}
)也必须写在函数主体后自成一行。类的属性和方法必须添加访问修饰符(
private
、protected
以及public
),abstract
以及final
必须声明在访问修饰符之前,而static
必须声明在访问修饰符之后。类的属性和方法名不该以下划线(_)作为前缀,如
_getTime
。控制结构的关键字后必须要有一个空格,而调用方法或函数时则一定不能有。
控制结构的开始花括号(
{
)必须写在声明后自成一行,不能跟在声明后面,而结束花括号(}
)必须写在主体后自成一行。控制结构的开始左括号后和结束右括号前,都一定不能有空格。
关键字 以及
true
false
null
必须全部小写。
1.1. 例子
以下例子程序简单地展示了以上大部分规范:
namespace Vendor\Package;
use FooInterface;
use BarClass as Bar;
use OtherVendor\OtherPackage\BazClass;
class Foo extends Bar implements FooInterface
{
const VERSION = '1.0';
const DATE_APPROVED = '2012-06-01';
public function sampleFunction($user_id, $b = null)
{
if ($user_id === $b)
{
bar();
}
else if ($user_id > $b)
{
$foo->bar($arg1);
}
else
{
BazClass::bar($arg2, $arg3);
}
}
final public static function bar()
{
// method body
}
}
或
class mod_group extends model implements db_interface
{
const VERSION = '1.0';
const DATE_APPROVED = '2012-06-01';
public function sample_function($user_id, $b = null)
{
if ($user_id === $b)
{
bar();
}
else if ($user_id > $b)
{
$foo->bar($arg1);
}
else
{
baz_class::bar($arg2, $arg3);
}
}
final public static function bar()
{
// method body
}
}
^
2. 文件2.1. 通则
每个类都独立为一个文件。
多个类方法或函数定义用一个空行分隔。
行尾一定不能有多余的空格。
空行可以使得阅读代码更加方便以及有助于代码的分块。
每行一定不能存在多于一条语句。
所有PHP文件必须使用
Unix LF (linefeed)
作为行的结束符。所有PHP文件必须以一个空白行作为结束。
纯PHP代码文件必须省略最后的
?>
结束标签。
2.2. PHP标签
PHP 代码必须使用 <?php ?>
长标签 或 <?= ?>
短输出标签,一定不可使用其它自定义标签。
2.3. 字符编码
PHP 代码必须且只可使用不带 BOM 的 UTF-8
编码。
2.4. 从属效应(副作用)
一份 PHP 文件中应该要不就只定义新的声明,如类、函数或常量等不产生从属效应的操作, 要不就只有会产生从属效应的逻辑操作,但不该同时具有两者。
“从属效应”(side effects)一词的意思是,仅仅通过包含文件,不直接声明类、函数和常量等,而执行的逻辑操作。
“从属效应”包含却不仅限于:生成输出、直接的 require
或 include
、 连接外部服务、修改 ini 配置、抛出错误或异常、修改全局或静态变量、读或写文件等。
以下是一个反例,一份包含声明以及产生从属效应的代码:
// 从属效应:修改 ini 配置
ini_set('error_reporting', E_ALL);
// 从属效应:引入文件
include "file.php";
// 从属效应:生成输出
echo "<html>\n";
// 声明函数
function foo()
{
// 函数主体部分
}
下面是一个范例,一份只包含声明不产生从属效应的代码:
// 声明函数
function foo()
{
// 函数主体部分
}
// 条件声明**不**属于从属效应
if (! function_exists('bar'))
{
function bar()
{
// 函数主体部分
}
}
^
3. 空格控制结构的关键字后必须要有一个空格,而调用方法或函数时方法和函数名后一定不能有。
代码注释符
//
/* */
与注释内容之间必须有一个空格。如果在行尾注释,注释符与代码之间必须有一个空格。函数和方法参数列表中,每个逗号后面必须要有一个空格,而逗号前面一定不能有空格。
数组成员列表中,每个逗号后面必须要有一个空格,而逗号前面一定不能有空格。
方法和函数调用的开始左括号后和结束右括号前,一定不能有空格。
控制结构的开始左括号后和结束右括号前,一定不能有空格。
赋值操作符
=
=>
.=
+=
等两边必须有一个空格。为了保持列对齐,左边的空格数可以超过一个,右边必须只有一个空格。字符串连接操作符
.
双边必须有空格。类型转换操作右括号后面不能有空格, 例如:
(int)$uid
。类方法调用操作符
->
两边必须没有空格,除非位于行首。数组成员操作符
[
]
两边必须没有空格。语句终止符
;
两边必须没有空格。如下操作符
!
(逻辑非)&
(引用 )++
--
@
(屏蔽错误)~
(位操作)...
右边必须不能有空格。其他操作符
+
-
*
/
%
**
==
==
!=
!==
>
>=
<
<=
&&
||
as
<<
&
instanceof
?:
等两边必须有且只有一个空格。
^
4. 类的常量、属性和方法此处的“类”指代所有的类、接口以及可复用代码块(traits)
4.1. 属性
类的属性命名必须是下划线分隔式 (_
)。
每个属性都必须添加访问修饰符。
一定不可使用关键字 var
声明一个属性。
每条语句一定不可定义超过一个属性。
不要使用下划线作为前缀来区分属性是 public,protected 或 private。
以下是属性声明的一个范例:
namespace Vendor\Package;
/**
* Class Description...
*/
class ClassName
{
/**
* @var int
*/
public $foo = 0;
}
4.2. 方法
方法名称必须符合 camelCase()
式的小写开头驼峰或用下划线 _
连接小写单词的命名规范, 不管选择哪一种,同一个项目中必须保持一致。
所有方法都必须添加访问修饰符。
不要使用下划线作为前缀,来区分方法是 public,protected 或 private。
方法名称后一定不能有空格,其开始花括号必须独占一行,结束花括号也必须在方法主体后单独成一行。
参数左括号后和右括号前一定不能有空格。
一个标准的方法声明可参照以下范例,留意其括号、逗号、空格以及花括号的位置。
namespace Vendor\Package;
/**
* Class Description...
*/
class ClassName
{
/**
* summary...
*
* @param callable $arg1 回调函数
* @param string $arg2
* @param User[] $arg3
*
* @throw Exception 远程调用失败
*
* @return false|int[]
*/
public function fooBarBaz($arg1, &$arg2, array $arg3 = [])
{
// method body
}
}
4.3. 方法的参数
参数列表中,每个逗号后面必须要有一个空格,而逗号前面一定不能有空格。
有默认值的参数,必须放到参数列表的末尾。
数组类型和自定义类型的参数必须写明。
namespace Vendor\Package;
/**
* Class Description...
*/
class ClassName
{
/**
* summary...
*
* @param callable $arg1 回调函数
* @param string $arg2
* @param User[] $arg3
*
* @throw Exception 远程调用失败
*
* @return false|int[]
*/
public function foo($arg1, User &$arg2, array $arg3 = [])
{
// method body
}
}
参数列表可以分列成多行,这样,包括第一个参数在内的每个参数都必须单独成行。
拆分成多行的参数列表后,结束括号以及方法开始花括号必须写在独立的一行。
namespace Vendor\Package;
/**
* Class Description...
*/
class ClassName
{
/**
* summary...
*
* @param ClassTypeHint $arg1
* @param string $arg2
* @param User[] $arg3
*
* @throw Exception 远程调用失败
*
* @return bool
*/
public function aVeryLongMethodName(
ClassTypeHint $arg1,
&$arg2,
array $arg3 = []
)
{
// method body
}
}
4.4. 方法及函数调用
方法及函数调用时,方法名或函数名与参数左括号之间一定不能有空格, 参数右括号前也 一定不能有空格。每个参数前一定不能有空格,但其后必须有一个空格。
bar();
$foo->bar($arg1);
Foo::bar($arg2, $arg3);
参数可以分列成多行,此时包括第一个参数在内的每个参数都必须单独成行。
$foo->bar(
$long_argument,
$longer_argument,
$much_longer_argument
);
4.5. namespace 以及 use 声明
namespace
声明后必须插入一个空白行。
所有 use
必须在 namespace
后声明。
每条 use
声明语句必须只有一个 use
关键词。
use
声明语句块后必须要有一个空白行。
例如:
namespace Vendor\Package;
use FooClass;
use BarClass as Bar;
use OtherVendor\OtherPackage\BazClass;
// ... additional PHP code ...
4.6. 扩展与继承
关键词 extends
和 implements
必须写在类名称的同一行。
类的开始花括号必须独占一行,结束花括号也必须在类主体后独占一行。
namespace Vendor\Package;
use FooClass;
use BarClass as Bar;
use OtherVendor\OtherPackage\BazClass;
/**
* Class Description....
*/
class ClassName extends ParentClass implements \ArrayAccess, \Countable
{
// constants, properties, methods
}
implements
的继承列表也可以分成多行,这样的话,每个继承接口名称都必须分开独立成行,包括第一个。
namespace Vendor\Package;
use FooClass;
use BarClass as Bar;
use OtherVendor\OtherPackage\BazClass;
class ClassName extends ParentClass implements
\ArrayAccess,
\Countable,
\Serializable
{
// constants, properties, methods
}
abstract
、 final
、 以及 static
4.7. 需要添加 abstract
或 final
声明时, 必须写在访问修饰符前,而 static
则必须写在其后。
namespace Vendor\Package;
/**
* Abstract Class
*/
abstract class ClassName
{
/**
* @var string
*/
protected static $foo;
/**
* Method zim
*/
abstract protected function zim();
/**
* Method bar
*
* @return void
*/
final public static function bar()
{
// method body
}
}
^
5. 控制结构控制结构关键词后必须有一个空格。
左括号
(
后一定不能有空格。右括号
)
前也一定不能有空格。结构体主体一定要有一次缩进。
开始花括号
{
一定在结构声明后单独成行。结束花括号
}
一定在结构体主体后单独成行。每个结构体的主体都必须被包含在成对的花括号之中。
if
、else if
和 else
5.1. 标准的 if
结构如下代码所示,留意括号、空格以及花括号的位置,注意 else
和 else if
都与前面的结束花括号不在同一行。
else if
也可以写成 elseif
。
if ($expr1)
{
// if body
}
else if ($expr2)
{
// else if body
}
else
{
// else body;
}
一定不能写成下面这样:
if ($expr1) unset($list['count']);
else
unset($list);
while ($expr2) $i++;
switch
和 case
5.2. 标准的 switch
结构如下代码所示,留意括号、空格以及花括号的位置。 case
语句必须相对 switch
进行一次缩进,而 break
语句以及 case
内的其它语句都必须相对 case
进行一次缩进。 如果存在非空的 case
直穿语句,主体里必须有类似 // no break
的注释。
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;
}
while
和 do while
5.3. 一个规范的 while
语句应该如下所示,注意其括号、空格以及花括号的位置。
while ($expr)
{
// structure body
}
标准的 do while
语句如下所示,同样的,注意其括号、空格以及花括号的位置。
do
{
// structure body;
}
while ($expr);
for
5.4. 标准的 for
语句如下所示,注意其括号、空格以及花括号的位置。
for ($i = 0; $i < 10; $i++)
{
// for body
}
foreach
5.5. 标准的 foreach
语句如下所示,注意其括号、空格以及花括号的位置。
foreach ($iterable as $key => $value)
{
// foreach body
}
try
, catch
5.6. 标准的 try catch
语句如下所示,注意其括号、空格以及花括号的位置。
try
{
// try body
}
catch (FirstExceptionType $e)
{
// catch body
}
catch (OtherExceptionType $e)
{
// catch body
}
^
6. 闭包闭包声明时,关键词 function
后以及关键词 use
的前后都必须要有一个空格。
开始花括号必须写在声明的下一行,结束花括号必须紧跟主体结束的下一行。
参数列表和变量列表的左括号后以及右括号前,必须不能有空格。
参数和变量列表中,逗号前必须不能有空格,而逗号后必须要有空格。
闭包中有默认值的参数必须放到列表的后面。
标准的闭包声明语句如下所示,注意其括号、逗号、空格以及花括号的位置。
$closure_with_args = function ($arg1, $arg2)
{
// body
};
$closure_with_args_and_vars = function ($arg1, $arg2) use ($var1, $var2)
{
// body
};
参数列表以及变量列表可以分成多行,这样,包括第一个在内的每个参数或变量都必须单独成行, 而列表的右括号与闭包的开始花括号必须放在独立的一行。
以下几个例子,包含了参数和变量列表被分成多行的多情况。
$long_args_no_vars = function(
$long_argument,
$longer_argument,
$much_longer_argument
)
{
// body
};
$no_args_long_vars = function () use (
$long_var1,
$longer_var2,
$much_longer_var3
)
{
// body
};
$long_args_long_vars = function (
$long_argument,
$longer_argument,
$much_longer_argument
) use (
$long_var1,
$longer_var2,
$much_longer_var3
)
{
// body
};
$long_args_short_vars = function (
$long_argument,
$longer_argument,
$much_longer_argument
) use ($var1)
{
// body
};
$short_args_long_vars = function ($arg) use (
$long_var1,
$longer_var2,
$much_longer_var3
)
{
// body
};
注意,闭包被直接用作函数或方法调用的参数时,以上规则仍然适用。
$foo->bar(
$arg1,
function ($arg2) use ($var1)
{
// body
},
$arg3
);
^
7. 代码太长时的折行方法每行的字符数应该保持在 80 个之内,理论上一定不可多于 100 个,超过限制的折成多行。
注意括号、空格以及花括号的位置。
if
else if
while
条件太长
7.1. 条件太长时按判断逻辑分行,每行一个判断逻辑,逻辑操作符放在行尾,每个判断逻辑与第一个列对齐。
范例:
if ($update_time > 0 &&
is_string($user_name) &&
($is_admin || $level < 1000))
{
// if body
}
else if ($update_time > (time() - 7 * 86400) ||
$gender == 1)
{
// else if body
}
else
{
$time_limit = time() + 3 * 86400;
while ($count < 100 &&
$update_time < $time_limit &&
!$is_admin)
{
// while body
}
}
反例:
if ($update_time > 0
&& is_string($user_name) &&
&& ($is_admin || $level < 1000))
{
// if body
}
else if ($update_time > (time() - 7 * 86400) ||
$gender == 1)
{
// else if body
}
else
{
$time_limit = time() + 3 * 86400;
while ($count < 100 &&
$update_time < $time_limit && !$is_admin)
{
// while body
}
}
7.2 函数或方法定义的参数太多时
参数列表可以分列成多行,这样,包括第一个参数在内的每个参数都必须单独成行,比访问修饰符或 function
多一次缩进。
范例:
/**
* User Model
*/
class User
{
/**
* 获取多个用户的信息
*
* @param int $gid
* @param int[] $uids
* @param string[] $fields 需要查询的字段
* @param bool $use_cache 是否使用缓存
*
* @return mixed[]
*/
public function getMulti(
$gid,
array $uids,
array $fields = [],
$use_cache = true
)
{
// method body
}
}
反例:
/**
* User Model
*/
class User
{
/**
* 获取多个用户的信息
*
* @param int $gid
* @param int[] $uids
* @param string[] $fields 需要查询的字段
* @param bool $use_cache 是否使用缓存
*
* @return mixed[]
*/
public function getMulti($gid, array $uids,
array $fields = [],
$use_cache = true)
{
// method body
}
}
7.3 调用函数或方法的参数太多时
参数列表可以分列成多行,这样,包括第一个参数在内的每个参数都必须单独成行,比函数名多一次缩进。
范例:
function foo()
{
$gid = 123;
$user = new User();
$list = $user->getMultiFromDB(
$gid,
[11, 22, 33, 44, 55, 66, 77, 88]
['user_id', 'user_name', 'level', 'face']
);
}
反例:
function foo()
{
$gid = 123;
$user = new User();
$list = $user->getMultiFromDB($gid,
[11, 22, 33, 44, 55, 66, 77, 88]
['user_id', 'user_name', 'level', 'face']);
}
7.4. 方法链式调用对齐
当方法链式调用需要折行时,->
需要列对齐。
范例:
function foo()
{
$group = new Group(123);
$list = $group->setName('banana', true)
->updateListCache()
->getAll()
->formatList();
}
反例:
function foo()
{
$group = new Group(123);
$list = $group->setName('banana', true)->updateListCache()
->getAll()
->formatList();
}
function bar()
{
$group = new Group(123);
$list = $group->setName('banana', true)
->updateListCache()
->getAll()
->formatList();
}
7.5. 三元操作
范例:
function foo()
{
$title = isset($row['data']['title']) ?
$row['data']['title'] :
$row['data']['content'];
}
反例:
function foo()
{
$title = isset($row['data']['title']) ?
$row['data']['title'] : $row['data']['content'];
}
7.6. 字符串太长
范例:
$sql = "SELECT `user_id`, `user_name`, `age` " .
"FROM `users` " .
"WHERE `user_id` != 131212 AND `age` >= 18 " .
"ORDER BY `age`";
反例:
$sql = "SELECT `user_id`, `user_name`, `age` " .
"FROM `users` " .
"WHERE `user_id` != 131212 AND `age` >= 18 " .
"ORDER BY `age`";
$sql = "SELECT `user_id`, `user_name`, `age`
FROM `users`
WHERE `user_id` != 131212 AND `age` >= 18
ORDER BY `age`";
^
8. 代码按列对齐8.1. 变量定义
多个连续的局部变量定义可以按 =
列对齐,但变量名长度差别太大时不该对齐。
范例:
$user_name = 'John';
$type = 34;
$list[3] = [1, 2, 3];
反例:
$uid = 123;
$this_is_a_long_variable = 32;
$result['list'][0]['type'] = 3;
8.2. 数组定义
推荐使用
[]
而不是array()
定义数组。数组定义必须一次定义完整,不允许使用多条赋值语句定义一个数组。
超过两个成员的键值数组定义,必须每行定义一对键值。
键值数组的赋值操作符应该按列对齐。
最后一个成员必须以逗号结束。
如果多个成员写在同一行,分隔成员的逗号之后必须有且只有一个空格。
只有值的数组,在成员很多时可以定义成多行,每行多个成员。
范例:
$count = 32;
$result = [
'count' => $count,
'list' => [
[
'user_id' => 123,
'type' => 88,
],
[
'user_id' => 124,
'type' => 89,
],
],
];
$types = ['123', 4555, 'abc'];
$uids = [
123, 456, 789,
1123, 1456, 1789,
2123, 2456, 2789,
];
$map = ['a' => 3, 'b' => 4];
反例:
$count = 32;
$result = [];
$result['count'] = $count;
$result['list'] = [
[
'user_id' => 123,
]
];
$map = ['a' => 3, 'b' => 5, 'x' => 32, 'y' => 'blah...', 'z' => 'long, long ago'];
^
9. 代码注释和文档只允许使用
//
/* */
作为注释符,注释符与注释内容之间必须有一个空格文件头可以注释。
类、类方法、类常量、类成员变量,函数,参数必须有文档类型的注释。
文档型注释遵从 phpDocumentor 的规范。
类方法和函数的文档型注释分为 5 段,
摘要
详情
参数注释
异常注释
返回值注释
,每段之间必须有一个空行,详情
可以省略。PHP 内置类型的注释分为
string
int
float
bool
resource
null
callable
, 根据 phpDocumentor 规范,还支持mixed
void
object
false
true
self
$this
。变量或参数类型如果是数组,必须写明成员类型,如
int[]
,User[]
,如果是混和类型则为mixed[]
。建议设置
todo
提醒将来要完成的代码。
范例:
注意空格,空行
/**
* 类说明
*/
class ClassName
{
/**
* 摘要......
*
* 详情............................
* 详情............................
*
* @param User $user
* @param int $argv1 变量说明
* @param string $arg2
* @param resource[] $arg3 optional
*
* @throws InvalidArgumentException 参数错误
* @throws DBException 数据库操作失败
*
* @return false|array
*/
public function fooBarBaz(User $user, $arg1, &$arg2, array $arg3 = [])
{
if (!is_numeric($arg1))
{
throw new InvalidArgumentException('arg1 invalid');
}
$user_id = $user->getId();
$is_admin = $user->isAdmin(); // level 大于 3 的都是管理员
if ($is_admin)
{
$user->foo($argv1);
$list = $user->baz();
// 只返回最新一天的记录
$start_time = time() - 86400;
$list = array_filter(
function ($i) use ($start_time)
{
return $i['update_time'] > $start_time;
},
$list
);
return $list;
}
else
{
// todo 如果不是管理员
return false;
}
}
}
^
10. 最佳实践一定不能使用执行命令的操作符,例如
ls -ls
。逻辑操作符不该使用
and
or
xor
,应该使用&&
||
。局部变量应该定义在开始使用的地方。
函数和方法不要太长,应该把复杂的逻辑分解成独立函数或方法,即使函数或方法只有一个地方调用。
^
11. 总结以上规范难免有疏忽,本规范之后的修订将弥补不足之处。