php_rule.md 11.9 KB

PHP语言规范

本规则适用于使用PHP语言编写应用的人员。一经定义,PHP编写时统一遵循此规范

本规范包括PHP编写时命名、缩进、文件结构、注释等一系列定义。

目标 :公司PHP编码形成统一风格,编码人员能够互相理解对方编写之意图。

1. 代码规范

1.1 文件夹命名
  • 如果有框架定义,则文件夹命名统一按照框架制定的规则,如果框架不定义,则统一使用小写字母。

以Lumen为例,项目下的第一级目录全部以小写字母命名。除app文件夹外,其它目录也是全部使用小写字母命名。 app目录下文件夹统一使用首字符大写命名。


1.2 文件及命名
  • 类文件,命名与类名称保持一致,必须统一使用大驼峰形式。
  // CacheManger.php

  class CacheManger {
    //...
  }
  • 配置文件,必须统一使用字母小写形式命名
  // config/jwt.php

  return [
    //...
  ]
  • 普通工具脚本,必须统一使用小驼峰命名
  // tools/uploadImage.php

  $i=1;
  dosth();
  • 只含有php代码的文件,结尾处忽略掉 "?>", 以防止多余的空格或者其它字符影响到代码。

  • 文件的编码必须为UTF-8字符集

  • 文件换行必须以Unix换行\n为准,不允许出现\r\n换行。 注:通过git可配置

  • 如果是接口类,则文件以I开头,如 IAuthedUser。 如果是抽象类,则以Abs开头,如AbsAppClient

  • 接口类和抽象类的实现,必须在文件命名中强调其继承哪个接口或抽象.

  // 命名的准确

  interface IAuthedUser {}
  abstract AbsAppClient {}

  class WeixinAuthedUser implements IAutheduser { } 
  class MessageAppClient extends AbsAppClient {}

1.3 变量命名
  • 全局变量命名使用大驼峰,前缀加上G_, 所有单词首字母大写。

  • 常量统一使用大写,中间分隔使用_,力求语义表达完整清楚,不要嫌名字长。

  • 私有变量命名小驼峰。

  • 变量命名确认以英文名词为主。

  • 变量如果是类的对象,必须使用注释,使用@var定义对象所属的类

  • 必须注意单复数,变量如果是数组列表,则以s/es/List为结尾

  • 变量定义示例

  // 全局变量
  $G_System_Config = config('myconfig');  
  $G_Root_Path  = '/';  

  // 常量
  define('CURRENT_SCRIPT','index_php');
  const TRANSCATION_TYPE = 'income';

  // 私有变量
  private $orderCnt = 0;
  protected $salaryAmount = 0;

  /**
   * 我的订单列表
   * @var array 
   */
  private $myOrderList = [];

  /**
   * 客户连接对象
   * @var ClientConnection|null 
   */
  private $peerConnection = null;

1.4 类、方法、对象
  • 类统一使用大驼峰形式命名,与文件名必须保持一致。
  • 自编写的类一般遵循名词或名词短语来进行命名。
  • 类的动作方法 ,一般使用【动词+名词】方式进行命名,例如 sendMessage / getAttr / setAttr / postLogin等
  • 类的属性和变量字段,使用小写字母或者小写驼峰方式命名
  • 常量值,统一使用大写。
  • 如果方法为API由外部调用,不允许修改方法签名,避免对接口调用方产生影响。
  • 所有继承的覆写方法,必须加@Override 注解
  • 接口过时必须加@deprecated 注解,并清晰地说明采用的新接口或者新服务是什么
  • PHP自定义类示例参考

