# PHP编码习惯

## 编写代码的“四项基本原则”

* 正确的实现功能
* 执行的速度要快
* 占用系统资源少
* 后期维护方便

## 习惯1：命名非常重要！

最重要的命名注意事项

* 命名要有实际含义
* 命名风格保持一致
* 不用拼音命名
* 不用语言关键字

## 习惯2：适当的使用注释

* 好的代码应该是自描述的
* 难以理解的地方加上注释
* 函数的功能加上注释说明
* 类的功能和使用方法加注释

phpdocumentor 文档生成手册对php的注释有一个很标准的规范

```php
/**
* @name 名字　（常用）
* @abstract 申明变量/类/方法　（常用）
* @access 指明这个变量、类、函数/方法的存取权限
* @author 函数作者的名字和邮箱地址　（常用）

* @category 组织packages
* @copyright 指明版权信息（常用）
* @const 指明常量（常用）
* @deprecate 指明不推荐或者是废弃的信息
* @example 示例
* @exclude 指明当前的注释将不进行分析，不出现在文挡中
* @final 指明这是一个最终的类、方法、属性，禁止派生、修改。
* @global 指明在此函数中引用的全局变量（常用）
* @include 指明包含的文件的信息（常用）
* @link 定义在线连接
* @module 定义归属的模块信息
* @modulegroup 定义归属的模块组
* @package 定义归属的包的信息（常用）
* @param 定义函数或者方法的参数信息（常用）
* @return 定义函数或者方法的返回信息（常用）
* @see 定义需要参考的函数、变量，并加入相应的超级连接。
* @since 指明该api函数或者方法是从哪个版本开始引入的（常用）
* @static 指明变量、类、函数是静态的。（常用）
* @throws 指明此函数可能抛出的错误异常,极其发生的情况
* @todo 指明应该改进或没有实现的地方
* @var 定义说明变量/属性。（常用）
* @version 定义版本信息（常用）
*/
```

注释的信息很全面，可能有很多我们用不到，（常用）部分是我们经常用到的。

示例：

**文件头部模板**

```php
/** 
*这是一个什么文件 
* 
*此文件程序用来做什么的（详细说明，可选。）。 
* @author      revin<509129@qq.com> 
* @version     $Id$ 
* @since        1.0 
*/
```

**函数/方法头部注释（最重要）**

```php
/** 
* some_func  
* 函数的含义说明 
* 
* @access public (可选)
* @param mixed $arg1 参数一的说明 
* @param mixed $arg2 参数二的说明 
* @param mixed $mixed 这是一个混合类型 
* @since 1.0 
* @return array 
*/  
public function thisIsFunction($string, $integer, $mixed) {return array();}
```

