找回密码
 骑士注册

QQ登录

微博登录

搜索
❏ 站外平台:

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

PHP最佳实践(译)

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

净化HTML输入和输出

对于简单的数据净化,使用htmlentities()函数, 复杂的数据净化则使用HTML Purifier

经HTML Purifier 4.4.0测试

在任何wbe应用中展示用户输出时,首先对其进行“净化”去除任何潜在危险的HTML是非常必要的。 一个恶意的用户可以制作某些HTML,若被你的web应用直接输出,对查看它的人来说会很危险。

虽然可以尝试使用正则表达式来净化HTML,但不要这样做。HTML是一种复杂的语言,试图 使用正则表达式来净化HTML几乎总是失败的。

你可能会找到建议你使用strip_tags() 函数的观点。虽然strip_tags()从技术上来说是安全的,但如果输入的不合法的HTML(比如, 没有结束标签),它就成了一个“愚蠢”的函数,可能会去除比你期望的更多的内容。由于非技术用户 在通信中经常使用<和>字符,strip_tags()也就不是一个好的选择了。

如果阅读了验证邮件地址一节, 你也许也会考虑使用filter_var() 函数。然而filter_var()函数在遇到断行时会出现问题, 并且需要不直观的配置以接近htmlentities()函数的效果, 因此也不是一个好的选择。

对于简单需求的净化

如果你的web应用仅需要完全地转义(因此可以无害地呈现,但不是完全去除)HTML,则使用 PHP的内建htmlentities()函数。 这个函数要比HTML Purifier快得多,因此它不对HTML做任何验证—仅转义所有东西。

htmlentities()不同于类似功能的函数htmlspecialchars(), 它会编码所有适用的HTML实体,而不仅仅是一个小的子集。

示例

Mua-ha-ha!  Twiddling my evil mustache...

'; // Use the ENT_QUOTES flag to make sure both single and double // quotes are escaped. // Use the UTF-8 character encoding if you've stored the text as // UTF-8 (as you should have). // See the UTF-8 section in this document for more details. $safeHtml = htmlentities($evilHtml, ENT_QUOTES, 'UTF-8'); // $safeHtml is now fully escaped HTML. You can output $safeHtml // to your users without fear! ?>

对于复杂需求的净化

对于很多web应用来说,简单地转义HTML是不够的。你可能想完全去除任何HTML,或者允许 一小部分子集的HTML存在。若是如此,则使用HTML Purifier 库。

HTML Purifier是一个经过充分测试但效率比较低的库。这就是为什么如果你的需求并不复杂 就应使用htmlentities(),因为 它的效率要快得多。

HTML Purifier相比strip_tags() 是有优势的,因为它在净化HTML之前会对其校验。这意味着如果用户输入无效HTML,HTML Purifier相比strip_tags()更能保留HTML的原意。HTML Purifier高度可定制,允许你为HTML的一个子集建立白名单来允许这个HTML子集的实体存在 输出中。

但其缺点就是相当的慢,它要求一些设置,在一个共享主机的环境里可能是不可行的。其文档 通常也复杂而不易理解。以下示例是一个基本的使用配置。查看文档 阅读HTML Purifier提供的更多更高级的特性。

示例

Mua-ha-ha!  Twiddling my evil mustache...';
 
// Set up the HTML Purifier object with the default configuration.
$purifier = new HTMLPurifier(HTMLPurifier_Config::createDefault());
 
$safeHtml = $purifier->purify($evilHtml);
// $safeHtml is now sanitized.  You can output $safeHtml to your 
// users without fear!
?>

陷阱

  • 以错误的字符编码使用htmlentities()会造成意想不到的输出。在调用该函数时始终确认 指定了一种字符编码,并且该编码与将被净化的字符串的编码相匹配。更多细节请查看 UTF-8一节
  • 使用htmlentities()时,始终包含ENT_QUOTES和字符编码参数。默认情况下,htmlentities() 不会对单引号编码。多愚蠢的默认做法!
  • HTML Purifier对于复杂的HTML效率极其的低。可以考虑设置一个缓存方案如APC来保存经过净化的结果 以备后用。

进一步阅读

PHP与UTF-8

没有一行式解决方案。小心、注意细节,以及一致性。

PHP中的UTF-8糟透了。原谅我的用词。

目前PHP在低层次上还不支持Unicode。有几种方式可以确保UTF-8字符串能够被正确处理, 但并不容易,需要深入到web应用的所有层面,从HTML,到SQL,到PHP。我们旨在提供一个简洁、 实用的概述。

PHP层面的UTF-8

基本的字符串操作,如串接 两个字符串、将字符串赋给变量,并不需要任何针对UTF-8的特殊东西。然而,多数 字符串函数,如strpos() 和strlen,就需要特殊的考虑。这些 函数都有一个对应的mb_*函数:例如,mb_strpos()mb_strlen()。这些对应的函数 统称为多字节字符串函数。这些多字节字符串 函数是专门为操作Unicode字符串而设计的。

当你操作Unicode字符串时,必须使用mb_*函数。例如,如果你使用substr() 操作一个UTF-8字符串,其结果就很可能包含一些乱码。正确的函数应该是对应的多字节函数, mb_substr()

难的是始终记得使用mb_*函数。即使你仅一次忘了,你的Unicode字符串在接下来的处理中 就可能产生乱码。

并不是所有的字符串函数都有一个对应的mb_*。如果不存在你想要的那一个,那你就只能 自认倒霉了。

此外,在每个PHP脚本的顶部(或者在全局包含脚本的顶部)你都应使用 mb_internal_encoding 函数,如果你的脚本会输出到浏览器,那么还得紧跟其后加个mb_http_output() 函数。在每个脚本中显式地定义字符串的编码在以后能为你减少很多令人头疼的事情。

