将来的你, 肯定会感激现在拼命的自己。
  • php
  •  2015.02.14 14:43
  •  330

【推荐】提高PHP代码质量的有用技巧

不要使用相对路径

常常会看到: 

require_once('../../lib/some_class.php');

该方法有很多缺点:

它首先查找指定的php包含路径, 然后查找当前目录.

因此会检查过多路径.

如果该脚本被另一目录的脚本包含, 它的基本目录变成了另一脚本所在的目录.

另一问题, 当定时任务运行该脚本, 它的上级目录可能就不是工作目录了.

因此最佳选择是使用绝对路径:

define('ROOT', '/var/www/project/');
require_once(ROOT . '../../lib/some_class.php');
//rest of the code

我们定义了一个绝对路径, 值被写死了. 我们还可以改进它. 路径 /var/www/project 也可能会改变, 那么我们每次都要改变它吗? 不是的, 我们可以使用__FILE__常量, 如:

//suppose your script is /var/www/project/index.php
//Then __FILE__ will always have that full path.
define('ROOT', pathinfo(__FILE__, PATHINFO_DIRNAME));
require_once(ROOT . '../../lib/some_class.php');
//rest of the code

现在, 无论你移到哪个目录, 如移到一个外网的服务器上, 代码无须更改便可正确运行.

        

为应用保留调试代码

在开发环境中, 我们打印数据库查询语句, 转存有问题的变量值, 而一旦问题解决, 我们注释或删除它们. 然而更好的做法是保留调试代码.

在开发环境中, 你可以:

define( 'ENVIRONMENT', 'development' );
if ( !$db->query( $query ) ) {
    if ( ENVIRONMENT == 'development' ) {
        echo "$query failed";
    } else {
        echo "Database error. Please contact administrator";
    }
}

在服务器中, 你可以:

define( 'ENVIRONMENT', 'production' );
if ( !$db->query( $query ) ) {
    if ( ENVIRONMENT == 'development' ) {
        echo "$query failed";
    } else {
        echo "Database error. Please contact administrator";
    }
}



使用可跨平台的函数执行命令

system, exec, passthru, shell_exec 这4个函数可用于执行系统命令. 每个的行为都有细微差别. 问题在于, 当在共享主机中, 某些函数可能被选择性的禁用. 大多数新手趋于每次首先检查哪个函数可用, 然而再使用它.

更好的方案是封成函数一个可跨平台的函数.

/**
  Method to execute a command in the terminal
  Uses :
  1. system
  2. passthru
  3. exec
  4. shell_exec
 */
function terminal( $command ) {
     //system
    if ( function_exists( 'system' ) ) {
        ob_start();
        system( $command, $return_var );
        $output = ob_get_contents();
        ob_end_clean();
    }
    //passthru
    else if ( function_exists( 'passthru' ) ) {
        ob_start();
        passthru( $command, $return_var );
        $output = ob_get_contents();
        ob_end_clean();
    }
    //exec
    else if ( function_exists( 'exec' ) ) {
        exec( $command, $output, $return_var );
        $output = implode( "\n", $output );
    }
    //shell_exec
    else if ( function_exists( 'shell_exec' ) ) {
        $output = shell_exec( $command );
    } else {
        $output = 'Command execution not possible on this system';
        $return_var = 1;
    }
    return array( 'output' => $output, 'status' => $return_var );
}
terminal( 'ls' );

上面的函数將运行shell命令, 只要有一个系统函数可用, 这保持了代码的一致性.



发送正确的mime类型头信息, 如果输出非html内容的话.

输出一些xml.

$xml = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>';
$xml = "<response>
 <code>0</code>
 </response>";
//Send xml data
echo $xml;

工作得不错. 但需要一些改进.

$xml = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>';
$xml = "<response>
 <code>0</code>
 </response>";
//Send xml data
header( "content-type: text/xml" );
echo $xml;

注意header行. 该行告知浏览器发送的是xml类型的内容. 所以浏览器能正确的处理. 很多的javascript库也依赖头信息.

类似的有 javascript , css, jpg image, png image:

JavaScript

header("content-type: application/x-javascript");
echo "var a = 10";

 CSS

header("content-type: text/css");
echo "#div id { background:#000; }";



为mysql连接设置正确的字符编码