param参数这块变量type和返回的类型，具体可以从这里查找资料 [Definition of a ‘Type’](https://docs.phpdoc.org/references/phpdoc/types.html)

* string　字符串类型
* integer　整型
* boolean或bool　布尔类型
* float或double　浮点类型
* object　　对象
* mixed　混合类型或者不太清除是什么类型
* array　数组类型
* resource　资源类型
* void , 用于函数或者方法返回，当无任何返回时使用

**类的注释**

```php
/** 
* 类的介绍 
* 
* 类的详细介绍（可选。）。 
* @author      revin<509129@qq.com> 
* @since          1.0 
*/  
class Test
{  
}
```

## 习惯3：使用一个变量，需要初始化

```php
function getDocs($id) {
    $ids = implode(',', $id);
    $query = $this->db->query("SELECT * FROM doc WHERE id IN ($ids)");
    while ($doc = $this->db->fetch_array($query)) {
        $doc['rawtitle'] = $doc['title'];
        $doc['title']    = htmlspecialchars($doc['title']);
        $docs = $doc;
    }
    return $docs;
}
```

以上代码的问题:`$docs`没有做初始化， `$docs = array();`

## 习惯4：优先使用单引号

单引号中的变量不会被解析，双引号中的数组会被解析

另一种情况: $row\[‘id’]的效率是$row\[id]的7倍(原因是不加`''`会先去常量里找,没有找到再去数组中找，所以浪费了时间)

## 习惯5：用“1==$a” 替换 “$a==1”

常量和变量判断时，常量放前面的原则，原因看下面示例：

```php
//...
// 获取用户信息
$user = array();
if ($uid = 0) {
    $user['uid'] = 0;
    $user['groupid'] = 0;
} else {
    $user = $this->getUserById($uid);
}
//...
```

起初我也不太明白为什么要这么写，后来才明白用意，比如上面的错误代码片段，`if ($uid = 0) {`,并不会报错，如果养成习惯`用“1==$a” 替换 “$a==1”`方式，即使少写了一个=，**会语法报错及时发现代码错误**

```php
//...
// 获取用户信息
$user = array();
if (0 = $uid) {
    $user['uid'] = 0;
    $user['groupid'] = 0;
} else {
    $user = $this->getUserById($uid);
}
//...
```

## 习惯6：防御式编程思想

* 保护程序免遭非法输入数据的危害
* 错误处理技术
* 异常处理
* 隔离程序，使之相互影响小
* 因地制宜的防御，过度防御会增加复杂度

**示例：**

dedecms 注册部分代片段：

虽然前端javascript做了验证，但是仍然可以绕过js的验证

```php
   $userid = trim($userid);
    $pwd = trim($userpwd);
    $pwdc = trim($userpwdok);
    $rs = CheckUserID($userid, '用户名');
    if($rs != 'ok')
    {
        ShowMsg($rs, '-1');
        exit();
    }
    if(strlen($userid) > 20 || strlen($uname) > 36)
    {
        ShowMsg('你的用户名或用户笔名过长，不允许注册！', '-1');
        exit();
    }
    if(strlen($userid) < $cfg_mb_idmin || strlen($pwd) < $cfg_mb_pwdmin)
    {
        ShowMsg("你的用户名或密码过短，不允许注册！","-1");
        exit();
    }
    if($pwdc != $pwd)
    {
        ShowMsg('你两次输入的密码不一致！', '-1');
        exit();
    }

    $uname = HtmlReplace($uname, 1);
    //用户笔名重复检测
    if($cfg_mb_wnameone=='N')
    {
        $row = $dsql->GetOne("SELECT * FROM `#@__member` WHERE uname LIKE '$uname' ");
        if(is_array($row))
        {
            ShowMsg('用户笔名或公司名称不能重复！', '-1');
            exit();
        }
    }
    if(!CheckEmail($email))
    {
        ShowMsg('Email格式不正确！', '-1');
        exit();
    }
```

dedecms短消息代码片段:

```php
<?php
/**
 * 会员短消息,发送到一个
 *
 * @version        $Id: member_pmone.php 1 11:24 2010年7月20日Z tianya $
 * @package        DedeCMS.Administrator
 * @copyright      Copyright (c) 2007 - 2010, DesDev, Inc.
 * @license        http://help.dedecms.com/usersguide/license.html
 * @link           http://www.dedecms.com
 */
