# 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版本时效性等等，善于自己去验证与平日里总结验证。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://phper.shujuwajue.com/phpbian-ma-ji-qiao/phpbian-ma-xi-guan.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
