php之道

Posted 云旗

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了php之道相关的知识,希望对你有一定的参考价值。

 

PHP

The Right Way.

欢迎

目前网络上充斥着大量的过时资讯,让 php 新手误入歧途,并且传播着错误的实践以及不安全的代码。PHP 之道 收集了现有的 PHP 最佳实践、编码规范和权威学习指南,方便 PHP 开发者阅读和查找

使用 PHP 沒有规范化的方式。本网站主要是向 PHP 新手介绍一些他们没有发现或者是太晚发现的主题, 或是经验丰富的专业人士已经实践已久的做法提供一些新想法。本网站也不会告诉您应该使用什么样的工具,而是提供多种选择的建议,并尽可能地说明方法及用法上的差异。

当有更多有用的资讯以及范例时,此文件会随着相关技术的发展而持续更新。

翻译

PHP 之道 已经翻译成多种语言:

如何贡献

帮助我们让本网站作为 PHP 新手的最佳资源!在 GitHub 上贡献

推广

您可以在网站上放置 PHP之道 的横幅来支持我们,让 PHP 的新人知道哪里可以获取到好的资料!

广告横幅

Back to Top

入门指南

使用当前稳定版本 (5.6)

如果你刚开始学习 PHP,请使用最新的稳定版本 PHP 5.6。PHP 近年来有了巨大的改进,增加了许多强大的 新特性。虽然 5.2 和 5.6 之间增加的版本号似乎很小, 但它代表了 重大的 改进。如果你想查找一个函数及其用法,可以去官方手册 php.net 中查找。

内置的 web 服务器

PHP 5.4 之后, 你可以不用安装和配置功能齐全的 Web 服务器,就可以开始学习 PHP。 要启动内置的 Web 服务器,需要从你的命令行终端进入项目的 Web 根目录,执行下面的命令:

> php -S localhost:8000

 

Mac 安装

OS X 系统会预装 PHP, 只是一般情况下版本会比最新稳定版低一些。目前 Lion 是 5.3.10, Mavericks 是 5.4.17, Yosemite 则是 5.5.9, 但在 PHP 5.6 出来之后, 这些往往是不够的。

这里有许多方式在 OS X 上安装 PHP 。

通过 Homebrew 安装 PHP

Homebrew 是一个强大的 OS X 专用包管理器, 它可以帮助你轻松的安装 PHP 和各种扩展。 Homebrew PHP 是一个包含与 PHP 相关的 Formulae,能让你通过 homebrew 安装 PHP 的仓库。

也就是说, 你可以通过 brew install 命令安装 php53php54php55 或者 php56 ,并且通过修改 PATH 变量来切换各个版本。或者你也可以使用 brew-php-switcher 来自动切换。

通过 Macports 安装 PHP

MacPorts 是一个开源的,社区发起的项目,它的目的在于设计一个易于使用的系统,方便编译,安装以及升级 OS X 系统上的 command-line, X11 或者基于 Aqua 的开源软件。

MacPorts 支持预编译的二进制文件,因此你不必每次都重新从源码压缩包编译,如果你的系统没有安装这些包,它会节省你很多时间。

此时,你可以通过 port install 命名来安装 php53php54php55 或者 php56,比如:

sudo port install php54
sudo port install php55

 

你也可以执行 select 命令来切换当前的 php 版本:

sudo port select --set php php55

 

通过 phpbrew 安装 PHP

phpbrew 是一个安装与管理多个 PHP 版本的工具。它在应用程序或者项目需要不同版本的 PHP 时非常有用,让你不再需要使用虚拟机来处理这些情况。

通过 Liip’s binary installer 安装 PHP

php-osx.liip.ch 是另一种流行的选择,它提供了从5.3到5.6版本的单行安装功能。 它并不会覆盖Apple集成的PHP文件,而是将其安装在了一个独立的目录中(/usr/local/php5)。

源码编译

另一个让你控制安装 PHP 版本的选择就是 自行编译。 如果使用这种方法, 你必须先确认是否已经通过 「Apple’s Mac Developer Center」 下载、安装 Xcode 或者 “Command Line Tools for XCode”

集成包 (All-in-One Installers)

上面列出的解决方案主要是针对 PHP 本身, 并不包含:比如 Apache,nginx 或者 SQL 服务器。 集成包比如 MAMP 和 XAMPP 会安装这些软件并且将他们绑在一起,不过易于安装的背后也牺牲了一定的弹性。