require_once(dirname(__FILE__)."/config.php");
CheckPurview('member_Pm');
//检查用户名的合法性
function CheckUserID($uid,$msgtitle='用户名',$ckhas=true)
{
    global $cfg_mb_notallow,$cfg_mb_idmin,$cfg_md_idurl,$cfg_soft_lang,$dsql;
    if($cfg_mb_notallow != '')
    {
        $nas = explode(',', $cfg_mb_notallow);
        if(in_array($uid, $nas))
        {
            return $msgtitle.'为系统禁止的标识！';
        }
    }
    if($cfg_md_idurl=='Y' && preg_match("#[^a-z0-9]#i", $uid))
    {
        return $msgtitle.'必须由英文字母或数字组成！';
    }

    if($cfg_soft_lang=='utf-8') $ck_uid = utf82gb($uid);
    else $ck_uid = $uid;

    for($i=0;isset($ck_uid[$i]);$i++)
    {
        if(ord($ck_uid[$i]) > 0x80)
        {
            if(isset($ck_uid[$i+1]) && ord($ck_uid[$i+1])>0x40)
            {
                $i++;
            }
            else
            {
                return $msgtitle.'可能含有乱码，建议你改用英文字母和数字组合！';
            }
        }
        else
        {
            if(preg_match("#[^0-9a-z@\.-]i#", $ck_uid[$i]))
            {
                return $msgtitle.'不能含有 [@]、[.]、[-]以外的特殊符号！';
            }
        }
    }
    if($ckhas)
    {
        $row = $dsql->GetOne("SELECT * FROM `#@__member` WHERE userid LIKE '$uid' ");
        if(is_array($row)) return $msgtitle."已经存在！";
    }
    return 'ok';
}

if(!isset($action)) $action = '';
if($action=="post")
{
    $floginid = $cuserLogin->getUserName();
    $fromid = $cuserLogin->getUserID();
    if($subject=='')
    {
        ShowMsg("请填写信息标题!","-1");
        exit();
    }
    $msg = CheckUserID($msgtoid,"用户名",false);
    if($msg!='ok')
    {
        ShowMsg($msg,"-1");
        exit();
    }
    $row = $dsql->GetOne("Select * From `#@__member` where userid like '$msgtoid' ");
    if(!is_array($row))
    {
        ShowMsg("你指定的用户不存在,不能发送信息!","-1");
        exit();
    }
    $subject = cn_substrR(HtmlReplace($subject,1),60);
    $message = cn_substrR(HtmlReplace($message,0),1024);
    $sendtime = $writetime = time();

    //发给收件人(收件人可管理)
    $inquery = "INSERT INTO `#@__member_pms` (`floginid`,`fromid`,`toid`,`tologinid`,`folder`,`subject`,`sendtime`,`writetime`,`hasview`,`isadmin`,`message`)
      VALUES ('$floginid','$fromid','{$row['mid']}','{$row['userid']}','inbox','$subject','$sendtime','$writetime','0','0','$message'); ";

    $dsql->ExecuteNoneQuery($inquery);
    ShowMsg('短信已成功发送','member_pmone.php');
    exit();
}
require_once(DEDEADMIN."/templets/member_pmone.htm");
```

## 习惯7：用自己可控的环境参数

* 明确包含文件的路径
* 给予恰当的默认值
* 自定义错误报警的级别
* 不依赖系统环境参数，程序要动态了解所处的环境

> 不要相信外部的一切输入！

## 习惯8：PHP去掉 ?> 结束标记

**纯 PHP 代码**，最好在文件末尾删除 PHP 结束标记(官方推荐建议),

其实目的的原因是防止`?>`打出空格等,导致出错,难以排查.

## 习惯9：header头的编码

避免页面乱码

```php
header("Content-type: text/html;charset=utf-8");
```

```markup
<head> 
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
</head>
```

## 习惯10：坚持字符编码统一

php文件的编码，静态模板文件的编码，数据库的编码

比如说全用utf8 或者全用gbk

## 习惯11：error\_reporting(7)

error\_reporting() 设置 PHP 的报错级别并返回当前级别。

写代码时，开启最高级别, 正式环境关闭

```php
  error_reporting(E_ALL);
  ini_set('display_errors', true);