1.5 缩进和注释
  • 缩进采用4空格进行缩进,禁止使用TAB制表符。

  • 所有的类、函数、方法定义都需要进行注释。

  • 类、方法、函数统一使用/** 内容 */ 注释格式, 不得使用 // 方式

  • 方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/* */注释,注意与代码对齐。

  • 与其“半吊子”英文来注释,不如用中文注释把问题说清楚。专有名词与关键字保持英文原文即可。

  • 代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑等的修改。

  • 好的命名、代码结构是自解释的,注释力求精简准确、表达到位。避免出现注释的一个极端:过多过滥的注释,代码的逻辑一旦修改,修改注释是相当大的负担。

  • 两个特殊注释标记的注释格式 : TODO / FIXME

    • TODO : 待办事宜(TODO):( 标记人,标记时间,[预计处理时间])
    • FIXME : 错误,不能工作(FIXME):(标记人,标记时间,[预计处理时间])
  • 一般php注释遵循phpdocumentor规范, phpdocumentor

  • 如果是API的接口,则提供PHP APIDOC的相关注释 APIDOC

  • 注释

  // ------- 文件的注释 -------
  /**
   * Created by Qingger.
   * User: shipfi
   * Date: 2016/9/22
   * Time: 15:49
   */

  // ------- 类的注释 -------
  /**
   * 所有应用Controller的基类,提供了一般性请求/应答的共通实现
   * @desc 如有必要,通过desc描述详细的类的功能
   * Class BasicController
   * @package App\Http\Controllers
   */
  class BasicController extends Controller
  {
     // ------- 变量的注释,如果变量为 Array/Object类型,一定要使用@var说明其类型定义 -------

     /**
       * 缓存管理对象
       * @var AppCacheManager|null
       */
      protected $appCacheManager = null;

      const MY_RET = GlobalErrCode::ERR_SUCCESS;

        /**
       * @param Request $request
       * @param AppCacheManager $appCacheManager
       */
      public function __construct(Request $request,AppCacheManager $appCacheManager){
          $this->requestHandler  = $request;
          $this->appCacheManager = $appCacheManager;
          DB::connection()->enableQueryLog();
      }

     // ------- 方法的注释,参数如果为Array/Object类型,则一定需要指明 -------

      /**
       * 记录Debug日志信息
       * @desc : 参考----
       * @param $message
       * @param array $moreInfo
       */
      protected function debugLogIt($message,array $moreInfo=[]) {
          Log::debug($message,$moreInfo);
      }

  }

1.6 代码和语句
  • if/else/for/while/do 语句中必须使用大括号,即使只有一行代码.

  • 大括号的使用约定。如果是大括号内为空,则简洁地写成{}即可,不需要换行.

  • 左括号和后一个字符之间不出现空格;同样,右括号和前一个字符之间也不出现空格

  ($age > 18)
  • 任何运算符左右必须加一个空格。运算符包括赋值运算符=、逻辑运算符&&、加减乘除符号、三目运算符等

  • 两元运算符,前后使用空格

  $ret = $age>18 ? 'adult' : 'children';
  • 变量赋值必须保持相等间距和排列
  $newQyAgents = array_values(array_rename_keys($qyAgents,[
                  'agent_id'              => 'agentId',
                  'agent_name'            => 'agentName',
                  'agent_round_logo_url'  => 'agentRoundLogo',
                  'agent_square_logo_url' => 'agentSquareLogo',
                  'agent_app_id'          => 'appId'
  ]));

  if($corpMangerIns) {
                  $ret['corpUserManger']['corpUserId']     = $corpMangerIns->getMyUserId();
                  $ret['corpUserManger']['corpUserEmail']  = $corpMangerIns->getMyUserEmail();
                  $ret['corpUserManger']['corpUserMobile'] = $corpMangerIns->getMyUserMobile();
  }
  • 每行代码长度应控制在80个字符以内,最长不超过120个字符, 如超过则另起一行

运算符与下文一起换行。

方法调用的 -> 符号与下文一起换行。

在多个参数超长,逗号后进行换行。

