建议与指正

最后更新于:2022-04-01 21:14:16

感谢阅读!如果你有些地方还不太理解,很正常,PHP 是复杂的,并且充斥着陷阱。 因为我也只是一个人,所以本文档中难免存在错误。 如果你想为本文档贡献建议或纠正错误之处, 请使用[最后修订日期&维护者](40856) 一节中的信息联系我。
';

检测一个值是否为 null 或 false

最后更新于:2022-04-01 21:14:13

## 使用 [===](http://php.net/manual/zh/language.operators.comparison.php) 操作符来检测 null 和布尔 false 值。 PHP 宽松的类型系统提供了许多不同的方法来检测一个变量的值。 然而这也造成了很多问题。 使用 `==` 来检测一个值是否为 null 或 false,如果该值实际上是一个空字符串或 0,也会误报为 false。 [isset](http://php.net/manual/zh/function.isset.php) 是检测一个变量是否有值, 而不是检测该值是否为 null 或 false,因此在这里使用是不恰当的。 [is_null()](http://php.net/manual/zh/function.is-null.php) 函数能准确地检测一个值是否为 null, [is_bool](http://php.net/manual/zh/function.is-bool.php) 可以检测一个值是否是布尔值(比如 false), 但存在一个更好的选择:`===` 操作符。`===` 检测两个值是否同一, 这不同于 PHP 宽松类型世界里的 **相等**。它也比 is_null() 和 is_bool() 要快一些,并且有些人认为这比使用函数来做比较更干净些。 ## 示例 ~~~ ~~~ ## 陷阱 * 测试一个返回 0 或布尔 false 的函数的返回值时,如 strpos(),始终使用 `===` 和`!==`,否则你就会碰到问题。 ## 进一步阅读 * [PHP 手册:比较操作符](http://php.net/manual/zh/language.operators.comparison.php) * [Stack Overflow: is_null() vs ===](http://stackoverflow.com/questions/8228837/is-nullx-vs-x-null-in-php) * [Laruence:isset 和 is_null 的不同](http://www.laruence.com/2009/12/09/1180.html)
';

处理日期和时间

最后更新于:2022-04-01 21:14:11

## 使用[DateTime 类](http://www.php.net/manual/en/class.datetime.php)。 在 PHP 糟糕的老时光里,我们必须使用 [date()](http://www.php.net/manual/en/function.date.php), [gmdate()](http://www.php.net/manual/en/function.gmdate.php), [date_timezone_set()](http://www.php.net/manual/en/function.date-timezone-set.php), [strtotime()](http://www.php.net/manual/en/function.strtotime.php)等等令人迷惑的 组合来处理日期和时间。悲哀的是现在你仍旧会找到很多在线教程在讲述这些不易使用的老式函数。 幸运的是,我们正在讨论的 PHP 版本包含友好得多的 [DateTime 类](http://www.php.net/manual/en/class.datetime.php)。 该类封装了老式日期函数所有功能,甚至更多,在一个易于使用的类中,并且使得时区转换更加容易。 在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()](http://www.php.net/manual/en/datetime.construct.php) 就会将生成日期的时区设置为正在运行的计算机的时区。之后,这会导致大量令人头疼的事情。 **在创建新日期时始终指定 UTC 时区,除非你确实清楚自己在做的事情。** * 如果你在 DateTime::__construct() 中使用 Unix 时间戳,那么时区将始终设置为 UTC 而不管第二个参数你指定了什么。 * 向 DateTime::__construct() 传递零值日期(如:“0000-00-00”,常见 MySQL 生成该值作为 DateTime 类型数据列的默认值)会产生一个无意义的日期,而不是“0000-00-00”。 * 在 32 位系统上使用 [DateTime::getTimestamp()](http://www.php.net/manual/en/datetime.gettimestamp.php) 不会产生代表 2038 年之后日期的时间戳。64 位系统则没有问题。 ## 进一步阅读 * [PHP 手册:DateTime 类](http://www.php.net/manual/en/book.datetime.php) * [Stack Overflow: 访问超出 2038 的日期](http://stackoverflow.com/questions/5319710/accessing-dates-in-php-beyond-2038)
';

PHP 与 UTF-8

最后更新于:2022-04-01 21:14:09