```

* 1 E\_ERROR 致命的运行错误。错误无法恢复，暂停执行脚本。
* 2 E\_WARNING 运行时警告(非致命性错误)。非致命的运行错误，脚本执行不会停止。
* 4 E\_PARSE 编译时解析错误。解析错误只由分析器产生。

**error\_reporting(7) ＝ １ ＋２ ＋ ４**

> 1的二进制 01 , 2的二进制为10 4的二进制为100 相加 等于 111 ,换算成十进制为7

系统调试时候使用如下方式

```
D_BUG ? error_reporting(7) : error_reporting(0);
```

其他请查看：[PHP error\_reporting() 错误控制函数功能详解](http://www.cnblogs.com/setsail/archive/2013/05/27/3101589.html),[PHP错误级别 error\_reporting() 函数详解](http://www.cnblogs.com/52php/p/5666424.html)

## 习惯12：优先使用PHP内置函数

### filter\_var() 函数

比如：`filter_var() 函数`通过指定的过滤器过滤变量。比如邮箱验证，IP验证均可使用，不需要再用繁琐的正则。

在zf2中 , php-elastcisearch中，monolog中guzzle中均有使用。

```php
filter_var(variable, filter, options)
```

| ID 名称                                                                                                | 描述                                                                                               |
| ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| [FILTER\_CALLBACK](http://www.w3school.com.cn/php/filter_callback.asp)                               | 调用用户自定义函数来过滤数据。                                                                                  |
| [FILTER\_SANITIZE\_STRING](http://www.w3school.com.cn/php/filter_sanitize_string.asp)                | 去除标签，去除或编码特殊字符。                                                                                  |
| [FILTER\_SANITIZE\_STRIPPED](http://www.w3school.com.cn/php/filter_sanitize_stripped.asp)            | "string" 过滤器的别名。                                                                                 |
| [FILTER\_SANITIZE\_ENCODED](http://www.w3school.com.cn/php/filter_sanitize_encoded.asp)              | URL-encode 字符串，去除或编码特殊字符。                                                                        |
| [FILTER\_SANITIZE\_SPECIAL\_CHARS](http://www.w3school.com.cn/php/filter_sanitize_special_chars.asp) | HTML 转义字符 '"<>& 以及 ASCII 值小于 32 的字符。                                                             |
| [FILTER\_SANITIZE\_EMAIL](http://www.w3school.com.cn/php/filter_sanitize_email.asp)                  | 删除所有字符，除了字母、数字以及 !#$%&'\*+-/=?^\_\`{\|}\~@.\[]                                                   |
| [FILTER\_SANITIZE\_URL](http://www.w3school.com.cn/php/filter_sanitize_url.asp)                      | 删除所有字符，除了字母、数字以及 $-\_.+!\*'(),{}\|\\^\~\[]\`<>#%";/?:@&=                                         |
| [FILTER\_SANITIZE\_NUMBER\_INT](http://www.w3school.com.cn/php/filter_sanitize_number_int.asp)       | 删除所有字符，除了数字和 +-                                                                                  |
| [FILTER\_SANITIZE\_NUMBER\_FLOAT](http://www.w3school.com.cn/php/filter_sanitize_number_float.asp)   | 删除所有字符，除了数字、+- 以及 .,eE。                                                                          |
| [FILTER\_SANITIZE\_MAGIC\_QUOTES](http://www.w3school.com.cn/php/filter_sanitize_magic_quotes.asp)   | 应用 addslashes()。                                                                                 |
| [FILTER\_UNSAFE\_RAW](http://www.w3school.com.cn/php/filter_unsafe_raw.asp)                          | 不进行任何过滤，去除或编码特殊字符。                                                                               |
| [FILTER\_VALIDATE\_INT](http://www.w3school.com.cn/php/filter_validate_int.asp)                      | 在指定的范围以整数验证值。                                                                                    |
| [FILTER\_VALIDATE\_BOOLEAN](http://www.w3school.com.cn/php/filter_validate_boolean.asp)              | 如果是 "1", "true", "on" 以及 "yes"，则返回 true，如果是 "0", "false", "off", "no" 以及 ""，则返回 false。否则返回 NULL。 |
| [FILTER\_VALIDATE\_FLOAT](http://www.w3school.com.cn/php/filter_validate_float.asp)                  | 以浮点数验证值。                                                                                         |
| [FILTER\_VALIDATE\_REGEXP](http://www.w3school.com.cn/php/filter_validate_regexp.asp)                | 根据 regexp，兼容 Perl 的正则表达式来验证值。                                                                    |
| [FILTER\_VALIDATE\_URL](http://www.w3school.com.cn/php/filter_validate_url.asp)                      | 把值作为 URL 来验证。                                                                                    |
| [FILTER\_VALIDATE\_EMAIL](http://www.w3school.com.cn/php/filter_validate_email.asp)                  | 把值作为 e-mail 来验证。                                                                                 |
| [FILTER\_VALIDATE\_IP](http://www.w3school.com.cn/php/filter_validate_ip.asp)                        | 把值作为 IP 地址来验证。                                                                                   |

示例：

**IP的验证：**

```php
$ip = '192.168.0.11000';
var_dump(filter_var($ip, FILTER_VALIDATE_IP));
if (!filter_var($ip, FILTER_VALIDATE_IP)) {
   echo "IP不合法" ;
}
```

验证通过则返回原值，验证失败色返回false

**邮箱验证**

```php
$email = '509129';
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    echo "邮箱地址不合法";
}
```

**验证URL**

```php
/**
 * @param string $host
 *
 * @return string
 */
