找回密码
 骑士注册

QQ登录

微博登录

搜索
❏ 站外平台:

Linux中国开源社区 技术 查看内容

PHP最佳实践(译)

2013-06-04 09:19    收藏: 4 分享: 1    

简介

PHP是一门复杂的语言,经过多年折腾,使其不同版本之间高度不一致,有时还有些bug。 每个版本都有自己独有的特性、多余和怪异之处,也很难跟踪哪个版本有哪些问题。这也就 很好理解为什么有时它会遭到那么多的厌恶。

尽管如此,如今它还是Web开发方面最流行的语言。因其悠久的历史,对于实现密码哈希和 数据库访问诸如此类的基本任务你能够找到很多教程。但问题在于,5个教程,你就很有可能 找到5种完全不同的完成任务的方式,那么哪种是“正确”的方式呢?其他方式有难以捉摸的bug 或者陷阱?确实很难搞明白,所以你经常要在互联网上反复查找尝试确认正确的答案。

这也是PHP编程新手频繁地因为丑陋、过时、或不安全的代码而遭到责备的原因之一。如果 Google搜索的第一个结果是一篇4年前的文章,讲述一种5年前的方法,那么PHP新手们也就 很难改变经常遭受责备的现状。

本文档通过为PHP中常见的令人困惑的问题和任务编辑组织一系列被认为最佳实践的基本做法, 来尝试解决上述问题。若一个低层次的任务在PHP中有多种令人困惑的实现方式,本文也会涵盖。

是什么

这是一份指南,在PHP程序员遇到一些常见低层次任务但不明确最佳做法(由于PHP可能提供 了多种解决方案)之时,为其建议最佳实践。例如:连接数据库是一个常见任务,PHP中提供了 大量可行的方案,但并不是所有的都是好的做法,因此,本文也会包含该问题。

本文包含的是一系列简短的、入门性质的方案。涉及的示例在基本设定下就能够运行起来, 你研究一下应该就能把它们变为对你有用的东西。

本文将指出一些我们认为是PHP中最新最好的东西。然而,这意味如果你在使用老版本的PHP, 一些用来实现这些解决方案的特性对你并不可用。

这份文档会一直更新,我会尽我最大努力保持该文档与PHP的发展同步。

不是什么

本文档不是一份PHP教程。你应该在别处学习语言基础和语法。

它也不是一份针对web应用常见问题,如cookie存储、缓存、编程风格、文档等的指南。

它也不是一个安全指南。当本文档触碰到一些安全相关的问题时,也是希望你自己做些研究来 确保你的PHP应用的安全问题。你的代码造成的问题应该都是自己的过错。

该文档也并不是在主张一种特定的编程风格、模式或者框架。

也不是在主张一种特定的方式来完成高层次任务如用户注册、登录系统等。本文档只限于 PHP的悠久历史所造成的一些易混淆或不明确的低层次任务。

它不是一个一劳永逸的解决方案,也不是一个唯一的方案。下面要讲述的一些方法对于你的 特定场景来说也许并不是最好的,存在很多不同的方式来达到同样的目的。特别是,高负载web 应用也许能从更加难懂的方案中获益更多。

我们在使用哪个版本的PHP?

带Suhosin-Patch的PHP 5.3.10-1ubuntu3.6,安装在Ubuntu 12.04 LTS上。

PHP是Web世界里的百年老龟,它的壳上铭刻着一段丰富、复杂、而粗糙的历史。在一个共享 主机的环境里,它的配置可能会限制你能做的事情。

为了保持清晰地叙述,我们将仅针对一个版本的PHP进行讲述。在2013年4月30日时,该版本 为PHP 5.3.10-1ubuntu3.6 with Suhosin-Patch。若你在Ubuntu 12.04 LTS服务器 上使用apt-get进行安装的就是该版本的PHP。

你也许发现这些方案中的一些在其他或者更老版本的PHP上也能工作。如果是这样的话,就由 你来研究在这些更老版本上潜在的难以捉摸的bug或安全问题

存储密码

使用phpass库来哈希和比较密码

经phpass 0.3测试

在存入数据库之前进行哈希保护用户密码的标准方式。许多常用的哈希算法如md5,甚至是sha1 对于密码存储都是不安全的,因为骇客能够使用那些算法轻而易举地破解密码

对密码进行哈希最安全的方法是使用bcrypt算法。开源的phpass库以一个易于使用的类来提供 该功能。

示例

HashPassword('my super cool password');

// You can now safely store the contents of $hashedPassword in your database!

// Check if a user has provided the correct password by comparing what they
// typed with our hash
$hasher->CheckPassword('the wrong password', $hashedPassword);  // false

$hasher->CheckPassword('my super cool password', $hashedPassword);  // true
?>

陷阱

  • 许多资源可能推荐你在哈希之前对你的密码“加盐”。想法很好,但phpass在HashPassword()函数中已经对你的密码“加盐”了,这意味着你不需要自己“加盐”。

进一步阅读

连接并查询MySQL数据库

使用PDO及其预处理语句功能。

在PHP中,有很多方式来连接到一个MySQL数据库。PDO(PHP数据对象)是其中最新且最健壮的一种。PDO跨多种不同类型数据库有一个一致的接口,使用面向对象的方式,支持更多的新数据库支持的特性。

