PHP最佳实践指南(中英对照)

1,910 阅读24分钟

原文链接

A short, practical guide for common and confusing PHP tasks

一个简短的实用指南,用于常见的和令人困惑的PHP任务

Last revised & maintainers 最后修改和维护人员

This document was last reviewed on January 25, 2019. It was last changed on February 20, 2019. It’s maintained by me, Alex Cabal. I’ve been writing PHP for a long time now, and currently I run Scribophile, an online writing group for serious writers, Writerfolio, an easy online writing portfolio for freelancers, and Standard Ebooks, an open source project that produces liberated ebooks for the true book lover. Drop me a line if you think I can help you with something, or with suggestions or corrections to this document.

这份文件最后一次审查是在2019年1月25日。最后一次更改是在2019年2月20日。 它是由我Alex Cabal维护的。我已经编写PHP很长时间了,目前我运行于Scribophile,这是一个面向严肃作家的在线写作小组,Writerfolio,这是一个面向自由作家的简单的在线写作组合,还有Standard Ebooks,这是一个开源项目,为真正的图书爱好者提供免费的电子书。 如果你认为我能帮你做些什么,或者对这份文件有什么建议或修改,请给我写信。

Introduction

PHP is a complex language that has suffered years of twists, bends, stretches, and hacks. It’s highly inconsistent and sometimes buggy. Each version has its own unique features, warts, and quirks, and it’s hard to keep track of what version has what problems. It’s easy to see why it gets as much hate as it does sometimes.

PHP是一种复杂的语言,经历了多年的歪曲、夸大和诋毁。这是高度不合逻辑和反常的。每个版本都有自己独特的特性、缺点和怪癖,很难跟踪哪个版本有哪些问题。很容易理解为什么有时候会有那么多的仇恨。

Despite that, it’s the most popular language on the web today. Because of its long history, you’ll find lots of tutorials on how to do basic things like password hashing and database access. The problem is that out of five tutorials, you have a good chance of finding five totally different ways of doing something. Which way is the “right” way? Do any of the other ways have subtle bugs or gotchas? It’s really hard to find out, and you’ll be bouncing around the internet trying to pin down the right answer.

尽管如此,它仍然是当今网络上最流行的语言。由于其悠久的历史,您将发现许多教程,如何做基本的事情,如密码哈希和数据库访问。问题是,在五篇教程中,您很有可能找到五种完全不同的方法。哪条路是“正确”的?其他方法是否有细微的bug或陷阱?这真的很难找到,你会在互联网上跳来跳去试图找到正确的答案。

That’s also one of the reasons why new PHP programmers are so frequently blamed for ugly, outdated, or insecure code. They can’t help it if the first Google result was a four-year-old article teaching a five-year-old method!

这也是为什么新的PHP程序员经常因为丑陋、过时或不安全的代码而受到指责的原因之一。如果谷歌的第一个结果是四年前的一篇文章教的是五年前的方法,那他们就没办法了!

This document tries to address that. It’s an attempt to compile a set of basic instructions for what can be considered best practices for common and confusing issues and tasks in PHP. If a low-level task has multiple and confusing approaches in PHP, it belongs here.

本文档试图解决这个问题。它试图编写一套基本的操作指南,用于PHP中常见和令人困惑的问题和任务的最佳实践。如果一个低阶任务在PHP中有多种令人困惑的方法,那么它就属于这里。

What this is 这是什么

It’s a guide suggesting the best direction to take when facing one of the common low-level tasks a PHP programmer might encounter that are unclear because of the many options PHP might offer. For example: connecting to a database is a common task with a large amount of possible solutions in PHP, not all of them good ones—thus, it’s included in this document.

这是一份指南,建议在面对PHP程序员可能遇到的常见低阶任务时采取的最佳方向。由于PHP可能提供许多选项,这些任务并不清楚。例如:连接到数据库是一项常见的任务,在PHP中有大量可能的解决方案,但并不是所有的都是好的解决方案——因此,本文包含了这些解决方案。

It’s a series of short, introductory solutions. Examples should get you up and running in a basic setting, and you should do your own research to flesh them out into something useful to you.

它是一系列简短的介绍性解决方案。示例应该让您在基本设置中启动和运行,并且您应该自己进行研究,将它们充实成对您有用的内容。

It points to what we consider the state-of-the-art of PHP. However, this means that if you’re using an older version of PHP, some of the features required to pull off these solutions might not be available to you. This is a living document that I’ll do my best to keep updated as PHP continues to evolve.

它指出了什么是我们认为的最先进的PHP。然而,这意味着如果您使用的是较老版本的PHP,那么实现这些解决方案所需的一些特性可能对您来说是不可用的。 这是一个动态的文档,随着PHP的不断发展,我将尽力保持更新。

What this isn’t 这不是什么

This document is not a PHP tutorial. You should learn the basics and syntax of the language elsewhere.

本文档不是PHP教程。您应该在其他地方学习该语言的基础知识和语法。

It’s not a guide to common web application problems like cookie storage, caching, coding style, documentation, and so on.

它不是关于常见web应用程序问题(如cookie存储、缓存、编码风格、文档等)的指南。

It’s not a security guide. While it touches upon some security-related issues, you’re expected to do your own research when it comes to securing your PHP apps. In particular, you should carefully review any solution proposed here before implementing it. Your code, and your copy and paste, is your own fault.

它不是安全指南。虽然它涉及到一些与安全相关的问题,但是当涉及到保护PHP应用程序时,您需要自己进行研究。特别是,在实现它之前,您应该仔细检查这里提出的任何解决方案。您的代码,以及您的复制和粘贴,都是您自己的错误。

It’s not an advocate of a certain coding style, pattern, or framework.

它并不提倡某种编码风格、模式或框架

It’s not an advocate for a certain way of doing high-level tasks like user registration, login systems, etc. This document is strictly for low-level tasks that, because of PHP’s long history, might be confusing or unclear.

它并不提倡用某种方式来完成高级任务,比如用户注册、登录系统等等。本文档严格适用于低层任务,由于PHP的历史很长,这些任务可能令人困惑或不清楚。

It’s not a be-all and end-all solution, nor is it the only solution. Some of the methods described below might not be what’s best for your particular situation, and there are lots of different ways of achieving the same ends. In particular, high-load web apps might benefit from more esoteric solutions to some of these problems.

它不是万能的解决方案,也不是唯一的解决方案。下面描述的一些方法可能并不适合您的特定情况,并且有许多不同的方法可以达到相同的目的。特别是,高负载web应用程序可能会受益于这些问题的一些更深奥的解决方案

What PHP version are we using? 我们使用的是什么PHP版本?

PHP 7.2.10-0ubuntu0.18.04.1, installed on Ubuntu 18.04 LTS.

PHP is the 100-year-old tortoise of the web world. Its shell is inscribed with a rich, convoluted, and gnarled history. In a shared-hosting environment, its configuration might restrict what you can do.

PHP是web世界的百年老古董。它的外壳上镌刻着丰富、曲折、多节的历史。在共享宿主环境中,其配置可能会限制您的操作