Windows 安装

你可以从 windows.php.net/download 下载二进制包。 解压后, 最好为你的 PHP 所在的根目录(php.exe 所在的文件夹)设置 PATH,这样就可以从命令行中直接执行 PHP。

Windows 下有多种安装 PHP 的方式,你可以 下载二进制安装包 并使用 .msi 安装程序。从 PHP 5.3.0 之后,这个安装程序将不再提供下载支持。

如果只是学习或者本地开发,可以直接使用 PHP 5.4+ 内置的 Web 服务器, 还能省去配置服务器的麻烦。如果你想要包含有网页服务器以及 mysql 的集成包,那么像是Web Platform Installer,XAMPPEasyPHP 和 WAMP 这类工具将会帮助你快速建立 Windows 开发环境。不过这些工具将会与线上环境有些许差别,如果你是在 Windows 下开发,而生产环境则部署至 Linux ,请小心。

如果你需要将生产环境部署在 Windows 上,那 IIS7 将会提供最稳定和最佳的性能。你可以使用 phpmanager (IIS7 的图形化插件) 让你简单的设置并管理 PHP。IIS7 也有内置的 FastCGI ,你只需要将 PHP 配置为它的处理器即可。更多详情请见dedicated area on iis.net

Back to Top

代码风格指南

PHP 社区百花齐放,拥有大量的函数库、框架和组件。PHP 开发者通常会在自己的项目中使用若干个外部库,因此 PHP 代码遵循(尽可能接近)同一个代码风格就非常重要,这让开发者可以轻松地将多个代码库整合到自己的项目中。

PHP标准组 提出并发布了一系列的风格建议。其中有部分是关于代码风格的,即 PSR-0PSR-1PSR-2 和 PSR-4。这些推荐只是一些被其他项目所遵循的规则,如 Drupal, Zend, Symfony, CakePHP, phpBB, AWS SDK, FuelPHP, Lithium 等。你可以把这些规则用在自己的项目中,或者继续使用自己的风格。

通常情况下,你应该遵循一个已知的标准来编写 PHP 代码。可能是 PSR 的组合或者是 PEAR 或 Zend 编码准则中的一个。这代表其他开发者能够方便的阅读和使用你的代码,并且使用这些组件的应用程序可以和其他第三方的组件保持一致。

你可以使用 PHP_CodeSniffer 来检查代码是否符合这些准则,文本编辑器 Sublime Text 的插件也可以提供实时检查。

你可以通过以下两个工具来自动修正你的程序语法,让它符合标准。 一个是 PHP Coding Standards Fixer,它具有良好的测试。 另外一个工具是 php.tools, 它是 sublime text 的一个非常流行的插件sublime-phpfmt,虽然比较新,但是在性能上有了很大的提高,这意味着实时的修复语法会更加的流畅。

你也可以手动运行 phpcs 命令:

phpcs -sw --standard=PSR2 file.php

 

它会显示出相应的错误以及如何修正的方法。同样地,这条命令也可以用在 git hook 中,如果你的分支代码不符合选择的代码标准则无法提交。

所有的变量名称以及代码结构建议用英文编写。注释可以使用任何语言,只要让现在以及未来的小伙伴能够容易阅读理解即可。

Back to Top

语言亮点

编程范式

PHP 是一个灵活的动态语言,支持多种编程技巧。这几年一直不断的发展,重要的里程碑包含 PHP 5.0 (2004) 增加了完善的面向对象模型,PHP 5.3 (2009) 增加了匿名函数与命名空间以及 PHP 5.4 (2012) 增加的 traits。

面向对象编程

PHP 拥有完整的面向对象编程的特性,包括类,抽象类,接口,继承,构造函数,克隆和异常等。

函数式编程 Functional Programming

PHP 支持函数是”第一等公民”,即函数可以被赋值给一个变量,包括用户自定义的或者是内置函数,然后动态调用它。函数可以作为参数传递给其他函数(称为高阶函数),也可以作为函数返回值返回。

PHP 支持递归,也就是函数自己调用自己,但多数 PHP 代码使用迭代。

自从 PHP 5.3 (2009) 之后开始引入对闭包以及匿名函数的支持。

PHP 5.4 增加了将闭包绑定到对象作用域中的特性,并改善其可调用性,如此即可在大部分情况下使用匿名函数取代一般的函数。

元编程