## 没有一行式解决方案。小心、注意细节,以及一致性。 PHP 中的 UTF-8 糟透了。原谅我的用词。 目前 PHP 在低层次上还不支持 Unicode。有几种方式可以确保 UTF-8 字符串能够被正确处理, 但并不容易,需要深入到 web 应用的所有层面,从 HTML,到 SQL,到 PHP。我们旨在提供一个简洁、 实用的概述。 ## PHP 层面的 UTF-8 基本的[字符串操作](http://php.net/manual/zh/language.operators.string.php),如串接 两个字符串、将字符串赋给变量,并不需要任何针对 UTF-8 的特殊东西。 然而,多数[字符串函数](http://php.net/manual/zh/ref.strings.php),如 [strpos()](http://php.net/manual/zh/function.strpos.php) 和 [strlen](http://php.net/manual/zh/function.strlen.php),就需要特殊的考虑。 这些函数都有一个对应的 `mb_*` 函数:例如,[mb_strpos()](http://php.net/manual/zh/function.mb-strpos.php) 和 [mb_strlen()](http://php.net/manual/zh/function.mb-strlen.php)。 这些对应的函数统称为[多字节字符串函数](http://php.net/manual/zh/ref.mbstring.php)。 这些多字节字符串函数是专门为操作 Unicode 字符串而设计的。 当你操作 Unicode 字符串时,必须使用 `mb_*` 函数。 例如,如果你使用 [substr()](http://php.net/manual/zh/function.substr.php) 操作一个 UTF-8 字符串,其结果就很可能包含一些乱码。 正确的函数应该是对应的多字节函数, [mb_substr()](http://php.net/manual/zh/function.mb-substr.php)。 难的是始终记得使用 `mb_*` 函数。即使你仅一次忘了,你的 Unicode 字符串在接下来的处理中就可能产生乱码。 并不是所有的字符串函数都有一个对应的 `mb_*`。如果不存在你想要的那一个,那你就只能自认倒霉了。 此外,在每个 PHP 脚本的顶部(或者在全局包含脚本的顶部)你都应使用 [mb_internal_encoding](http://php.net/manual/zh/function.mb-internal-encoding.php) 函数,如果你的脚本会输出到浏览器,那么还得紧跟其后加个[mb_http_output()](http://php.net/manual/zh/function.mb-http-output.php) 函数。在每个脚本中显式地定义字符串的编码在以后能为你减少很多令人头疼的事情。 最后,许多操作字符串的 PHP 函数都有一个可选参数让你指定字符编码。 若有该选项, 你应始终显式地指明 UTF-8 编码。 例如,[htmlentities()](http://php.net/manual/zh/function.htmlentities.php) 就有一个字符编码方式选项,在处理这样的字符串时应始终指定 UTF-8。 ## MySQL 层面的 UTF-8 如果你的 PHP 脚本会访问 MySQL,即使你遵从了前述的注意事项,你的字符串也有可能在数据库中存储为非 UTF-8 字符串。 确保从 PHP 到 MySQL 的字符串为 UTF-8 编码的,确保你的数据库以及数据表均设置为 utf8mb4 字符集, 并且在你的数据库中执行任何其他查询之前先执行 MySQL 查询 `set names utf8mb4`。这是至关重要的。 示例请查看[连接并查询 MySQL 数据库](http://phpbestpractices.justjavac.com/#mysql)一节内容。 注意你必须使用 `utf8mb4` 字符集来获得完整的 UTF-8 支持,而不是 `utf8` 字符集!原因请查看[进一步阅读](http://phpbestpractices.justjavac.com/#utf8-further-reading)。 ## 浏览器层面的 UTF-8 使用 [mb_http_output()](http://php.net/manual/zh/function.mb-http-output.php) 函数 来确保你的 PHP 脚本输出 UTF-8 字符串到浏览器。 并且在 HTML 页面的 ` ` 标签块中包含 [字符集 ` ` 标签块](http://htmlpurifier.org/docs/enduser-utf8.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 page Body); // This should correctly output our transformed UTF-8 string to the browser } ?> ~~~ ## 进一步阅读 * [PHP 手册:多字节字符串函数](http://php.net/manual/zh/ref.mbstring.php) * [PHP UTF-8 备忘单](http://blog.loftdigital.com/blog/php-utf-8-cheatsheet) * [Stack Overflow: 什么因素致使 PHP 不兼容 Unicode?](http://stackoverflow.com/questions/571694/what-factors-make-php-unicode-incompatible) * [Stack Overflow: PHP 与 MySQL 之间国际化字符串的最佳实践](http://stackoverflow.com/questions/140728/best-practices-in-php-and-mysql-with-international-strings) * [怎样在MySQL数据库中完整支持Unicode](http://mathiasbynens.be/notes/mysql-utf8mb4)
';