In order to retain a scrap of sanity, we’re going to focus on just one version of PHP: PHP 7.2.10-0ubuntu0.18.04.1. This is the version of PHP you’ll get if you install it using apt-get on an Ubuntu 18.04 LTS server. In other words, it’s the sane default used by many.

为了保持一点理智,我们只关注PHP的一个版本:PHP 7.2.10-0ubuntu0.18.04.1。如果您在Ubuntu 18.04 LTS服务器上使用apt-get安装PHP,就会得到这个版本。换句话说,这是许多人使用的正常默认值。

You might find that some of these solutions work on different or older versions of PHP. If that’s the case, it’s up to you to research the implications of subtle bugs or security issues in these older versions.

您可能会发现,其中一些解决方案适用于不同或较老版本的PHP。如果是这样的话,就由您来研究这些旧版本中细微的bug或安全问题的含义。

Storing passwords

Use the built-in password hashing functions to hash and compare passwords.

使用内置的密码散列函数散列和比较密码。

Hashing is the standard way of protecting a user’s password before it’s stored in a database. Many common hashing algorithms like md5 and even sha1 are unsafe for storing passwords, because hackers can easily crack passwords hashed using those algorithms.

哈希是在用户密码存储到数据库之前保护其密码的标准方法。许多常见的哈希算法,如md5甚至sha1,对于存储密码都是不安全的,因为黑客可以很容易地破解使用这些算法哈希的密码。

Example

<?php
// Hash the password.  $hashedPassword will be a 60-character string.
$hashedPassword = password_hash('my super cool password', PASSWORD_DEFAULT);
 
// 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
password_verify('the wrong password', $hashedPassword); // false
 
password_verify('my super cool password', $hashedPassword); // true
?>

Gotchas 陷阱

  • Many sources will recommend that you also “salt” your password before hashing it. That’s a great idea, and password_hash() already salts your password for you. That means that you don’t have to salt it yourself.

    许多资料来源会建议您在散列密码之前也要“加盐”。这是个好主意,但是password_hash()已经为您做了“加盐”。这意味着你不需要自己再加盐。

Further reading 延伸阅读

Connecting to and querying a MySQL database 连接并查询MySQL数据库

Use PDO and its prepared statement functionality.

使用PDO及其准备好的语句功能。

There are many ways to connect to a MySQL database in PHP. PDO (PHP Data Objects) is the newest and most robust of them. PDO has a consistent interface across many different types of database, uses an object-oriented approach, and supports more features offered by newer databases.

在PHP中,有许多方法可以连接到MySQL数据库。PDO (PHP数据对象)是其中最新的、最健壮的。PDO具有跨许多不同类型数据库的一致接口,使用面向对象的方法,并支持更新数据库提供的更多特性。

You should use PDO’s prepared statement functions to help prevent SQL injection attacks. Using the bindValue() function ensures that your SQL is safe from first-order SQL injection attacks. (This isn’t 100% foolproof though, see Further Reading for more details.) In the past, this had to be achieved with some arcane combination of “magic quote” functions. PDO makes all that gunk unnecessary.

您应该使用PDO的预处理语句函数来帮助防止SQL注入攻击。使用bindValue()函数可以确保SQL不会受到一阶SQL注入攻击。(不过,这并不是100%万无一失的,更多细节请参阅后续阅读。)在过去,这必须通过一些神秘的“magic quote”函数组合来实现。PDO让所有的奇技淫巧都没有必要了。

Example

<?php
// Create a new connection.
// You'll probably want to replace hostname with localhost in the first parameter.
// Note how we declare the charset to be utf8mb4.  This alerts the connection that we'll be passing UTF-8 data.  This may not be required depending on your configuration, but it'll save you headaches down the road if you're trying to store Unicode strings in your database.  See "Gotchas".
// The PDO options we pass do the following:
// PDO::ATTR_ERRMODE enables exceptions for errors.  This is optional but can be handy.
// PDO::ATTR_PERSISTENT disables persistent connections, which can cause concurrency issues in certain cases.  See "Gotchas".
$link = new PDO(    'mysql:host=your-hostname;dbname=your-db;charset=utf8mb4',
                    'your-username',
                    'your-password',
                    array(
                        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                        PDO::ATTR_PERSISTENT => false
                    )
                );
 
$handle = $link->prepare('select Username from Users where UserId = ? or Username = ? limit ?');
 
$handle->bindValue(1, 100);
$handle->bindValue(2, 'Bilbo Baggins');
$handle->bindValue(3, 5);
 
$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);
}
?>

Gotchas 陷阱

  • Not having set the character set to utf8mb4 in the connection string might cause Unicode data to be stored incorrectly in your database, depending on your configuration.

    在连接字符串中没有将字符集设置为utf8mb4可能会导致Unicode数据在数据库中存储不正确,这取决于您的配置。

  • Even if you declare your character set to be utf8mb4, make sure that your actual database tables are in the utf8mb4 character set. For why we use utf8mb4 instead of just utf8, check the PHP and UTF-8 section.

    即使将字符集声明为utf8mb4,也要确保实际的数据库表是utf8mb4字符集。 要了解为什么我们使用utf8mb4而不是utf8,请检查PHP和UTF-8部分。

  • Enabling persistent connections can possibly lead to weird concurrency-related issues. This isn’t a PHP problem, it’s an app-level problem. Persistent connections are safe to use as long as you consider the consequences. See this Stack Overflow question.

    启用持久连接可能会导致奇怪的并发相关问题。这不是PHP问题,而是应用程序级别的问题。只要考虑到后果,使用持久连接是安全的。参见这个堆栈溢出问题

Further reading 延伸阅读

PHP tags PHP标签

Use <?php ?>.

There are a few different ways to delimit blocks of PHP: <?php ?>, <?= ?>, <? ?>, and <% %>. While the shorter ones might be more convenient to type, they’re disabled by default and must be enabled by configuring the PHP server with the short_open_tag option. Therefore the only method that’s guaranteed to work on all PHP servers is <?php ?>. If you ever plan on deploying your PHP to a server whose configuration you can’t control, then you should always use <?php ?>.

有几种不同的方法来分隔PHP块:<?php ?>, <?= ?>, <? ?>, 和 <% %>。虽然较短的选项可能更便于输入,但它们在默认情况下是禁用的,必须使用short_open_tag选项配置PHP服务器来启用它们。因此,保证在所有PHP服务器上工作的唯一方法是<?php ? >。如果您计划将PHP部署到无法控制其配置的服务器,那么应该始终使用<?php ? >。

Fortunately <?= is available regardless of whether or not short tags are enabled, so it’s always safe to use that shorthand instead of <? php print() ?>.

幸运的是<?= 是可用的,不管是否启用了短标记,所以使用这种简写代替<?php print () ? >也是安全的。

If you’re only coding for yourself and have control over the PHP configuration you’ll be using, you might find the shorter tags to be more convenient. But remember that <? ?>; might conflict with XML declarations and <% %> is actually ASP style. Whatever you choose, make sure you stay consistent!

如果您只是为自己编写代码,并且能够控制将要使用的PHP配置,那么您可能会发现更短的标记更方便。但是记得<? ?>可能与XML声明冲突,<% %>实际上是ASP风格。无论你选择什么,一定要保持一致!