function prependMissingScheme($host)
{
    if (!filter_var($host, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED)) {
        $host = 'http://' . $host;
    }

    return $host;
}
```

**格式化true和false**

**FILTER\_VALIDATE\_BOOLEAN**

* 如果是 "1", "true", "on" 以及 "yes"，则返回 true，
* 如果是 "0", "false", "off", "no" 以及 ""，则返回 false
* 否则返回 NULL。

```php
/**
 * Formats a boolean value as a string
 *
 * @param string|integer|bool $value Value to convert to a boolean 'true' / 'false' value
 *
 * @return string
 */
public static function formatBooleanAsString($value)
{
    return filter_var($value, FILTER_VALIDATE_BOOLEAN) ? 'true' : 'false';
}
```

**验证浮点数**

```php
if (filter_var($value, FILTER_VALIDATE_FLOAT) === false) {
   echo "不是浮点数";
 }
```

参数的详情说明请看：[PHP filter\_var() 函数](http://www.cnblogs.com/wenzichiqingwa/archive/2012/12/26/2833459.html)

### pathinfo函数

pathinfo() 函数以数组的形式返回文件路径的信息。

示例：

**获取文件的后缀**

```php
$filename = 'abc.txt';
function getExtension($filename) {
    return pathinfo($filename, PATHINFO_EXTENSION);
}
echo getExtension($filename);
```

更多查看：[PHP pathinfo() 函数](http://www.w3school.com.cn/php/func_filesystem_pathinfo.asp)

### 其他有用的php内置：

* usort — 使用用户自定义的比较函数对数组中的值进行排序&#x20;
* rawurlencode — 按照 RFC 1738 对 URL 进行编码&#x20;
* parse\_url — 解析 URL，返回其组成部分&#x20;
* http\_build\_query — 生成 URL-encode 之后的请求字符串&#x20;
* [exif\_imagetype](http://php.net/manual/zh/function.exif-imagetype.php) — 判断一个图像的类型&#x20;
* levenshtein — 计算两个字符串之间的编辑距离&#x20;
* uniqid — 生成一个唯一ID&#x20;
* get\_browser — 获取浏览器具有的功能&#x20;
* get\_defined\_vars — 返回由所有已定义变量所组成的数组&#x20;
* str\_word\_count — 返回字符串中单词的使用情况&#x20;

## 习惯13：屏蔽错误非常低效

```php
$file = @file('non_existent_file') or
    die("Failed opening file:error was not exist");
```

养成不用@的好习惯

## 习惯14：时刻备份源代码

* 代码不能只有一份
* 启用编辑器的自动备份
* 用代码管理工具备份(git or svn)

## 总结：

不要随便相信网上的那些PHP优化50则之类的东西，记住一切都有时效性，php版本时效性等等，善于自己去验证与平日里总结验证。