曾经遇到过在mysql表中设置了unicode/utf-8编码, phpadmin也能正确显示, 但当你获取内容并在页面输出的时候,会出现乱码. 这里的问题出在mysql连接的字符编码.

//Attempt to connect to database
$c = mysqli_connect( $this->host, $this->username, $this->password );
//Check connection validity
if ( !$c ) {
    die( "Could not connect to the database host: " . mysqli_connect_error() );
}
//Set the character set of the connection
if ( !mysqli_set_charset( $c, 'UTF8' ) ) {
    die( 'mysqli_set_charset() failed' );
}

一旦连接数据库, 最好设置连接的 characterset. 你的应用如果要支持多语言, 这么做是必须的.



使用 htmlentities 设置正确的编码选项

php5.4前, 字符的默认编码是ISO-8859-1, 不能直接输出如À â等.

$value = htmlentities($this->value , ENT_QUOTES , CHARSET);

php5.4以后, 默认编码为UTF-8, 这將解决很多问题. 但如果你的应用是多语言的, 仍然要留意编码问题。



写文件前, 检查目录写权限

写或保存文件前, 确保目录是可写的, 假如不可写, 输出错误信息. 这会节约你很多调试时间. linux系统中, 需要处理权限, 目录权限不当会导致很多很多的问题, 文件也有可能无法读取等等.

确保你的应用足够智能, 输出某些重要信息.

$contents = "All the content";
$file_path = "/var/www/project/content.txt";
file_put_contents($file_path , $contents);

这大体上正确. 但有些间接的问题. file_put_contents 可能会由于几个原因失败:

>>父目录不存在

>>目录存在, 但不可写

>>文件被写锁住?

所以写文件前做明确的检查更好.

$contents = "All the content";
$dir = '/var/www/project';
$file_path = $dir . "/content.txt";
if ( is_writable( $dir ) ) {
    file_put_contents( $file_path, $contents );
} else {
    die( "Directory $dir is not writable, or does not exist. Please check" );
}

这么做后, 你会得到一个文件在何处写及为什么失败的明确信息.



更改应用创建的文件权限

在linux环境中, 权限问题可能会浪费你很多时间. 从今往后, 无论何时, 当你创建一些文件后, 确保使用chmod设置正确权限. 否则的话, 可能文件先是由”php”用户创建, 但你用其它的用户登录工作, 系统將会拒绝访问或打开文件, 你不得不奋力获取root权限, 更改文件的权限等等.

// Read and write for owner, read for everybody else
chmod( "/somedir/somefile", 0644 );
// Everything for owner, read and execute for others
chmod( "/somedir/somefile", 0755 );



为函数内总具有相同值的变量定义成静态变量

//Delay for some time
function delay() {
    $sync_delay = get_option( 'sync_delay' );
    echo "Delaying for $sync_delay seconds...";
    sleep( $sync_delay );
    echo "Done ";
}

用静态变量取代:

//Delay for some time
function delay() {
    static $sync_delay = null;
    if ( $sync_delay == null ) {
        $sync_delay = get_option( 'sync_delay' );
    }
    echo "Delaying for $sync_delay seconds...";
    sleep( $sync_delay );
    echo "Done ";
}



使用array_map快速处理数组

比如说你想 trim 数组中的所有元素. 新手可能会:

foreach($arr as $c => $v){
 $arr[$c] = trim($v);
}

但使用 array_map 更简单:

$arr = array_map('trim' , $arr);

这会为$arr数组的每个元素都申请调用trim. 另一个类似的函数是 array_walk. 请查阅文档学习更多技巧.



使用 php filter 验证数据

你肯定曾使用过正则表达式验证 email , ip地址等. 是的,每个人都这么使用. 现在, 我们想做不同的尝试, 称为filter.

php的filter扩展提供了简单的方式验证和检查输入.



强制类型检查

$amount = intval( $_GET['amount'] );
$rate = (int) $_GET['rate'];

这是个好习惯.



如果需要,使用profiler如xdebug

如果你使用php开发大型的应用, php承担了很多运算量, 速度会是一个很重要的指标. 使用profile帮助优化代码. 可使用xdebug和webgrid.



小心处理大数组