Gotchas 陷阱

  • When including a closing ?> tag in a pure PHP file (for example, in a file that only contains a class definition), make sure not to leave any trailing newlines after it. While the PHP parser safely “eats” a single newline character after the closing tag, any other newlines might be outputted to the browser and possibly confuse things if you’re outputting any HTTP headers later.

    当在纯PHP文件中包含一个关闭 ?>标记时(例如,在一个只包含类定义的文件中),请确保不要在它后面留下任何尾随的换行符。虽然PHP解析器在结束标记之后安全地“吃掉”了一个换行符,但是任何其他换行符都可能输出到浏览器中,如果稍后输出任何HTTP头信息,可能会造成混淆。

  • When writing a web app targeting older versions of IE, make sure not to leave a newline between any closing ?>; tag and the html <!doctype> tag. Old versions of IE will enter quirks mode if they encounter any white space, including newlines, before the doctype declaration. This isn’t an issue for newer versions of IE and other, more advanced browsers. (Read: every other browser besides IE.)

    当编写一个针对较老版本IE的web应用程序时,请确保在任何结束符?>标签和html <!doctype之间不要留下新的换行。因为旧版本的IE在doctype声明之前遇到任何空白(包括换行),就会进入quirks模式。对于更新版本的IE和其他更高级的浏览器来说,这不是问题。(阅读:除IE之外的所有其他浏览器。)

Further reading 扩展阅读

Auto-loading classes 自动加载类

Use spl_autoload_register() to register your auto-load function.

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

PHP provides several ways to auto-load files containing classes that haven’t yet been loaded. The older way is to use a magic global function called __autoload(). However you can only have one __autoload() function defined at once, so if you’re including a library that also uses the __autoload() function, then you’ll have a conflict.

PHP提供了几种方法来自动加载包含尚未加载的类的文件。较老的方法是使用一个名为__autoload()的全局魔术函数。但是,您一次只能定义一个__autoload()函数,所以如果您包含一个也使用了__autoload()函数的库,那么您将会遇到冲突。

The correct way to handle this is to name your autoload function something unique, then register it with the spl_autoload_register() function. This function allows more than one __autoload() function to be defined, so you won’t step on any other code’s own __autoload() function.

处理此问题的正确方法是将autoload函数命名为惟一的,然后使用spl_autoload_register()函数注册它。这个函数允许定义多个_autoload()函数,因此您不会踩到任何其他代码自己的_autoload()函数。

Example

<?php
// First, define your auto-load function.
function MyAutoload($className){
    include_once($className . '.php');
}
 
// Next, register it with PHP.
spl_autoload_register('MyAutoload');
 
// Try it out!
// Since we haven't included a file defining the MyClass object, our auto-loader will kick in and include MyClass.php.
// For this example, assume the MyClass class is defined in the MyClass.php file.
$var = new MyClass();
?>

Further reading 扩展阅读

Single vs. double quotes from a performance perspective 从性能的角度来看,单引号和双引号

It doesn’t really matter. 这并不重要。

A lot of ink has been spilled about whether to define strings with single quotes (‘) or double quotes (“). Single-quoted strings aren’t parsed, so whatever you’ve put in the string, that’s what will show up. Double-quoted strings are parsed and any PHP variables in the string are evaluated. Additionally, escaped characters like \n for newline and \t for tab are not evaluated in single-quoted strings, but are evaluated in double-quoted strings.