最后,许多操作字符串的PHP函数都有一个可选参数让你指定字符编码。若有该选项, 你应 始终显式地指明UTF-8编码。例如,htmlentities() 就有一个字符编码方式选项,在处理这样的字符串时应始终指定UTF-8。

MySQL层面的UTF-8

如果你的PHP脚本会访问MySQL,即使你遵从了前述的注意事项,你的字符串也有可能在数据库 中存储为非UTF-8字符串。

确保从PHP到MySQL的字符串为UTF-8编码的,确保你的数据库以及数据表均设置为utf8mb4字符集, 并且在你的数据库中执行任何其他查询之前先执行MySQL查询`set names utf8mb4`。这是至关重要的。示例 请查看连接并查询MySQL数据库一节内容。

注意你必须使用`utf8mb4`字符集来获得完整的UTF-8支持,而不是`utf8`字符集!原因 请查看进一步阅读

浏览器层面的UTF-8

使用mb_http_output()函数 来确保你的PHP脚本输出UTF-8字符串到浏览器。并且在HTML页面的标签块中包含字符集标签块

示例

 \PDO::ERRMODE_EXCEPTION,
                        \PDO::ATTR_PERSISTENT => false,
                        \PDO::MYSQL_ATTR_INIT_COMMAND => 'set names utf8mb4'
                    )
                );
     
// Store our transformed string as UTF-8 in our database
// Assume our DB and tables are in the utf8mb4 character set and collation
$handle = $link->prepare('insert into Sentences (Id, Body) values (?, ?)');
$handle->bindValue(1, 1, PDO::PARAM_INT);
$handle->bindValue(2, $string);
$handle->execute();
 
// Retrieve the string we just stored to prove it was stored correctly
$handle = $link->prepare('select * from Sentences where Id = ?');
$handle->bindValue(1, 1, PDO::PARAM_INT);
$handle->execute();
    
// Store the result into an object that we'll output later in our HTML
$result = $handle->fetchAll(\PDO::FETCH_OBJ);
?>UTF-8 test pageBody);  
            // This should correctly output our transformed UTF-8 string to the browser
        }
        ?>

进一步阅读

处理日期和时间

使用DateTime类

在PHP糟糕的老时光里,我们必须使用date(), gmdate(), date_timezone_set(), strtotime()等等令人迷惑的 组合来处理日期和时间。悲哀的是现在你仍旧会找到很多在线教程在讲述这些不易使用的老式函数。

幸运的是,我们正在讨论的PHP版本包含友好得多的DateTime类。 该类封装了老式日期函数所有功能,甚至更多,在一个易于使用的类中,并且使得时区转换更加容易。 在PHP中始终使用DateTime类来创建,比较,改变以及展示日期。

示例

add(new DateInterval('P10D'));
 
echo($date->format('Y-m-d h:i:s')); // 2011-05-14 05:00:00
 
// Sadly we don't have a Middle Earth timezone
// Convert our UTC date to the PST (or PDT, depending) time zone
$date->setTimezone(new DateTimeZone('America/Los_Angeles'));
 
// Note that if you run this line yourself, it might differ by an 
// hour depending on daylight savings
echo($date->format('Y-m-d h:i:s')); // 2011-05-13 10:00:00
 
$later = new DateTime('2012-05-20', new DateTimeZone('UTC'));
 
// Compare two dates
if($date < $later)
    echo('Yup, you can compare dates using these easy operators!');
 
// Find the difference between two dates
$difference = $date->diff($later);
 
echo('The 2nd date is ' . $difference['days'] . ' later than 1st date.');
?>

陷阱

  • 如果你不指定一个时区,DateTime::__construct() 就会将生成日期的时区设置为正在运行的计算机的时区。之后,这会导致大量令人头疼的事情。 在创建新日期时始终指定UTC时区,除非你确实清楚自己在做的事情。
  • 如果你在DateTime::__construct()中使用Unix时间戳,那么时区将始终设置为UTC而不管 第二个参数你指定了什么。
  • 向DateTime::__construct()传递零值日期(如:“0000-00-00”,常见MySQL生成该值作为 DateTime类型数据列的默认值)会产生一个无意义的日期,而不是“0000-00-00”。
  • 在32位系统上使用DateTime::getTimestamp() 不会产生代表2038年之后日期的时间戳。64位系统则没有问题。

进一步阅读

检测一个值是否为null或false

使用===操作符来检测null和布尔false值。

PHP宽松的类型系统提供了许多不同的方法来检测一个变量的值。然而这也造成了很多问题。 使用==来检测一个值是否为null或false,如果该值实际上是一个空字符串或0,也会误报 为false。isset是检测一个变量是否有值, 而不是检测该值是否为null或false,因此在这里使用是不恰当的。

is_null()函数能准确地检测一个值 是否为null,is_bool可以检测一个值 是否是布尔值(比如false),但存在一个更好的选择:===操作符。===检测两个值是否同一, 这不同于PHP宽松类型世界里的相等。它也比is_null()和is_bool()要快一些,并且有些人 认为这比使用函数来做比较更干净些。

示例

陷阱

  • 测试一个返回0或布尔false的函数的返回值时,如strpos(),始终使用===和!==,否则 你就会碰到问题。

进一步阅读

建议与指正

感谢阅读!如果你有些地方还不太理解,很正常,PHP是复杂的,并且充斥着陷阱。因为我也 只是一个人,所以本文档中难免存在错误。

如果你想为本文档贡献建议或纠正错误之处,请使用最后修订日期&维护者 一节中的信息联系我。

原文: PHP Best Practices-A short, practical guide for common and confusing PHP tasks

译者:youngsterxyf 

 

123
查看其它分页:

最新评论

我也要发表评论

收藏

返回顶部

分享到微信

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