PHP 通过反射 API 和魔术方法,可以实现多种方式的元编程。开发者通过魔术方法,如 __get()__set()__clone()__toString()__invoke(),等等,可以改变类的行为。Ruby 开发者常说 PHP 没有 method_missing 方法,实际上通过 __call() 和 __callStatic() 就可以完成相同的功能。

命名空间

如前所述,PHP 社区已经有许多开发者开发了大量的代码。这意味着一个类库的 PHP 代码可能使用了另外一个类库中相同的类名。如果他们使用同一个命名空间,那将会产生冲突导致异常。

命名空间 解决了这个问题。如 PHP 手册里所描述,命名空间好比操作系统中的目录,两个同名的文件可以共存在不同的目录下。同理两个同名的 PHP 类可以在不同的 PHP 命名空间下共存,就这么简单。

因此把你的代码放在你的命名空间下就非常重要,避免其他开发者担心与第三方类库冲突。

PSR-4 提供了一种命名空间的推荐使用方式,它提供一个标准的文件、类和命名空间的使用惯例,进而让代码做到随插即用。

2014 年 10 月,PHP-FIG 废弃了上一个自动加载标准: PSR-0,而采用新的自动加载标准 PSR-4。但 PSR-4 要求 PHP 5.3 以上的版本,而许多项目都还是使用 PHP 5.2,所以目前两者都能使用。如果你在新应用或扩展包中使用自动加载标准,应优先考虑使用 PSR-4。

PHP 标准库

PHP 标准库 (SPL) 随着 PHP 一起发布,提供了一组类和接口。包含了常用的数据结构类 (堆栈,队列,堆等等),以及遍历这些数据结构的迭代器,或者你可以自己实现 SPL 接口。

命令行接口

PHP 是为开发 Web 应用而创建,不过它的命令行脚本接口(CLI)也非常有用。PHP 命令行编程可以帮你完成自动化的任务,如测试,部署和应用管理。

CLI PHP 编程非常强大,可以直接调用你自己的程序代码而无需创建 Web 图形界面,需要注意的是不要把 CLI PHP 脚本放在公开的 web 目录下!

在命令行下运行 PHP :

> php -i

 

选项 -i 将会打印 PHP 配置,类似于 phpinfo() 函数。

选项 -a 提供交互式 shell,和 Ruby 的 IRB 或 python 的交互式 shell 相似,此外还有很多其他有用的命令行选项

接下来写一个简单的 “Hello, $name” CLI 程序,先创建名为 hello.php 的脚本:

<?php
if($argc != 2) {
    echo "Usage: php hello.php [name].\n";
    exit(1);
}
$name = $argv[1];
echo "Hello, $name\n";

 

PHP 会在脚本运行时根据参数设置两个特殊的变量,$argc 是一个整数,表示参数个数$argv 是一个数组变量,包含每个参数的, 它的第一个元素一直是 PHP 脚本的名称,如本例中为hello.php

命令运行失败时,可以通过 exit() 表达式返回一个非 0 整数来通知 shell,常用的 exit 返回码可以查看列表.

运行上面的脚本,在命令行输入:

> php hello.php
Usage: php hello.php [name]
> php hello.php world
Hello, world

 

Xdebug

合适的调试器是软件开发中最有用的工具之一,它使你可以跟踪程序执行结果并监视程序堆栈中的信息。 Xdebug 是一个 php 的调试器,它可以被用来在很多 IDE(集成开发环境) 中做断点调试以及堆栈检查。它还可以像 PHPUnit 和 KCacheGrind 一样,做代码覆盖检查或者程序性能跟踪。

如果你仍在使用 var_dump()/print_r() 调错,经常会发现自己处于困境,并且仍然找不到解决办法。这时,你该使用调试器了。

安装 Xdebug 可能很费事,但其中一个最重要的「远程调试」特性 —— 如果你在本地开发,并在虚拟机或者其他服务器上测试,远程调试可能是你想要的一种方式。

通常,你需要修改你的 Apache VHost 或者 .htaccess 文件的这些值:

php_value xdebug.remote_host=192.168.?.?
php_value xdebug.remote_port=9000

 

「remote host」 和 「remote port」 这两项对应和你本地开发机监听的地址和端口。然后将你的 IDE 设置成「listen for connections」模式,并访问网址:

http://your-website.example.com/index.php?XDEBUG_SESSION_START=1

 

你的 IDE 将会拦截当前执行的脚本状态,运行你设置的断点并查看内存中的值。