净化 HTML 输入和输出

最后更新于:2022-04-01 21:14:07

## 对于简单的数据净化,使用 [htmlentities()](http://php.net/manual/zh/function.htmlentities.php) 函数, 复杂的数据净化则使用[HTML Purifier](http://htmlpurifier.org/) 库 **经 HTML Purifier 4.4.0 测试** 在任何 wbe 应用中展示用户输出时,首先对其进行“净化”去除任何潜在危险的 HTML 是非常必要的。 一个恶意的用户可以制作某些 HTML,若被你的 web 应用直接输出,对查看它的人来说会很危险。 虽然可以尝试使用正则表达式来净化 HTML,但不要这样做。HTML是一种复杂的语言,试图使用正则表达式来净化 HTML 几乎总是失败的。 你可能会找到建议你使用 [strip_tags()](http://php.net/manual/zh/function.strip-tags.php) 函数的观点。 虽然 strip_tags() 从技术上来说是安全的,但如果输入的不合法的 HTML(比如, 没有结束标签),它就成了一个「愚蠢」的函数,可能会去除比你期望的更多的内容。 由于非技术用户在通信中经常使用`<` 和` >` 字符,`strip_tags()` 也就不是一个好的选择了。 如果阅读了[验证邮件地址](http://phpbestpractices.justjavac.com/#validating-emails)一节, 你也许也会考虑使用 [filter_var()](http://php.net/manual/zh/function.filter-var.php) 函数。 然而 [filter_var() 函数在遇到断行时会出现问题](http://stackoverflow.com/questions/3150413/filter-sanitize-special-chars-problem-with-line-breaks), 并且需要不直观的配置以接近 [htmlentities()](http://php.net/manual/zh/function.htmlentities.php) 函数的效果, 因此也不是一个好的选择。 ## 对于简单需求的净化 如果你的 web 应用仅需要完全地转义(因此可以无害地呈现,但不是完全去除) HTML, 则使用 PHP 的内建[htmlentities()](http://php.net/manual/zh/function.htmlentities.php) 函数。 这个函数要比 HTML Purifier 快得多,因此它不对 HTML 做任何验证---仅转义所有东西。 htmlentities() 不同于类似功能的函数[htmlspecialchars()](http://php.net/manual/zh/function.htmlspecialchars.php), 它会编码所有适用的 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](http://htmlpurifier.org/) 库。 HTML Purifier 是一个经过充分测试但效率比较低的库。 这就是为什么如果你的需求并不复杂就应使用[htmlentities()](http://php.net/manual/zh/function.htmlentities.php), 因为它的效率要快得多。 HTML Purifier 相比 [strip_tags()](http://php.net/manual/zh/function.strip-tags.php) 是有优势的, 因为它在净化 HTML 之前会对其校验。 这意味着如果用户输入无效 HTML,HTML Purifier 相比 strip_tags() 更能保留 HTML 的原意。 HTML Purifier 高度可定制,允许你为 HTML 的一个子集建立白名单来允许这个 HTML 子集的实体存在输出中。 但其缺点就是相当的慢,它要求一些设置,在一个共享主机的环境里可能是不可行的。 其文档通常也复杂而不易理解。 以下示例是一个基本的使用配置。 查看[文档](http://htmlpurifier.org/docs)阅读 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 一节](http://phpbestpractices.justjavac.com/#utf-8)。 * 使用 htmlentities() 时,始终包含 ENT_QUOTES 和字符编码参数。 默认情况下,htmlentities() 不会对单引号编码。多愚蠢的默认做法! * HTML Purifier 对于复杂的 HTML 效率极其的低。可以考虑设置一个缓存方案如APC来保存经过净化的结果以备后用。 ## 进一步阅读 * [PHP HTML 净化工具对比](http://htmlpurifier.org/comparison)(英文) * [Laruence:PHP Taint – 一个用来检测 XSS/SQL/Shell 注入漏洞的扩展](http://www.laruence.com/2012/02/14/2544.html) * [Stack Overflow: 使用 strip_tags() 来防止 XSS?](http://stackoverflow.com/questions/3605629/php-prevent-xss-with-strip-tags) * [Stack Overflow: PHP中净化用户输入的最佳方法是什么?](http://stackoverflow.com/questions/129677/whats-the-best-method-for-sanitizing-user-input-with-php) * [Stack Overflow: 断行时的 FILTER_SANITIZE_SPECIAL_CHARS 问题](http://stackoverflow.com/questions/3150413/filter-sanitize-special-chars-problem-with-line-breaks)