在括号前不要换行, 如 append \n ("---");

  $qyAuthAppGrpIns = (new QyAuthAppGrp())->getInstanceBySuiteNameCorpId(
                  $suiteName,
                  $this->requestHandler->input('corpId')
              );
  • 每行结尾不允许有多余空格

  • 方法参数在定义和传入时,多个参数逗号后边必须加空格。

  method('a', 'b', 'c');
  • 在类中(尤其是在Model),对于每个属性的访问, 统一使用get/set方法
  class MPAuthAppGrp extends BaseModel
  {
        public function getMyCallBackUrl()  { return isset($this->grp_callback_url) ? $this->grp_callback_url : null; }
      public function getMyGroupAppId()   { return isset($this->grp_app_id) ? $this->grp_app_id : null; }
      public function getMyWXAppId()      { return isset($this->auth_wx_app_id) ? $this->auth_wx_app_id : null; }
      public function getMyWXAppName()    { return isset($this->auth_wx_app_name) ? $this->auth_wx_app_name : null; }
      public function getMyGrpCode()      { return isset($this->grp_code) ? $this->grp_code : null; }
      public function getMyOpenAppId()    { return isset($this->opentp_appid) ? $this->opentp_appid : null; }
      public function getMyOpenAppName()  { return isset($this->opentp_name) ? $this->opentp_name : null; }
  }
  • 语句流程
  /** if / else 语句规范 **/
  if($age>1 && $age<10) {
    echo '1';
  } else if($age>30 && $age<=60) {
    echo '2';
  } else {
    echo '3';
  }

  /* switch语句规范 */
  switch($status) {
    // 严禁使用魔数,如 case 10:
    case OrderStatus::NO_PAY_STATUS:
       echo '1';
       break;
    case OrderStatus::HAS_PAY_STATUS:
       echo '2';
       break;
    default:
       break;
  }

  /* while语句 */
  while($condition) {
    // do sth
  }

  /* foreach语句 */
  foreach($a)
  • 使用 array 类型符声明关联数组的时候,我们鼓励把它分成多个行,只是我们必须同时保证每行的键与值的对齐,以保持美观。

  • 类中的所有代码都必须用四个空格来进行缩进。

  • 每个 php 文件只允许声明一个类。

  • 任何类变量的声明都必须放在类顶部,先于任何函数的声明。

  • 类中的方法必须总是用 private,protected 或者 public 来声明其作用域。

  • 对于方法的变量参数,如果参数确定为对象或数据,则必须在参数中给出变量提示:

  public function foo(AbsContract $obj, array options) {
    // do sth
  }
  • 代码做到复用,对于相同的代码逻辑,严禁出现在两个函数或者两个文件内,如果逻辑相同,使用重构提取共用性。必要时抽取共性方法,或者抽象公共类,甚至是共用模块。

  • 一个函数的逻辑实现严禁超过一屏(非常特殊或者逻辑简单可理解的除外)。如果业务复杂,使用多个函数实现。

  • 不要在条件判断中执行其它复杂的语句

  if ((file.open(fileName, "w") != null) && (...) || (...)) { }  // ERROR

  // RIGHT
  $existed = (file.open(fileName, "w") != null) && (...) || (...); 
  if ($existed) { /*...*/ }  
  • 一个函数中不能出现以下复杂的if/else逻辑判断, 一个规则,每个函数只if/elseif/else嵌套不超过三层。

  • 逻辑上超过 3 层的 if-else 代码可以使用卫语句或状态模式来实现,关于 卫语句


  if($condition1) {
     if($condition2) {
        // do sth
     } else if ($condition3) {
        // do sth
     } else {
         if($condiont4) { // do sth.. }
        // do sth
     }
  } else {
    // do sth
  }

1.7 变量的定义和使用
  • 代码中,类的变量必须做到先定义后使用.

  • 变量使用时,尤其是数组变量,一定要判断其是否被设置

  $sItem = isset($items['key']) ? $item['key'] : null;
  • 在定义和使用变量时一定要明确其类型
  // 模型变量定义
  $objectModel = new ObjectModel();
  $objectInstance = $objectModel->find(1);

  // 基本类型:boolean
  $isAuthed = $objectInstance->getMyAuthedFlag();

  // 基本类型:int
  $cntAccount = 1;

  // date类型
  $dtToday   = Carbon::now();
  $tsExpires = time() + 3800; 

  // 基本类型:array
  $accountList = [1,2,3];
  $acounts     = [1,2,3];

  // 基本类型: Map
  $personInfo = ['name'=>'a','sex'=>'m','age'=>10];

  // 基本类型对象: object
  $clientInstance = $this->getClient();

  // foreach使用时尤其是需要明确变量的单复数
  foreach($items as $key=>$item) {
    // do sth
  }

?


1.8 日志
  • 需要提供统一的日志SDK,作日志API的调用,(现暂时没有)

  • 日志 命名方式:appName_logType_logName.log。

logType : 推荐的分类有 : stats / desc / monitor /visit , 通过这些类型命名,有利于归类查找。

  • 对日志进行分类,错误日志和业务日志尽量分开存放,便于开发人员查看,也便于通过日志对系统进行及时监控。

  • 生产环境禁止输出 debug 日志;有选择地输出 info 日志;如果使用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题

  • 以使用 warn 日志级别来记录Request请求时用户输入参数错误的情况。

  • 注意日志输出的级别,error 级别只记录系统逻辑出错、异常等重要的错误信息。


1.9 异常
  • 异常不要用来做流程控制,条件控制.
  • 不想处理异常,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。
  • 不能在 finally 块中使用 return。finally 块中的 return 返回后方法结束执行,不会再执行 try 块中的 return 语句。
  • 在应用代码中使用“抛异常”代替“返回错误码”,应用如果是API,则对异常作出处理,返回匹配的Result结果信息。

1.10 其它
  • 对API接口和复杂的返回数据,必须进行注释。以能够在代码中准确的了解接口所实现的功能、接口所接收的数据、接口所返回的数据。
  • 修改代码时,一定要保持注释同步,在IDE中严禁出现带黄线提示的注释
  • 未完成的功能使用TODO标记,如果函数接口层已经定义好,则在函数体内抛出暂时无法实现的异常

?