图形化的调试器可以让你非常容易的逐步的查看代码、变量,以及运行时的 evel 代码。许多 IDE 已经内置或提供了插件支持 XDebug 图形化调试器。比如 MacGDBp 是 Mac 上的一个免费,开源的单机调试器。

Back to Top

依赖管理

PHP 有很多可供使用的库、框架和组件。通常你的项目都会使用到其中的若干项 - 这些就是项目的依赖。直到最近,PHP 也没有一个很好的方式来管理这些项目依赖。即使你通过手动的方式去管理,你依然会为自动加载器而担心。但现在这已经不再是问题了。

目前 PHP 有两个使用较多的包管理系统 - Composer 和 PEAR。Composer 是 PHP 所使用的主要的包管理器,然而在很长的一段时间里,PEAR 曾经扮演着这个角色。你应该了解 PEAR 是什么,因为即使你从来没有使用过它,你依然有可能会碰到对它的引用。

Composer 与 Packagist

Composer 是一个杰出 的依赖管理器。在 composer.json 文件中列出你项目所需的依赖包,加上一点简单的命令,Composer 将会自动帮你下载并设置你的项目依赖。

现在已经有许多 PHP 第三方包已兼容 Composer,随时可以在你的项目中使用。这些「packages(包)」都已列在 Packagist,这是一个官方的 Composer 兼容包仓库。

如何安装 Composer

你可以安装 Composer 到局部 (在你当前工作目录;这里不是很推荐)或是全局(e.g. /usr/local/bin)。我们假设你想安装 Composer 到局部。在你的项目根目录输入:

curl -s https://getcomposer.org/installer | php

 

这条命令将会下载 composer.phar (一个 PHP 二进制档)。你可以使用 php 执行这个文件用来管理你的项目依赖。 请注意: 假如你是直接下载代码来编译,请先在线阅读代码确保它是安全的。

Windows环境下安装

对于Windows 的用户而言最简单的获取及执行方法就是使用 ComposerSetup 安装程序,它会执行一个全局安装并设置你的 $PATH,所以你在任意目录下在命令行中使用 composer

如何手动安装 Composer

手动安装 Compose r是一个高端的技术;仅管如此还是有许多开发者有各种原因喜欢使用这种交互式的应用程序安装 Composer。在安装前请先确认你的PHP安装项目如下:

  • 正在使用一个满足条件的 PHP 版本
  • .phar 文件可以正确的被执行
  • 相关的目录有足够的权限
  • 相关有问题的扩展没有被载入
  • 相关的 php.ini 设置已完成

由于手动安装没有执行这些检查,你必须自已衡量决定是否值得做这些事,以下是如何手动安装 Composer :

curl -s https://getcomposer.org/composer.phar -o $HOME/local/bin/composer
chmod +x $HOME/local/bin/composer

 

路径 $HOME/local/bin (或是你选择的路径) 应该在你的 $PATH 环境变量中。这将会影响 composer 这个命令是否可用.

当你遇到文档指出执行 Composer 的命令是 php composer.phar install时,你可以使用下面命令替代:

composer install

 

本章节会假设你已经安装了全局的 Composer。

如何设置及安装依赖

Composer 会通过一个 composer.json 文件持续的追踪你的项目依赖。 如果你喜欢,你可以手动管理这个文件,或是使用 Composer 自己管理。composer require 这个指令会增加一个项目依赖,如果你还没有 composer.json 文件, 将会创建一个。这里有个例子为你的项目加入 Twig 依赖。

composer require twig/twig:~1.8

 

另外 composer init 命令将会引导你创建一个完整的 composer.json 文件到你的项目之中。无论你使用哪种方式,一旦你创建了 composer.json 文件,你可以告诉 Composer 去下载及安装你的依赖到 vendors/ 目录中。这命令也适用于你已经下载并已经提供了一个 composer.json 的项目:

composer install

 

接下来,添加这一行到你应用的主要 PHP 文件中,这将会告诉 PHP 为你的项目依赖使用 Composer 的自动加载器。

<?php
require vendor/autoload.php;

 

现在你可以使用你项目中的依赖,且它们会在需要时自动完成加载。

更新你的依赖

Composer 会建立一个 composer.lock 文件,在你第一次执行 php composer.phar install 时,存放下载的每个依赖包精确的版本编号。假如你要分享你的项目给其他开发者,并且composer.lock 文件也在你分享的文件之中的话。 当他们执行 php composer.phar install 这个命令时,他们将会得到与你一样的依赖版本。 当你要更新你的依赖时请执行 php composer.phar update