对于大的数组和字符串, 必须小心处理. 常见错误是发生数组拷贝导致内存溢出,抛出Fatal Error of Memory size 信息:

$db_records_in_array_format; //This is a big array holding 1000 rows from a table each having 20 columns , every row is atleast 100 bytes , so total 1000 * 20 * 100 = 2MB
$cc = $db_records_in_array_format; //2MB more
some_function($cc); //Another 2MB ?

当导入或导出csv文件时, 常常会这么做.

不要认为上面的代码会经常因内存限制导致脚本崩溃. 对于小的变量是没问题的, 但处理大数组的时候就必须避免.

确保通过引用传递, 或存储在类变量中:

$a = get_large_array();

 pass_to_function(&$a);

这么做后, 向函数传递变量引用(而不是拷贝数组). 查看文档.

class A {
    function first() {
        $this->a = get_large_array();
        $this->pass_to_function();
    }
    function pass_to_function() {
        //process $this->a
    }
}

尽快的 unset 它们, 让内存得以释放,减轻脚本负担.



避免直接写SQL, 抽象之

不厌其烦的写了太多如下的语句:

$query = "INSERT INTO users(name , email , address , phone) VALUES('$name' , '$email' , '$address' , '$phone')";
$db->query($query); //call to mysqli_query()

这不是个建壮的方案. 它有些缺点:

>>每次都手动转义值

>>验证查询是否正确

>>查询的错误会花很长时间识别(除非每次都用if-else检查)

>>很难维护复杂的查询

因此使用函数封装:

function insert_record( $table_name, $data ) {
    foreach ( $data as $key => $value ) {
        //mysqli_real_escape_string
        $data[$key] = $db->mres( $value );
    }
    $fields = implode( ',', array_keys( $data ) );
    $values = "'" . implode( "','", array_values( $data ) ) . "'";
    //Final query
    $query = "INSERT INTO {$table}($fields) VALUES($values)";
    return $db->query( $query );
}
$data = array( 'name' => $name, 'email' => $email, 'address' => $address, 'phone' => $phone );
insert_record( 'users', $data );

看到了吗? 这样会更易读和扩展. record_data 函数小心的处理了转义.

最大的优点是数据被预处理为一个数组, 任何语法错误都会被捕获.

该函数应该定义在某个database类中, 你可以像 $db->insert_record这样调用.

查看本文, 看看怎样让你处理数据库更容易.

类似的也可以编写update,select,delete方法. 试试吧.



在数据库中保存session

基于文件的session策略会有很多限制. 使用基于文件的session不能扩展到集群中, 因为session保存在单个服务器中. 但数据库可被多个服务器访问, 这样就可以解决问题.

在数据库中保存session数据, 还有更多好处:

>>处理username重复登录问题. 同个username不能在两个地方同时登录.

>>能更准备的查询在线用户状态.



避免使用全局变量

>>使用 defines/constants

>>使用函数获取值

>>使用类并通过$this访问



不要过分依赖 set_time_limit

如果你想限制最小时间, 可以使用下面的脚本:

set_time_limit(30);
//Rest of the code

高枕无忧吗? 注意任何外部的执行, 如系统调用,socket操作, 数据库操作等, 就不在set_time_limits的控制之下.

因此, 就算数据库花费了很多时间查询, 脚本也不会停止执行. 视情况而定.


使用扩展库

一些例子:

>>mPDF — 能通过html生成pdf文档

>>PHPExcel — 读写excel

>>PhpMailer — 轻松处理发送包含附近的邮件

>>pChart — 使用php生成报表

使用开源库完成复杂任务, 如生成pdf, ms-excel文件, 报表等.


使用MVC框架

是时候使用像 codeigniter 这样的MVC框架了. MVC框架并不强迫你写面向对象的代码. 它们仅將php代码与html分离.

>>明确区分php和html代码. 在团队协作中有好处, 设计师和程序员可以同时工作.

>>面向对象设计的函数能让你更容易维护

>>内建函数完成了很多工作, 你不需要重复编写

>>开发大的应用是必须的

>>很多建议, 技巧和hack已被框架实现了


时常看看phpbench

phpbench提供了些php基本操作的基准测试结果, 它展示了一些徽小的语法变化是怎样导致巨大差异的。



 钟永标

个人头像


 热门推荐


 热门阅读