关于是使用单引号(')定义字符串还是使用双引号(")定义字符串,已经花费了大量的笔墨。单引号字符串不会被解析,所以无论你在字符串中放入了什么,都会显示出来。双引号字符串解析并计算字符串中的任何PHP变量。此外,换行的\n和制表符的\t等转义字符不在单引号字符串中求值,而是在双引号字符串中求值。

Because double-quoted strings are evaluated at run time, the theory is that using single-quoted strings will improve performance because PHP won’t have to evaluate every single string. While this might be true on a certain scale, for the average real-life application the difference is so small that it doesn’t really matter. So for an average app, it doesn’t matter what you choose. For extremely high-load apps, it might matter a little. Make a choice depending on what your app needs, but whatever you choose, be consistent.

因为双引号字符串是在运行时计算的,所以理论上使用单引号字符串将提高性能,因为PHP不必计算每个字符串。虽然这在一定程度上可能是正确的,但是对于实际的应用程序来说,这种差异非常小,所以实际上并不重要。所以对于一个普通的应用,你选择什么并不重要。对于非常高负载的应用程序,这可能有点关系。根据您的应用程序的需要做出选择,但是无论您选择什么,都要保持一致。

Further reading

PHP and Memcached

If you need a distributed cache, use the Memcached client library. Otherwise, use APCu.

如果需要分布式缓存,请使用Memcached客户机库。否则,使用APCu。

A caching system can often improve your app’s performance. Memcached is a popular choice and it works with many languages, including PHP.

缓存系统通常可以提高应用程序的性能。Memcached是一个流行的选择,它可以与许多语言兼容,包括PHP。

However, when it comes to accessing a Memcached server from a PHP script, you have two different and very stupidly named choices of client library: Memcache and Memcached. They’re different libraries with almost the same name, and both are used to access a Memcached instance.

然而,当涉及到从PHP脚本访问Memcached服务器时,您有两个不同的客户端扩展库选择,它们的名称都非常愚蠢:Memcache和Memcached。它们是名称几乎相同的不同库,都用于访问Memcached实例。

It turns out that the Memcached library is the one that best implements the Memcached protocol. It includes a few useful features that the Memcache library doesn’t, and seems to be the one most actively developed.

事实证明,Memcached库是最好地实现Memcached协议的库。它包含一些Memcache库没有的有用特性,而且似乎是发展最活跃的。

However if you don’t need to access a Memcached instance from a series of distributed servers, then use APCu instead. APCu is supported by the PHP project and has much of the same functionality as Memcached.

但是,如果不需要从一系列分布式服务器访问Memcached实例,则使用APCu。APCu由PHP项目支持,并且具有与Memcached相同的功能。

Installing the Memached client library After you install the Memcached server, you need to install the Memcached client library. Without the library, your PHP scripts won’t be able to communicate with the Memcached server.

安装Memached客户端扩展库 安装Memcached服务器之后,需要安装Memcached客户机库。如果没有这个库,您的PHP脚本将无法与Memcached服务器通信。

You can install the Memcached client library on Ubuntu 16.04 by running this command in your terminal:

你可以在你的终端上运行这个命令在Ubuntu 16.04上安装Memcached客户端库:

user@localhost: sudo apt-get install php-memcached

Using APCu instead

Before Ubuntu 14.04, the APC project was both an opcode cache and a Memcached-like key-value store. Since the version of PHP that ships since Ubuntu 14.04 now includes a built-in opcode cache, APC was split into the APCu project, which is essentially APC’s key-value storage functionality—AKA the “user cache”, or the “u” in APCu—without the opcode-cache parts.

在Ubuntu 14.04之前,APC项目既是一个操作码缓存,也是一个类似memcache的键值存储。由于Ubuntu 14.04之后发布的PHP版本现在包含了一个内置的操作码缓存,APC被分割成APCu项目,APCu项目本质上是APC的key-value存储功能——也就是APCu中的“user cache”或“u”——没有操作码缓存部分。

Installing APCu

You can install APCu on Ubuntu 16.04 by running this command in your terminal:

你可以通过在你的终端运行这个命令在Ubuntu 16.04上安装APCu:

sudo apt-get install php-apcu

Example

<?php
// Store some values in the APCu cache.  We can optionally pass a time-to-live, but in this example the values will live forever until they're garbage-collected by APCu.
apcu_store('username-1532', 'Frodo Baggins');
apcu_store('username-958', 'Aragorn');
apcu_store('username-6389', 'Gandalf');
 
// You can store arrays and objects too.
apcu_store('creatures', array('ent', 'dwarf', 'elf'));
apcu_store('saruman', new Wizard());
 
// After storing these values, any PHP script can access them, no matter when it's run!
$value = apcu_fetch('username-958', $success);
if($success === true){
    print($value); // Aragorn
}
 
$value = apcu_fetch('creatures', $success);
if($success === true){
    print_r($value);
}
 
$value = apcu_fetch('username-1', $success); // $success will be set to boolean false, because this key doesn't exist.
if($success !== true){ // Note the !==, this checks for true boolean false, not "falsey" values like 0 or empty string.
    print('Key not found');
}
 
apcu_delete('username-958'); // This key will no longer be available.
?>

Gotchas 陷阱

  • If you’re migrating APCu code from a version of APUc before 16.04, note that the function names have changed from apc_* to apcu_*. For example, apc_store() became apcu_store().

    如果您正在从16.04之前的APUc版本迁移APCu代码,请注意函数名已经从apc_更改为apcu_。例如,apc_store()变成了apcu_store()。

Further reading 扩展阅读

define() vs. const

Use define() unless readability, class constants, or micro-optimization are concerns.

除非关注可读性、类常量或微优化,否则请使用define()。

Traditionally in PHP you would define constants using the define() function. But at some point PHP gained the ability to also declare constants with the const keyword. Which one should you use when defining your constants?

在PHP中,通常使用define()函数定义常量。但在某个时候,PHP也获得了使用const关键字声明常量的能力。定义常量时应该使用哪一个?

The answer lies in the little differences between the two methods.

答案在于这两种方法之间的细微差别。

  1. define() defines constants at run time, while const defines constants at compile time. This gives const a very slight speed edge, but not one worth worrying about unless you’re building large-scale software.

    define()在运行时定义常量,而const在编译时定义常量。这给了const一个非常小的速度优势,但是不值得担心,除非您正在构建大型软件。

  2. define() puts constants in the global scope, although you can include namespaces in your constant name. That means you can’t use define() to define class constants.

    define()将常量放在全局范围内,不过可以在常量名称中包含名称空间。这意味着您不能使用define()来定义类常量。

  3. define() lets you use expressions both in the constant name and in the constant value, unlike const which allows neither. This makes define() much more flexible.

    define()允许您在常量名称和常量值中同时使用表达式,而const不允许这两者。这使得define()更加灵活。

  4. define() can be called within an if() block, while const cannot.

    define()可以在if()块中调用,而const不能。

Example

<?php
// Let's see how the two methods treat namespaces
namespace MiddleEarthCreatures\Dwarves;
const GIMLI_ID = 1;
define('MiddleEarth\Creatures\Elves\LEGOLAS_ID', 2);
 
print(\MiddleEarth\Creatures\Dwarves\GIMLI_ID); // 1
print(\MiddleEarth\Creatures\Elves\LEGOLAS_ID); // 2; note that we used define(), but the namespace is still recognized
 
// Now let's declare some bit-shifted constants representing ways to enter Mordor.
define('TRANSPORT_METHOD_SNEAKING', 1 << 0); // OK!
const TRANSPORT_METHOD_WALKING = 1 << 1; // Compile error! const can't use expressions as values
 
// Next, conditional constants.
define('HOBBITS_FRODO_ID', 1);
 
if($isGoingToMordor){
    define('TRANSPORT_METHOD', TRANSPORT_METHOD_SNEAKING); // OK!
    const PARTY_LEADER_ID = HOBBITS_FRODO_ID // Compile error: const can't be used in an if block
}
 
// Finally, class constants
class OneRing{
    const MELTING_POINT_CELSIUS = 1000000; // OK!
    define('MELTING_POINT_ELVISH_DEGREES', 200); // Compile error: can't use define() within a class
}
?>

Because define() is ultimately more flexible, it’s the one you should use to avoid headaches unless you specifically require class constants. Using const generally results in more readable code, but at the expense of flexibility. Whichever one you use, be consistent!

因为define()最终更加灵活,所以应该使用它来避免麻烦,除非特别需要类常量。使用const通常会产生更易于阅读的代码,但这是以灵活性为代价的。 无论使用哪一个,都要保持一致!

Further reading

Caching PHP opcode PHP操作码缓存

Lucky you: PHP has a built-in opcode cache!

幸运的是:PHP有一个内置的操作码缓存!

In older versions of PHP, every time a script was executed it would have to be compiled from scratch, even if it had been compiled before. Opcode caches were additional software that saved previously compiled versions of PHP, speeding things up a bit. There were various flavors of caches you could choose from.

在较老版本的PHP中,每次执行脚本时都必须从头编译,即使之前已经编译过了。操作码缓存是另外一种软件,它可以保存以前编译过的PHP版本,从而稍微加快速度。有各种各样的缓存可供选择。

Lucky for us, the version of PHP that ships with Ubuntu 18.04 includes a built-in opcode cache that’s turned on by default. So there’s nothing for you to do!

幸运的是,附带Ubuntu 18.04的PHP版本包含一个默认打开的内置操作码缓存。所以你什么不用做了!

Further reading 扩展阅读

PHP and regex PHP和正则表达式

Use the PCRE (preg_*) family of functions.

使用PCRE (preg_*)函数家族。

Before PHP 7 came around, PHP had two different ways of using regular expressions: the PCRE (Perl-compatible, preg_) functions and the POSIX (POSIX extended, ereg_) functions.

在PHP 7出现之前,PHP有两种使用正则表达式的方法:PCRE (perl兼容的preg_)函数和POSIX (POSIX extended, ereg_)函数。

Each family of functions used a slightly different flavor of regular expression. Luckily for us, the ereg_* functions have been removed in PHP 7, so this source of confusion is past us.

每个函数家族使用的正则表达式略有不同。幸运的是,在PHP 7中已经删除了ereg_*函数,所以这个困惑的根源已经过去了。

Gotchas 陷阱

  • Remember to use the /u flag when working with regexes, to ensure you’re working in Unicode mode.

    记住,在使用regexes时使用/u标志,以确保在Unicode模式下工作。

Further Reading 扩展阅读

Serving PHP from a web server

Use PHP-FPM.

There are several ways of configuring a web server to serve PHP. Back in the stone age, we would use Apache’s mod_php. Mod_php attaches PHP to Apache itself, but Apache does a very bad job of managing it. You’ll suffer from severe memory problems as soon as you get any kind of real traffic.

有几种配置web服务器来服务PHP的方法。回到石器时代,我们使用Apache的mod_php。Mod_php将PHP附加到Apache本身,但是Apache在管理PHP方面做得非常糟糕。一旦你有了真正的流量,你就会有严重的内存问题。

Two new options soon became popular: mod_fastcgi and mod_fcgid. Both of these keep a limited number of PHP processes running, and Apache sends requests to these interfaces to handle PHP execution on its behalf. Because these libraries limit how many PHP processes are alive, memory usage is greatly reduced without affecting performance.

两个新选项很快流行起来:mod_fastcgi和mod_fcgid。这两种方法都保持有限数量的PHP进程运行,Apache向这些接口发送请求,以代表它处理PHP执行。由于这些库限制了有多少PHP进程是活动的,因此内存使用大大减少,而不会影响性能。

Some smart people created an implementation of fastcgi that was specially designed to work really well with PHP, and they called it PHP-FPM. This was the standard solution for web servers since Ubuntu 12.04.

一些聪明的人创建了一个fastcgi的实现,这个实现是专门为PHP而设计的,他们称之为PHP- fpm。这是自Ubuntu 12.04以来web服务器的标准解决方案。

In the years since Ubuntu 12.04, Apache introduced a new method of interacting with PHP-FPM: mod_proxy_fcgi. We’ll use this module to route PHP requests received by Apache to the FPM instance.

自Ubuntu 12.04以来,Apache引入了一种与PHP-FPM交互的新方法:mod_proxy_fcgi。我们将使用这个模块将Apache接收到的PHP请求路由到FPM实例。

The following example is for Apache 2.4.29, but PHP-FPM also works for other web servers like Nginx.

下面的示例适用于Apache 2.4.29,但是PHP-FPM也适用于其他web服务器,比如Nginx。

Installing PHP-FPM and Apache

You can install PHP-FPM and Apache on Ubuntu 18.04 by running these command in your terminal:

sudo apt-get install apache2 php-fpm
sudo a2enmod proxy_fcgi rewrite

First, we’ll create a new PHP FPM pool that will serve our app. Paste the following into /etc/php/7.2/fpm/pool.d/mysite.conf:

[mysite]
user = www-data
group = www-data
 
listen = /run/php/mysite.sock
listen.owner = www-data
listen.group = www-data
 
pm = ondemand
pm.max_children = 10

(Note that you can include many other very interesting options when configuring PHP-FPM pools. Of particular interest is the php_admin_value[include_path] option.)

(注意,在配置PHP-FPM池时,您可以包含许多其他非常有趣的选项。特别有趣的是php_admin_value[include_path]选项。)

Next, we’ll configure our Apache virtualhost to route PHP requests to the PHP-FPM process. Place the following in your Apache configuration file (in Ubuntu the default one is /etc/apache2/sites-available/000-default.conf; if you're using the default configuration, paste this into the existing < VirtualHost > directive).

接下来,我们将配置Apache虚拟主机,将PHP请求路由到PHP- fpm进程。将以下内容放入Apache配置文件中(在Ubuntu中,默认配置文件是/etc/apache2/site -available/000-default.conf;如果使用默认配置,请将其粘贴到现有配置中< VirtualHost >指令)。

<VirtualHost *:80>
    <Directory />
        Require all granted
    </Directory>
 
    RewriteEngine on
    RewriteCond %{REQUEST_FILENAME} \.php$
    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -f
    RewriteRule . proxy:unix:/run/php/mysite.sock|fcgi://localhost%{DOCUMENT_ROOT}%{REQUEST_FILENAME} [P]
</VirtualHost>

Finally, restart Apache and the FPM process:

最后重启Apache和FPM进程:

sudo systemctl restart apache2.service php7.2-fpm.service

Further reading

Sending email

Use PHPMailer.

Tested with PHPMailer 6.0.6.

PHP provides a mail() function that looks enticingly simple and easy. Unfortunately, like a lot of things in PHP, its simplicity is deceptive and using it at face value can lead to serious security problems.

PHP提供了一个看起来非常简单的mail()函数。不幸的是,就像PHP中的许多东西一样,它的简单性具有欺骗性,表面上使用它会导致严重的安全问题。

Email is a set of protocols with an even more tortured history than PHP. Suffice it to say that there are so many gotchas in sending email that just being in the same room as PHP’s mail() function should give you the shivers.

电子邮件是一组协议,其历史甚至比PHP还要痛苦。我只想说,在发送电子邮件时有太多的陷阱,仅仅与PHP的mail()函数在同一个房间就会让您不寒而栗。

PHPMailer is a popular and well-aged open-source library that provides an easy interface for sending mail securely. It takes care of the gotchas for you so you can concentrate on more important things.

PHPMailer是一个流行的、历史悠久的开放源码库,它为安全地发送邮件提供了一个简单的接口。它会帮你解决问题,这样你就能把精力集中在更重要的事情上。

Example

<?php
// Include the PHPMailer library
require_once('phpmailer-5.2.7/PHPMailerAutoload.php');
 
// Passing 'true' enables exceptions.  This is optional and defaults to false.
$mailer = new PHPMailer(true);
 
// Send a mail from Bilbo Baggins to Gandalf the Grey
 
// Set up to, from, and the message body.  The body doesn't have to be HTML; check the PHPMailer documentation for details.
$mailer->Sender = 'bbaggins@example.com';
$mailer->AddReplyTo('bbaggins@example.com', 'Bilbo Baggins');
$mailer->SetFrom('bbaggins@example.com', 'Bilbo Baggins');
$mailer->AddAddress('gandalf@example.com');
$mailer->Subject = 'The finest weed in the South Farthing';
$mailer->MsgHTML('<p>You really must try it, Gandalf!</p><p>-Bilbo</p>');
 
// Set up our connection information.
$mailer->IsSMTP();
$mailer->SMTPAuth = true;
$mailer->SMTPSecure = 'ssl';
$mailer->Port = 465;
$mailer->Host = 'my smtp host';
$mailer->Username = 'my smtp username';
$mailer->Password = 'my smtp password';
 
// All done!
$mailer->Send();
?>

Further reading 扩展阅读

Validating email addresses 验证电子邮件地址

Use the filter_var() function.

使用filter_var()函数

A common task your web app might need to do is to check if a user has entered a valid email address. You’ll no doubt find online a dizzying range of complex regular expressions that all claim to solve this problem, but the easiest way is to use PHP’s built-in filter_var() function, which can validate email addresses.

web应用程序可能需要做的一项常见任务是检查用户是否输入了有效的电子邮件地址。毫无疑问,您将在网上发现一系列令人眼花缭乱的复杂正则表达式,它们都声称可以解决这个问题,但是最简单的方法是使用PHP内置的filter_var()函数,该函数可以验证电子邮件地址。

Example

<?php
filter_var('sgamgee@example.com', FILTER_VALIDATE_EMAIL); // Returns "sgamgee@example.com". This is a valid email address.
filter_var('sauron@mordor', FILTER_VALIDATE_EMAIL); // Returns boolean false! This is *not* a valid email address.
?>

Further reading

Sanitizing HTML input and output 净化HTML输入和输出

Use the htmlentities() function for simple sanitization and the HTML Purifier library for complex sanitization.

使用htmlentities()函数进行简单的清理,使用HTML净化器库进行复杂的清理。

Tested with HTML Purifier 4.10.0.

使用 HTML净化器4.10.0 测试。

When displaying user input in any web application, it’s essential to “sanitize” it first to remove any potentially dangerous HTML. A malicious user can craft HTML that, if outputted directly by your web app, can be dangerous to the person viewing it.

在任何web应用程序中显示用户输入时,必须首先对其进行“清理”,以删除任何潜在的危险HTML。恶意用户可以创建HTML,如果您的web应用程序直接输出HTML,可能会对查看它的人造成危险。

While it may be tempting to use regular expressions to sanitize HTML, do not do this. HTML is a complex language and it’s virtually guaranteed that any attempt you make at using regular expressions to sanitize HTML will fail.

虽然使用正则表达式来清理HTML可能很诱人,但是不要这样做。HTML是一种复杂的语言,几乎可以肯定,使用正则表达式对HTML进行清理的任何尝试都将失败。

You might also find advice suggesting you use the strip_tags() function. While strip_tags() is technically safe to use, it’s a “dumb” function in the sense that if the input is invalid HTML (say, is missing an ending tag), then strip_tags() might remove much more content than you expected. As such it’s not a great choice either, because non-technical users often use the < and > characters in communications.

您可能还会发现建议使用strip_tags()函数的建议。虽然strip_tags()在技术上是安全的,但它是一个“愚蠢”的函数,因为如果输入是无效的HTML(比如缺少结束标记),那么strip_tags()可能会删除比您预期多得多的内容。因此,这也不是一个很好的选择,因为非技术用户经常在通信中使用<和>字符。

If you read the section on validating email addresses, you might also be considering using the filter_var() function. However the filter_var() function has problems with line breaks, and requires non-intuitive configuration to closely mirror the htmlentities() function. As such it’s not a good choice either.

如果您阅读了关于验证电子邮件地址的部分,您可能还在考虑使用filter_var()函数。但是filter_var()函数存在换行问题,并且需要非直观的配置来紧密地反映htmlentities()函数。因此,这也不是一个好的选择。

Sanitization for simple requirements

简单要求的净化

If your web app only needs to completely escape (and thus render harmless, but not remove entirely) HTML, use PHP’s built-in htmlentities() function. This function is much faster than HTML Purifier, because it doesn’t perform any validation on the HTML—it just escapes everything.

如果您的web应用程序只需要完全转义HTML(从而呈现无害的HTML,但不需要完全删除),那么使用PHP的内置htmlentities()函数。这个函数比HTML净化器快得多,因为它不执行HTML上的任何验证—它只是转义所有内容。

htmlentities() differs from its cousin htmlspecialchars() in that it encodes all applicable HTML entities, not just a small subset.

htmlentities()与其近亲htmlspecialchars()的不同之处在于,它编码所有适用的HTML实体,而不仅仅是一个小子集。

Example

<?php
// Oh no!  The user has submitted malicious HTML, and we have to display it in our web app!
$evilHtml = '<div onclick="xss();">Mua-ha-ha!  Twiddling my evil mustache...</div>';
 
// 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!
?>

Sanitization for complex requirements

复杂要求的净化

For many web apps, simply escaping HTML isn’t enough. You probably want to entirely remove any HTML, or allow a small subset of HTML through. To do this, use the HTML Purifier library.

对于许多web应用程序来说,仅仅转义HTML是不够的。您可能希望完全删除任何HTML,或者允许HTML的一小部分通过。为此,使用HTML净化器库。

HTML Purifier is a well-tested but slow library. That’s why you should use htmlentities() if your requirements aren’t that complex, because it will be much, much faster.

HTML净化器是一个经过良好测试但速度很慢的库。这就是为什么如果您的需求没有那么复杂,那么应该使用htmlentities(),因为它会快得多。

HTML Purifier has the advantage over strip_tags() because it validates the HTML before sanitizing it. That means if the user has inputted invalid HTML, HTML Purifier has a better chance of preserving the intended meaning of the HTML than strip_tags() does. It’s also highly customizable, allowing you to whitelist a subset of HTML to keep in the output.

HTML净化器比strip_tags()更有优势,因为它在对HTML进行清理之前验证它。这意味着如果用户输入了无效的HTML, HTML净化器比strip_tags()更有可能保留HTML的预期含义。它也是高度可定制的,允许您在输出中保留HTML的一个子集。

The downside is that it’s quite slow, it requires some setup that might not be feasible in a shared hosting environment, and the documentation is often complex and unclear. The following example is a basic configuration; check the documentation to read about the more advanced features HTML Purifier offers.

缺点是它非常慢,在一些共享主机环境中可能还需要做一些安装配,而且文档通常很复杂且不清楚。下面的例子是一个基本配置;查看文档以了解HTML净化器提供的更高级特性。

Example

<?php
// Include the HTML Purifier library
require_once('htmlpurifier-4.6.0/HTMLPurifier.auto.php');
 
// Oh no!  The user has submitted malicious HTML, and we have to display it in our web app!
$evilHtml = '<div onclick="xss();">Mua-ha-ha!  Twiddling my evil mustache...</div>';
 
// 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!
?>

Gotchas 陷阱

Using htmlentities() with the wrong character encoding can result in surprising output. Always make sure that you specify a character encoding when calling the function, and that it matches the encoding of the string being sanitized. See the UTF-8 section for more details.

使用带有错误字符编码的htmlentities()可能会导致惊人的输出。始终确保在调用函数时指定了一个字符编码,并且该字符编码与要清除的字符串的编码匹配。有关更多细节,请参见UTF-8部分。

Always include the ENT_QUOTES and character encoding parameters when using htmlentities(). By default, htmlentities() doesn’t encode single quotes. What a dumb default!

在使用htmlentities()时,始终包含ENT_QUOTES和字符编码参数。默认情况下,htmlentities()不编码单引号。多么愚蠢的默认!

HTML Purifier is extremely slow for complex HTML. Consider setting up a caching solution like APCu to store the sanitized result for later use.

对于复杂的HTML, HTML净化器非常慢。考虑设置一个类似APCu的缓存解决方案来存储经过清理的结果,以供以后使用。

Further reading 扩展阅读

PHP and UTF-8

There’s no one-liner. Be careful, detailed, and consistent.

UTF-8 in PHP sucks. Sorry. PHP中的UTF-8糟透了。对不起。

Right now PHP does not support Unicode at a low level. There are ways to ensure that UTF-8 strings are processed OK, but it’s not easy, and it requires digging in to almost all levels of the web app, from HTML to SQL to PHP. We’ll aim for a brief, practical summary.

目前,PHP在较低的级别上不支持Unicode。有一些方法可以确保处理好UTF-8字符串,但这并不容易,而且需要深入到web应用程序的几乎所有级别,从HTML到SQL再到PHP。我们的目标是一个简短的,实用的总结。

UTF-8 at the PHP level

在PHP级别使用UTF-8

The basic string operations, like concatenating two strings and assigning strings to variables, don’t need anything special for UTF-8. However most string functions, like strpos() and strlen(), do need special consideration. These functions often have an mb_* counterpart: for example, mb_strpos() and mb_strlen(). Together, these counterpart functions are called the Multibyte String Functions. The multibyte string functions are specifically designed to operate on Unicode strings.

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

These functions aren’t installed by default in Ubuntu 18.04. You can install them with:

这些函数在Ubuntu 18.04中默认不安装。你可以用以下软件安装:

sudo apt install php-mbstring

You must use the mb_* functions whenever you operate on a Unicode string. For example, if you use substr() on a UTF-8 string, there’s a good chance the result will include some garbled half-characters. The correct function to use would be the multibyte counterpart, mb_substr().

无论何时对Unicode字符串进行操作,都必须使用mb_*函数。例如,如果在UTF-8字符串上使用substr(),很有可能结果中包含一些混淆的半字符。要使用的正确函数是多字节对应的mb_substr()。

The hard part is remembering to use the mb_* functions at all times. If you forget even just once, your Unicode string has a chance of being garbled during further processing.

最难的部分是始终记住使用mb_*函数。如果您只忘记一次,您的Unicode字符串有可能在进一步处理期间被打乱。

Not all string functions have an mb_* counterpart. If there isn’t one for what you want to do, then you might be out of luck.

并不是所有字符串函数都有一个mb_*对应项。如果没有一个适合你想做的事情,那么你可能就不走运了。

Additionally, you should use the mb_internal_encoding() function at the top of every PHP script you write (or at the top of your global include script), and the mb_http_output() function right after it if your script is outputting to a browser. Explicitly defining the encoding of your strings in every script will save you a lot of headaches down the road.

此外,您应该在编写的每个PHP脚本的顶部(或全局包含脚本的顶部)使用mb_internal_encoding()函数,如果脚本要输出到浏览器,则在它之后使用mb_http_output()函数。在每个脚本中显式地定义字符串编码将为您省去很多麻烦。

Finally, many PHP functions that operate on strings have an optional parameter letting you specify the character encoding. You should always explicitly indicate UTF-8 when given the option. For example, htmlentities() has an option for character encoding, and you should always specify UTF-8 if dealing with such strings.

最后,许多操作字符串的PHP函数都有一个可选参数,允许您指定字符编码。当提供该选项时,应该始终显式地指出UTF-8。例如,htmlentities()有一个字符编码选项,如果处理这样的字符串,应该始终指定UTF-8。

UTF-8 at the OS level 操作系统级别的UTF-8

Often you’ll find yourself writing files with contents or filenames encoded in some flavor of Unicode. PHP is able to run on a variety of operating systems, including Linux and Windows; but sadly how it handles Unicode filenames differs on each platform due to OS-level quirks.

通常,您会发现自己编写的文件的内容或文件名以某种Unicode格式编码。PHP能够运行在各种操作系统上,包括Linux和Windows;但遗憾的是,由于os级别的怪癖,它处理Unicode文件名的方式在每个平台上都有所不同。

Linux and OSX appear to handle UTF-8 filenames fairly well. Windows, however, doesn’t. If you try to use PHP to write to a file with non-ASCII characters in the filename in Windows, you may discover that the filename is displayed with strange or corrupted characters.

Linux和OSX似乎可以很好地处理UTF-8文件名。然而,Windows没有。如果您尝试使用PHP在Windows中写入文件名中含有非ascii字符的文件,您可能会发现文件名显示的字符很奇怪或已损坏。

There doesn’t seem to be an easy, portable workaround here. In Linux and OSX you can encode your filenames with UTF-8, but in Windows you have to remember to encode using ISO-8859-1.

这里似乎没有一种简单、便携的工作方式。在Linux和OSX中,您可以使用UTF-8对文件名进行编码,但是在Windows中,您必须记住使用ISO-8859-1进行编码。

If you don’t want to bother with having your script check if it’s running on Windows or not, you could always URL encode all of your filenames before writing them. This effectively works around Unicode quirks by representing Unicode characters by a subset of ASCII.

如果您不想麻烦检查您的脚本是否在Windows上运行,您可以在编写它们之前对所有的文件名进行URL编码。通过用ASCII的子集表示Unicode字符,这有效地解决了Unicode的一些怪癖。

UTF-8 at the MySQL level

If your PHP script accesses MySQL, there’s a chance your strings could be stored as non-UTF-8 strings in the database even if you follow all of the precautions above.

如果您的PHP脚本访问MySQL,那么即使您遵循了上面的所有预防措施,您的字符串也有可能以非utf -8字符串的形式存储在数据库中。

To make sure your strings go from PHP to MySQL as UTF-8, make sure your database and tables are all set to the utf8mb4 character set and collation, and that you use the utf8mb4 character set in the PDO connection string. For an example, see the section on connecting to and querying a MySQL database. This is critically important.

要确保字符串从PHP转换到MySQL为UTF-8,请确保数据库和表都设置为utf8mb4字符集和排序规则,并在PDO连接字符串中使用utf8mb4字符集。例如,请参阅关于连接和查询MySQL数据库的部分。 这一点至关重要。

Note that you must use the utf8mb4 character set for complete UTF-8 support, not the utf8 character set! See Further Reading for why.

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

UTF-8 at the browser level

浏览器级别的UTF-8

Use the mb_http_output() function to ensure that your PHP script outputs UTF-8 strings to your browser. In your HTML, include the charset meta tag in your page’s tag.

使用mb_http_output()函数确保PHP脚本将UTF-8字符串输出到浏览器。在HTML中,在页面的标记中包含charset元标记。

Example

<?php
// Tell PHP that we're using UTF-8 strings until the end of the script
mb_internal_encoding('UTF-8');
 
// Tell PHP that we'll be outputting UTF-8 to the browser
mb_http_output('UTF-8');
 
// Our UTF-8 test string
$string = 'Êl síla erin lû e-govaned vîn.';
 
// Transform the string in some way with a multibyte function
// Note how we cut the string at a non-Ascii character for demonstration purposes
$string = mb_substr($string, 0, 15);
 
// Connect to a database to store the transformed string
// See the PDO example in this document for more information
// Note that we define the character set as utf8mb4 in the PDO connection string
$link = new PDO(    'mysql:host=your-hostname;dbname=your-db;charset=utf8mb4',
                    'your-username',
                    'your-password',
                    array(
                        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                        PDO::ATTR_PERSISTENT => false
                    )
                );
 
// Store our transformed string as UTF-8 in our database
// Your DB and tables are in the utf8mb4 character set and collation, right?
$handle = $link->prepare('insert into ElvishSentences (Id, Body) values (?, ?)');
$handle->bindValue(1, 1);
$handle->bindValue(2, $string);
$handle->execute();
 
// Retrieve the string we just stored to prove it was stored correctly
$handle = $link->prepare('select * from ElvishSentences where Id = ?');
$handle->bindValue(1, 1);
$handle->execute();
 
// Store the result into an object that we'll output later in our HTML
$result = $handle->fetchAll(PDO::FETCH_OBJ);
?><!doctype html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>UTF-8 test page</title>
    </head>
    <body>
        <?php
        foreach($result as $row){
            print($row->Body);  // This should correctly output our transformed UTF-8 string to the browser
        }
        ?>
    </body>
</html>

Further reading 扩展阅读

Working with dates and times 处理日期和时间

Use the DateTime class.

使用DateTime类

In the bad old days of PHP we had to work with dates and times using a bewildering combination of date(), gmdate(), date_timezone_set(), strtotime(), and so on. Sadly you’ll still find lots of tutorials online featuring these difficult and old-fashioned functions.

在PHP糟糕的旧时代,我们必须使用日期()、gmdate()、date_timezone_set()、strtotime()等令人困惑的组合来处理日期和时间。遗憾的是,您仍然可以在网上找到许多教程,其中介绍了这些困难而过时的功能。

Fortunately for us, the version of PHP we’re talking about features the much friendlier DateTime class. This class encapsulates all the functionality and more of the old date functions in one easy-to-use class, with the bonus of making time zone conversions much simpler. Always use the DateTime class for creating, comparing, changing, and displaying dates in PHP.

幸运的是,我们正在讨论的PHP版本具有友好得多的DateTime类。这个类将所有的功能和更多的旧日期函数封装在一个易于使用的类中,并且使时区转换更加简单。始终使用DateTime类在PHP中创建、比较、更改和显示日期。

Example

<?php
// Construct a new UTC date.  Always specify UTC unless you really know what you're doing!
$date = new DateTime('2011-05-04 05:00:00', new DateTimeZone('UTC'));
 
// Add ten days to our initial date
$date->add(new DateInterval('P10D'));
 
print($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
print($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){
    print('Yup, you can compare dates using these easy operators!');
}
 
// Find the difference between two dates
$difference = $date->diff($later);
 
print('The 2nd date is ' . $difference->days . ' later than 1st date.');
?>

Gotchas 陷阱

  • If you don’t specify a time zone, DateTime::__construct() will set the resulting date’s time zone to the time zone of the computer you’re running on. This can lead to spectacular headaches later on. Always specify the UTC time zone when creating new dates unless you really know what you’re doing.

    如果没有指定时区,DateTime:: _construct()将把结果日期的时区设置为您正在运行的计算机的时区。这可能会在以后导致严重的头痛。创建新日期时,一定要指定UTC时区,除非您真的知道自己在做什么

  • If you use a Unix timestamp in DateTime::__construct(), the time zone will always be set to UTC regardless of what you specify in the second argument.

    如果在DateTime:: _construct()中使用Unix时间戳,则无论在第二个参数中指定什么,时区都将始终设置为UTC

  • Passing zeroed dates (e.g. “0000-00-00”, a value commonly produced by MySQL as the default value in a DateTime column) to DateTime::__construct() will result in a nonsensical date, not “0000-00-00”.

    将带0的日期(例如“00:00 -00-00”,这是MySQL通常在DateTime列中生成的默认值)传递给DateTime::……construct()会得到一个无意义的日期,而不是“00:00 -00-00”。

  • Using DateTime::getTimestamp() on 32-bit systems will not represent dates past 2038. 64-bit systems are OK.

    在32位系统上使用DateTime::getTimestamp()不会表示超过2038年的日期。64位系统没有问题。

Further Reading

Checking if a value is null or false 检查值是null还是false

Use the === operator to check for null and boolean false values.

使用===操作符检查null和boolean假值。

PHP’s loose typing system offers many different ways of checking a variable’s value. However it also presents a lot of problems. Using == to check if a value is null or false can return false positives if the value is actually an empty string or 0. isset() checks whether a variable has a value that is not null, but doesn’t check against boolean false.

PHP的松散类型系统提供了许多不同的方法来检查变量的值。然而,它也提出了许多问题。如果值实际上是空字符串或0,则使用==检查值是否为null或false则会返回错误的信息。isset()检查变量的值是否为非null,但不检查布尔值是否为false。

The is_null() function accurately checks if a value is null, and the is_bool() function checks if it’s a boolean value (like false), but there’s an even better option: the === operator. === checks if the values are identical, which is not the same as equivalent in PHP’s loosely-typed world. It’s also slightly faster than is_null() and is_bool(), and looks nicer than using a function for comparison.

函数is_null()精确地检查值是否为空,is_bool()函数检查值是否为布尔值(类似于false),但是还有一个更好的选项:===操作符。===检查值是否相同,这在PHP的松散类型世界中并不等同于等效值。它也比is_null()和is_bool()稍快一些,并且看起来比使用函数进行比较要好。

Example

<?php
$x = 0;
$y = null;
 
// Is $x null?
if($x == null){
    print('Oops! $x is 0, not null!');
}
 
// Is $y null?
if(is_null($y)){
    print('Great, but could be faster.');
}
 
if($y === null){
    print('Perfect!');
}
 
// Does the string abc contain the character a?
if(strpos('abc', 'a')){
    // GOTCHA!  strpos returns 0, indicating it wishes to return the position of the first character.
    // But PHP interpretes 0 as false, so we never reach this print statement!
    print('Found it!');
}
 
//Solution: use !== (the opposite of ===) to see if strpos() returns 0, or boolean false.
if(strpos('abc', 'a') !== false){
    print('Found it for real this time!');
}
?>

Gotchas 陷阱

  • When testing the return value of a function that can return either 0 or boolean false, like strpos(), always use === and !==, or you’ll run in to problems.

    在测试可以返回0或boolean false的函数的返回值时,如strpos(),始终使用===和!==,否则会遇到问题。

Further reading 扩展阅读

Removing accent marks (diacritics)

Most web guides will suggest using PHP’s iconv() function to remove diacritics. However iconv() often has trouble with UTF-8 input and will sometimes produce surprising errors.

大多数web指南都建议使用PHP的iconv()函数删除变音符号。但是iconv()经常在UTF-8输入方面有问题,有时会产生令人惊讶的错误。

A better way is to use PHP’s intl library. It can be installed with:

更好的方法是使用PHP的intl库。可安装:

sudo apt install php-intl

Once you have it installed, use the Transliterator class to remove diacritics from text:

安装后,使用Transliterator类从文本中删除变音符号:

<?php
$transliterator = Transliterator::createFromRules(':: Any-Latin; :: Latin-ASCII; :: NFD; :: [:Nonspacing Mark:] Remove; :: NFC;', Transliterator::FORWARD);
 
print($transliterator->transliterate('Êl síla erin lû e-govaned vîn.'));
?>

Suggestions and corrections 建议和修正

Thanks for reading! If you haven’t figured it out already, PHP is complex and filled with pitfalls. Since I’m only human, there might be mistakes in this document.

感谢你的阅读!如果您还没有弄清楚,那么PHP是复杂的,并且充满了陷阱。由于我只是个凡人,这份文件可能会有错误。

If you’d like to contribute to this document with suggestions or corrections, please contact me using the information in the last revised & maintainers section.

如果您想为这个文档提供建议或更正,请使用上一个修订和维护部分中的信息与我联系。