当你需要灵活的定义你所需要的依赖版本时,这是最有用。 举例来说需要一个版本为 ~1.8 时,意味着 “任何大于 1.8.0 ,但小于 2.0.x-dev 的版本”。你也可以使用通配符 * 在 1.8.* 之中。现在Composer在composer update 时将升级你的所有依赖到你限制的最新版本。

更新通知

要接收关于新版本的更新通知。你可以注册 VersionEye,这个 web 服务可以监控你的 Github 及 BitBucket 帐号中的 composer.json 文件,并且当包有新更新时会发送邮件给你。

检查你的依赖安全问题

Security Advisories Checker 是一个 web 服务和一个命令行工具,二者都会仔细检查你的 composer.lock 文件,并且告诉你任何你需要更新的依赖。

处理 Composer 全局依赖

Composer 也可以处理全局依赖和他们的二进制文件。用法很直接,你所要做的就是在命令前加上global前缀。如果你想安装 PHPUnit 并使它全局可用,你可以运行下面的命令:

composer global require phpunit/phpunit

 

这将会创建一个 ~/.composer 目录存放全局依赖,要让已安装依赖的二进制命令随处可用,你需要添加 ~/.composer/vendor/bin 目录到你的 $PATH 变量。

PEAR 介绍

PEAR 是另一个常用的依赖包管理器, 它跟 Composer 很类似,但是也有一些显著的区别。

PEAR 需要扩展包有专属的结构, 开发者在开发扩展包的时候要提前考虑为 PEAR 定制, 否则后面将无法使用 PEAR.

PEAR 安装扩展包的时候, 是全局安装的, 意味着一旦安装了某个扩展包, 同一台服务器上的所有项目都能用上, 当然, 好处是当多个项目共同使用同一个扩展包的同一个版本, 坏处是如果你需要使用不同版本的话, 就会产生冲突.

如何安装 PEAR

你可以通过下载 .phar 文件来安装 PEAR. 官方文档安装部分 里面有不同系统中安装 PEAR 的详细信息.

如果你是使用 Linux, 你可以尝试找下系统应用管理器, 举个栗子, Debian 和 Ubuntu 有个 php-pear 的 apt 安装包.

如何安装扩展包

如果扩展包是在 PEAR packages list 这个列表里面的, 你可以使用以下命令安装:

pear install foo

 

如果扩展包是托管到别的渠道上, 你需要 发现 (discover) 渠道先, 请见文档 使用渠道.

使用 Composer 来安装 PEAR 扩展包

如果你正在使用 Composer, 并且你想使用一些 PEAR 的代码, 你可以通过 Composer 来安装 PEAR 扩展包.

下面是从 pear2.php.net 安装代码依赖的示例:

{
    "repositories": [
        {
            "type": "pear",
            "url": "http://pear2.php.net"
        }
    ],
    "require": {
        "pear-pear2/PEAR2_Text_Markdown": "*",
        "pear-pear2/PEAR2_HTTP_Request": "*"
    }
}

 

第一部分 "repositories" 是让 Composer 从哪个渠道去获取扩展包, 然后, "repositories" 部分使用下面的命名规范:

pear-channel/Package

前缀 “pear” 是为了避免冲突写死的.

成功安装扩展包以后, 代码会放到项目的 vendor 文件夹中, 并且可以通过加载 Composer 的自动加载器进行加载:

vendor/pear-pear2.php.net/PEAR2_HTTP_Request/pear2/HTTP/Request.php

在代码里面可以这样使用:

<?php
$request = new pear2\HTTP\Request();

 

Back to Top

开发实践

基础知识

PHP 是一门庞大的语言,各个水平层次的开发者都可以利用它进行迅捷高效的开发。然而在对语言逐渐深入的学习过程中,我们往往会因为走捷径和/或不良习惯而忘记(或忽视掉)基础的知识。为了帮助彻底解决这个问题,这一章的目的就是提醒开发人员注意有关 PHP 的基础编程实践。

日期和时间

PHP 中 DateTime 类的作用是在你读、写、比较或者计算日期和时间时提供帮助。除了 DateTime 类之外,PHP 还有很多与日期和时间相关的函数,但 DateTime 类为大多数常规使用提供了优秀的面向对象接口。它还可以处理时区,不过这并不在这篇简短的介绍之内。