你应该使用PDO的预处理语句函数来帮助防范SQL注入攻击。使用函数bindValue来确保你的SQL免于一级SQL注入攻击。(虽然并不是100%安全的,查看进一步阅读获取更多细节。)在以前,这必须使用一些“魔术引号(magic quotes)”函数的组合来实现。PDO使得那堆东西不再需要。

示例

 \PDO::ERRMODE_EXCEPTION, 
                            \PDO::ATTR_PERSISTENT => false, 
                            \PDO::MYSQL_ATTR_INIT_COMMAND => 'set names utf8mb4'
                        )
                    );
 
    $handle = $link->prepare('select Username from Users where 
         UserId = ? or Username = ? limit ?');
 
    // PHP bug: if you don't specify PDO::PARAM_INT, PDO may enclose
    //  the argument in quotes.
    // This can mess up some MySQL queries that don't expect integers 
    // to be quoted.
    // See: https://bugs.php.net/bug.php?id=44639
    // If you're not sure whether the value you're passing is an integer, 
    // use the is_int() function.
    $handle->bindValue(1, 100, PDO::PARAM_INT);
    $handle->bindValue(2, 'Bilbo Baggins');
    $handle->bindValue(3, 5, PDO::PARAM_INT);
 
    $handle->execute();
 
    // Using the fetchAll() method might be too resource-heavy if you're 
    // selecting a truly massive amount of rows.
    // If that's the case, you can use the fetch() method and loop through 
    // each result row one by one.
    // You can also return arrays and other things instead of objects.  See
    //  the PDO documentation for details.
    $result = $handle->fetchAll(\PDO::FETCH_OBJ);
 
    foreach($result as $row){
        print($row->Username);
    }
}
catch(\PDOException $ex){
    print($ex->getMessage());
}
?>

陷阱

  • 当绑定整型变量时,如果不传递PDO::PARAM_INT参数有事可能会导致PDO对数据加引号。这会 搞坏特定的MySQL查询。查看该bug报告

  • 未使用 `set names utf8mb4` 作为首个查询,可能会导致Unicode数据错误地存储进数据库,这依赖于你的配置。如果你 绝对有把握你的Unicode编码数据不会出问题,那你可以不管这个。

  • 启用持久连接可能会导致怪异的并发相关的问题。这不是一个PHP的问题,而是一个应用层面 的问题。只要你仔细考虑了后果,持久连接一般会是安全的。查看Stack Overfilow这个问题

  • 即使你使用了 `set names utf8mb4` ,你也得确认实际的数据库表使用的是utf8mb4字符集!

  • 可以在单个execute()调用中执行多条SQL语句。只需使用分号分隔语句,但注意这个bug,在该文档所针对的PHP版本中还没修复。

进一步阅读

PHP标签

使用  。

有几种不同的方式用来区分PHP程序块:, , 以及。对于打字来说,更短的标签更方便些,但唯一一种在所有PHP服务器上都一定能工作的标签 是。若你计划将你的PHP应用部署到一台上面的PHP配置你无法控制的服务器上,那么你应始终使用 

若你仅仅是为自己编码,也能控制你将使用的PHP配置,你可能觉得短标签更方便些。但记住 可能会和XML声明冲突,并且实际上是ASP的风格。

无论你选择哪一种,确保一致。

陷阱

  • 在一个纯PHP文件(例如,仅包含一个类定义的文件)中包含一个关闭?>标签时,确保其后 不会跟着任何换行。当PHP解析器安全地吃进跟在关闭标签之后的单个换行符时,任何其他的换行 都可能被输出到浏览器,如果之后要输出某些HTTP头,那么可能会造成混淆。
  • 编写Web应用时,确保在关闭?>标签与html的标签之间不会留下换行。正确的HTML 文件中,标签必须是文件中的第一样东西—在其之前的任何空格或换行都会使其 无效。

进一步阅读

自动加载类

使用spl_autoload_register()来注册你的自动加载函数。

PHP提供了若干方式来自动加载包含还未加载的类的文件。老的方法是使用名为__autoload()魔术全局函数。然而你一次仅能定义一个__autoload()函数,因此如果你的程序 包含一个也使用了__autoload()函数的库,就会发生冲突。

处理这个问题的正确方法是唯一地命名你的自动加载函数,然后使用spl_autoload_register()函数 来注册它。该函数允许定义多个__autoload()这样的函数,因此你不必担心其他代码的__autoload()函数。

示例

进一步阅读

从性能角度来看单引号和双引号

其实并不重要。

已有很多人花费很多笔墨来讨论是使用单引号(')还是双引号(")来定义字符串。 单引号字符串不会被解析,因此放入字符串的任何东西都会以原样显示。双引号字符串会被解析, 字符串中的任何PHP变量都会被求值。另外,转义字符如换行符\n和制表符\t在单引号字符串中 不会被求值,但在双引号字符串中会被求值。

由于双引号字符串在程序运行时要求值,从而理论上使用单引号字符串能提高性能,因为PHP 不会对单引号字符串求值。这对于一定规模的应用来说也许确实如此,但对于现实中一般的应用来说, 区别非常小以至于根本不用在意。因此对于普通应用,你选择哪种字符串并不重要。对于负载 极其高的应用来说,是有点作用的。根据你的应用的需要来做选择,但无论你选择什么,请保持一致。

进一步阅读

123下一页
查看其它分页:

最新评论

我也要发表评论

收藏

返回顶部

分享到微信

打开微信,点击顶部的“╋”,
使用“扫一扫”将网页分享至微信。