PHP编码习惯

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

  • 正确的实现功能

  • 执行的速度要快

  • 占用系统资源少

  • 后期维护方便

习惯1:命名非常重要!

最重要的命名注意事项

  • 命名要有实际含义

  • 命名风格保持一致

  • 不用拼音命名

  • 不用语言关键字

习惯2:适当的使用注释

  • 好的代码应该是自描述的

  • 难以理解的地方加上注释

  • 函数的功能加上注释说明

  • 类的功能和使用方法加注释

phpdocumentor 文档生成手册对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 定义版本信息(常用)
*/

注释的信息很全面,可能有很多我们用不到,(常用)部分是我们经常用到的。

示例:

文件头部模板

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

函数/方法头部注释(最重要)

/** 
* 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’

  • string 字符串类型

  • integer 整型

  • boolean或bool 布尔类型

  • float或double 浮点类型

  • object  对象

  • mixed 混合类型或者不太清除是什么类型

  • array 数组类型

  • resource 资源类型

  • void , 用于函数或者方法返回,当无任何返回时使用

类的注释

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

习惯3:使用一个变量,需要初始化

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”

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

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

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

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

习惯6:防御式编程思想

  • 保护程序免遭非法输入数据的危害

  • 错误处理技术

  • 异常处理

  • 隔离程序,使之相互影响小

  • 因地制宜的防御,过度防御会增加复杂度

示例:

dedecms 注册部分代片段:

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

   $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
/**
 * 会员短消息,发送到一个
 *
 * @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头的编码

避免页面乱码

header("Content-type: text/html;charset=utf-8");
<head> 
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
</head>

习惯10:坚持字符编码统一

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

比如说全用utf8 或者全用gbk

习惯11:error_reporting(7)

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

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

  error_reporting(E_ALL);
  ini_set('display_errors', true);
  • 1 E_ERROR 致命的运行错误。错误无法恢复,暂停执行脚本。

  • 2 E_WARNING 运行时警告(非致命性错误)。非致命的运行错误,脚本执行不会停止。

  • 4 E_PARSE 编译时解析错误。解析错误只由分析器产生。

error_reporting(7) = 1 +2 + 4

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

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

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

其他请查看:PHP error_reporting() 错误控制函数功能详解,PHP错误级别 error_reporting() 函数详解

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

filter_var() 函数

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

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

filter_var(variable, filter, options)

ID 名称

描述

调用用户自定义函数来过滤数据。

去除标签,去除或编码特殊字符。

"string" 过滤器的别名。

URL-encode 字符串,去除或编码特殊字符。

HTML 转义字符 '"<>& 以及 ASCII 值小于 32 的字符。

删除所有字符,除了字母、数字以及 !#$%&'*+-/=?^_`{|}~@.[]

删除所有字符,除了字母、数字以及 $-_.+!*'(),{}|\^~[]`<>#%";/?:@&=

删除所有字符,除了数字和 +-

删除所有字符,除了数字、+- 以及 .,eE。

应用 addslashes()。

不进行任何过滤,去除或编码特殊字符。

在指定的范围以整数验证值。

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

以浮点数验证值。

根据 regexp,兼容 Perl 的正则表达式来验证值。

把值作为 URL 来验证。

把值作为 e-mail 来验证。

把值作为 IP 地址来验证。

示例:

IP的验证:

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

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

邮箱验证

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

验证URL

/**
 * @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。

/**
 * 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';
}

验证浮点数

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

参数的详情说明请看:PHP filter_var() 函数

pathinfo函数

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

示例:

获取文件的后缀

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

更多查看:PHP pathinfo() 函数

其他有用的php内置:

  • usort — 使用用户自定义的比较函数对数组中的值进行排序

  • rawurlencode — 按照 RFC 1738 对 URL 进行编码

  • parse_url — 解析 URL,返回其组成部分

  • http_build_query — 生成 URL-encode 之后的请求字符串

  • exif_imagetype — 判断一个图像的类型

  • levenshtein — 计算两个字符串之间的编辑距离

  • uniqid — 生成一个唯一ID

  • get_browser — 获取浏览器具有的功能

  • get_defined_vars — 返回由所有已定义变量所组成的数组

  • str_word_count — 返回字符串中单词的使用情况

习惯13:屏蔽错误非常低效

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

养成不用@的好习惯

习惯14:时刻备份源代码

  • 代码不能只有一份

  • 启用编辑器的自动备份

  • 用代码管理工具备份(git or svn)

总结:

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

Last updated