在使用 DateTime 之前,通过 createFromFormat() 工厂方法将原始的日期与时间字符串转换为对象或使用 new DateTime 来取得当前的日期和时间。使用 format() 将 DateTime 转换回字符串用于输出。

<?php
$raw = 22. 11. 1968;
$start = DateTime::createFromFormat(d. m. Y, $raw);

echo Start date:  . $start->format(Y-m-d) . "\n";

 

对 DateTime 进行计算时可以使用 DateInterval 类。DateTime 类具有例如 add() 和 sub() 等将 DateInterval 当作参数的方法。编写代码时注意不要认为每一天都是由相同的秒数构成的,不论是夏令时(DST)还是时区转换,使用时间戳计算都会遇到问题,应当选择日期间隔。使用 diff() 方法来计算日期之间的间隔,它会返回新的 DateInterval,非常容易进行展示。

<?php
// create a copy of $start and add one month and 6 days
$end = clone $start;
$end->add(new DateInterval(P1M6D));

$diff = $end->diff($start);
echo Difference:  . $diff->format(%m month, %d days (total: %a days)) . "\n";
// Difference: 1 month, 6 days (total: 37 days)

 

DateTime 对象之间可以直接进行比较:

<?php
if ($start < $end) {
    echo "Start is before end!\n";
}

 

最后一个例子来演示 DatePeriod 类。它用来对循环的事件进行迭代。向它传入开始时间、结束时间和间隔区间,会得到这其中所有的事件。

<?php
// output all thursdays between $start and $end
$periodInterval = DateInterval::createFromDateString(first thursday);
$periodIterator = new DatePeriod($start, $periodInterval, $end, DatePeriod::EXCLUDE_START_DATE);
foreach ($periodIterator as $date) {
    // output each date in the period
    echo $date->format(Y-m-d) .  ;
}

 

设计模式

当你在编写自己的应用程序时,最好在项目的代码和整体架构中使用通用的设计模式,这将帮助你更轻松地对程序进行维护,也能够让其他的开发者更快地理解你的代码。

当你使用框架进行开发时,绝大部分的上层代码以及项目结构都会基于所使用的框架,因此很多关于设计模式的决定已经由框架帮你做好了。当然,你还是可以挑选你最喜欢的模式并在你的代码中进行应用。但如果你并没有使用框架的话,你就需要自己去寻找适合你的应用的最佳模式了。

使用 UTF-8 编码

本章是由 Alex Cabal 最初撰写在 PHP Best Practices 中的,我们使用它作为进行建议的基础

这不是在开玩笑。请小心、仔细并且前后一致地处理它。

目前,PHP 仍未在底层实现对 Unicode 的支持。虽然有很多途径可以确保 UTF-8 字符串能够被正确地处理,但这并不是很简单的事情,通常需要对 Web 应用进行全方面的检查,从 html 到 SQL 再到 PHP。我们将争取进行一个简洁实用的总结。

PHP 层面的 UTF-8

最基本的字符串操作,像是连结两个字符串或将字符串赋值给变量,并不需要对 UTF-8 做特别的处理。然而大多数字符串的函数,像 strpos() 和 strlen(),确实需要特别的处理。这些函数名中通常包含 mb_*:比如,mb_strpos() 和 mb_strlen()。这些 mb_* 字符串是由 Multibyte String Extension 提供支持的,它专门为操作 Unicode 字符串而特别进行了设计。

在操作 Unicode 字符串时,请你务必使用 mb_* 函数。例如,如果你对一个 UTF-8 字符串使用 substr(),那返回的结果中有很大可能会包含一些乱码。正确的方式是使用 mb_substr()

最难的地方在于每次都要记得使用 mb_* 函数。如果你哪怕只有一次忘记了使用,你的 Unicode 字符串就有在接下来的过程中变成乱码的风险。

不是所有的字符串函数都有一个对应的 mb_* 函数。如果你想要的功能没有对应的 mb_* 函数的话,那只能说你运气不佳了。

你应该在你所有的 PHP 脚本(或全局包含的脚本)的开头使用 mb_internal_encoding() 函数,然后紧接着在会对浏览器进行输出的脚本中使用 mb_http_output()。在每一个脚本当中明确声明字符串的编码可以免去很多日后的烦恼。

另外,许多对字符串进行操作的函数都有一个可选的参数用来指定字符串编码。当可以设定这类参数时,你应该始终明确指定使用 UTF-8。例如,htmlentities() 有一个字符编码的选项,你应该始终将其设为 UTF-8。从 PHP 5.4.0 开始, htmlentities() 和 htmlspecialchars() 的编码都已经被默认设为了 UTF-8。

最后,如果你所编写的是分布式的应用程序并且不能确定 mbstring 扩展一定开启的话,可以考虑使用 patchwork/utf8 Composer 包。它会在 mbstring 可用时自动使用,否则自动切换回非 UTF-8 函数。

数据库层面的 UTF-8

如果你使用 PHP 来操作到 MySQL,有些时候即使你做到了上面的每一点,你的字符串仍可能面临在数据库中以非 UTF-8 的格式进行存储的问题。

为了确保你的字符串从 PHP 到 MySQL都使用 UTF-8,请检查确认你的数据库和数据表都设定为 utf8mb4 字符集和整理,并且确保你的 PDO 连接请求也使用了 utf8mb4 字符集。请看下方的示例代码,这是 非常重要 的。

请注意为了完整的 UTF-8 支持,你必须使用 utf8mb4 而不是 utf8!你会在进一步阅读中找到原因。

浏览器层面的 UTF-8

使用 mb_http_output() 函数来确保 PHP 向浏览器输出 UTF-8 格式的字符串。

随后浏览器需要接收 HTTP 应答来指定页面是由 UTF-8 进行编码的。以前这一步是通过在页面 <head> 标签下包含字符集 <meta> 标签实现的,这是一种可行的方式。但更好的做法是在 Content-Type 响应头中进行设置,因为这样做的速度会更快

<?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 the `charset=utf8mb4` in the Data Source Name (DSN)
$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, 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 ElvishSentences 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);

header(Content-Type: text/html; charset=UTF-8);
?><!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>

 

延伸阅读

Back to Top

依赖注入

出自维基百科 Wikipedia:

依赖注入是一种允许我们从硬编码的依赖中解耦出来,从而在运行时或者编译时能够修改的软件设计模式。

这句解释让依赖注入的概念听起来比它实际要复杂很多。依赖注入通过构造注入,函数调用或者属性的设置来提供组件的依赖关系。就是这么简单。

基本概念

我们可以用一个简单的例子来说明依赖注入的概念

下面的代码中有一个 Database 的类,它需要一个适配器来与数据库交互。我们在构造函数里实例化了适配器,从而产生了耦合。这会使测试变得很困难,而且 Database 类和适配器耦合的很紧密。

<?php
namespace Database;

class Database
{
    protected $adapter;

    public function __construct()
    {
        $this->adapter = new MySqlAdapter;
    }
}

class MysqlAdapter {}

 

这段代码可以用依赖注入重构,从而解耦

<?php
namespace Database;

class Database
{
    protected $adapter;

    public function __construct(MySqlAdapter $adapter)
    {
        $this->adapter = $adapter;
    }
}

class MysqlAdapter {}

 

现在我们通过外界给予 Database 类的依赖,而不是让它自己产生依赖的对象。我们甚至能用可以接受依赖对象参数的成员函数来设置,或者如果 $adapter 属性本身是 public的,我们可以直接给它赋值。

复杂的问题

如果你曾经了解过依赖注入,那么你可能见过 “控制反转”(Inversion of Control) 或者 “依赖反转准则”(Dependency Inversion Principle)这种说法。这些是依赖注入能解决的更复杂的问题。

控制反转

顾名思义,一个系统通过组织控制和对象的完全分离来实现”控制反转”。对于依赖注入,这就意味着通过在系统的其他地方控制和实例化依赖对象,从而实现了解耦。

一些 PHP 框架很早以前就已经实现控制反转了,但是问题是,应该反转哪部分以及到什么程度?比如, MVC 框架通常会提供超类或者基本的控制器类以便其他控制器可以通过继承来获得相应的依赖。这就是控制反转的例子,但是这种方法是直接移除了依赖而不是减轻了依赖。

依赖注入允许我们通过按需注入的方式更加优雅地解决这个问题,完全不需要任何耦合。

依赖反转准则

依赖反转准则是面向对象设计准则 S.O.L.I.D 中的 “D” ,倡导 “依赖于抽象而不是具体”。简单来说就是依赖应该是接口/约定或者抽象类,而不是具体的实现。我们能很容易重构前面的例子,使之遵循这个准则