PHP7简介:新特性与移除的特性


/**
 * 原文出处:https://www.toptal.com/php/php-7-performance-features
 * @author dogstar.huang <chanzonghuang@gmail.com> 2016-03-13
 */

在PHP世界里,2015年最振奋人心的消息之一是PHP 7的版本发布,距离上一次主版本PHP 5长达10年之久。 向前迈进了巨大的一步后,PHP 7引入了大量的新特性以及性能升级。

然而,由于采用了一些兼容性中断,它把老的、弃用的功能也移除了,这使得老应用迁移到新版本更为困难。 如果你计划把已有的应用移到PHP 7之上或者构建新应用的话,此篇指南应该作为期待什么会发生的的快速向导。

但是等一等,PHP 6哪里去了?

如果近期你没在工作中一直使用PHP的话,你可能会疑惑PHP 6发生了什么,为什么会直接从PHP 5跳到PHP 7? 好吧,长话短说,PHP 6失败了。由于PHP主要用于网站开发而网站需要Unicode,版本6的主要特性是对于 Unicode的本地支持,所以把Unicode带到PHP这一举动是有意义的。

对此的想法就是把对于Unicode的完整支持带到本身的核心中去。这本来可以把扩展能力带到语言,从使用滑稽的emoji 作为变量和函数名字的能力,到强大的国际化字符功能。例如,当另一个有别于英文的语言使用大写和小写字符时, 或者当一个中文字符的名字需要转换成英语时。

PHP 6雄心勃勃,但很糟糕。这就是我们为什么在这个过程以PHP 7来结束,直接跳过6的原因。

不幸的是,雄心勃勃的计划证明是一个比预期还要大的问题。大部分的代码库需要移植以便在核心和重要扩展中都能支持Unicode, 这证明是繁琐而复杂的。这缓慢了语言中其他特性的开发进度,并在这个过程中使得很多PHP开发人员 感到沮丧。额外障碍的出现,让开发本地Unicode支持的兴趣大大减少,最终导致了这个项目被抛弃。

由于像书和文章这样的资源已经为PHP 6和它的Unicode支持编著好了,为了避免混淆新的版本将会命名为PHP 7。

不管怎样,往事不堪回首,让我们来看下PHP 7带来了什么。

性能之战,PHP 7 vs PHP 5

几乎所有的更新,轻微的性能升级是可以预期的。然而,这一次PHP带来了比以往早期的版本更重要的提升,使得纯粹性能成为了PHP 7 最吸引人的特性。这来自于“PHPNG”项目的一部分,这抓住了Zend Engine本身的内部结构。

通过重构内部数据结构和添加一个中间步骤到在抽象语法树(AST)表单的代码编译,结果是优越的性能和更高效的内存分配。 数字本身看起来来非常让人满怀希望:在真实世界应用完成的基准测试表明PHP 7平均比PHP 5.6快两倍,并且在请求期间减少了 50%的内存消耗,使得PHP 7成为了脸书 HHVM JIT 编译器强劲的对手。可以看下来自Zend为一些通用CMS和框架
描绘的图表信息。

PHP 7看起来和感觉都熟悉,但是它为性能作了调整。精制的Zend引擎和得到的结果性能产生了巨大的变化。

随着围绕PHP构建微服务的机遇,内存消耗的减少也使得更小的机器可以更好处理请求。内部的变化,尤其是AST实现,也打开了 可以把性能推向更远的未来优化的可能性。一个新的,在内部的JIT实现正在为未来的版本而被考虑。

PHP 7语法糖

伴随PHP 7而来的是新的语法特性。当没有在扩展语言本身的功能时,他们提供了一个更好的,或者更容易的方式让你的代码更令人愉悦 地编写以及看起来更畅心。

组导入声明

现在,我们可以把来源于相同命名空间的类组导入声明分组简化成use一行。这应该能在一种意义丰富的方式或简单地在文件中节 省一些字节帮助对齐声明。

use Framework\Module\Foo;  
use Framework\Module\Bar;  
use Framework\Module\Baz;  

使用PHP 7我们可以使用:

use Framework\Module\{Foo, Bar, Baz};  

或者,如果你更倾向多行的风格:

use Framework\Module{  
    Foo,
    Bar,
    Baz
};

Null合并运算符

这解决了在PHP编程中一个通用的问题,就是我们想把一个变量的值赋给另一个变量时,如果后者实际上有设置的话,否则为它提供一个不同的值。 通常用于我们处理用户提供的输入时。

在PHP 7前

if (isset($foo)) {  
    $bar = $foo;
} else {
    $bar = 'default'; // we would give $bar the value 'default' if $foo is NULL
}

PHP 7后

$bar = $foo ?? 'default';

也可以这样串联多个变量:

$bar = $foo ?? $baz ?? 'default';

飞船操作符

飞船操作符<=>允许有三种方式来比较两个值,不仅意味着如果他们相等,还包括哪一个更大,通过返回1,0或-1的不等式。

我们可以根据值的不同做出不同的动作:

switch ($bar <=> $foo) {  
    case 0:
        echo '$bar and $foo are equal';
    case -1:
        echo '$foo is bigger';
    case 1:
        echo '$bar is bigger';
}

对比的值可以是整型int,浮点数float,字符串string甚至数组array。关于不同的值是如何相互对比的更多信息请查看文档:https://wiki.php.net/rfc/combined-comparison-operator

PHP 7中的新特性

当然PHP 7也带来了新的令人兴奋的功能。

标量参数类型 & 返回类型提示

通过添加这四个标量类型:;整型(int),浮点数(float),布尔值(bool)和字符串(string)和尽可能的参数类型, PHP 7扩展了先前在方法里(类,接口和数组)的参数类型声明。

更进一步,我们可以可选地指定方法和函数返回哪些类型。支持的类型有 bool,int,float,string,array,callable, 类名或接口名,self和parent(针对类方法)

class Calculator  
{
    // We declare that the parameters provided are of type integer
    public function addTwoInts(int $x, int $y): int {
        return $x + $y; // We also explicitly say that this method will return an integer
    }
}

类型声明有助于构建更强壮的应用并且避免从函数传递或返回错误的值。其他好外包括静态代码分析和当缺少文档块时提供更好的代码库提示的IDE。

既然PHP是一个弱类型语言,参数和返回类型的值基于上下文投射。如果在一个函数中传递的值“3”并且声明参数类型为int,解释器会把它作为 一个整数来接收而不会抛出任何错误。如果你不想要这点,可以通过添加一个declare指示开启strict mode

declare(strict_types=1);  

这设置在每个文件的基础上,作为一个全局的选项划分代码库为和全局严格开启进行构建和全局严格开关关闭时构建,导致当我们从两者合并代码时出 现了非期望的行为。

引擎异常

通过添加的引擎异常,由脚本终端导致的致命错误可以轻松捕捉并处理。

例如调用一个不存在的方法的错误不会终止脚本,取而代之的是他们会抛出一个可以被try catch块处理的异常,这点提高了你应用的错误处理。 这对于诸如应用、服务和进程是重要的因为致命错误需要他们重新启动。在PHPUNit的测试也会变得更可用因为致命错误之前会丢掉整个测试套件。 异常,而不是错误,会在每一个测试案例基础上处理。

PHP 7看起来和感觉都熟悉,但是它为性能作了调整。精制的Zend引擎和得到的结果性能产生了巨大的变化。

PHP 7根据可能触发的错误类型添加了若干新的异常类。为了维护版本之间的兼容性,添加了一个新的接口Throwable,可以被引擎异常和用户异
常这两者实现。为了避免引擎异常扩展基础异常类这是有需要的,导致更老的代码捕捉当时还没有的异常。

在PHP 7前这样会以一个致命错误终止脚本:

try {  
    thisFunctionDoesNotEvenExist();
} catch (\EngineException $e) {
    // Clean things up and log error
    echo $e->getMessage();
}

匿名类

匿名类是匿名函数的表兄弟,你可以在一个简短的实例中使用。匿名类创建简单并且像一个正常的对象那样使用即可。以下是来自文档的一个示例。

PHP 7前

class MyLogger {  
  public function log($msg) {
    print_r($msg . "\n");
  }
}

$pusher->setLogger( new MyLogger() );

使用匿名类:

$pusher->setLogger(new class {
  public function log($msg) {
    print_r($msg . "\n");
  }
});

匿名类在单元测试中很有用,特别在模拟测试对象和服务时。这有利于我们通过创建一个简单的对象并提供我们想要模拟的接口来避免重量级的模拟库和框架。

CSPRNG函数

添加了两个生成加密安全字符串和整数的新函数。

random_bytes(int $len);  

返回一个长度为$len的随机字符串。

random_int(int $min, int $max);  

返回一个$min$max之间的数字。

Unicode代码点转义语法

在PHP 7之前,不像其他很多语言,PHP没有一种转义字符语中Unicode代码点的方式。这个功能添加了序列\u以产生使用他们UTF-8代码点的字符。 这样相比直接插入字符更好,使得更好处理不可见的字符和有相同图表展示但意义不同的字符。

echo "\u{1F602}"; // outputs 一个表情符号‚  

注意这会使用\u断开已有的代码因为它改变了行为。

生成器升级

在PHP的生成器也得到了一些优雅额外的特性。现在,生成器有一个可用于跟在迭代后允许输出一个最终值的返回声明。这点可用于检测生成器已经执行且没有错误 并且允许调用生成的代码处理大量适当的场景。

再进一步,生成器可以从其他生成器返回和yield表达式。这使得他们可以把复杂的操作划分为更简单和模块化的单元。

function genA() {  
    yield 2;
    yield 3;
    yield 4;
}

function genB() {  
    yield 1;
    yield from genA(); // 'genA' gets called here and iterated over
    yield 5;
    return 'success'; // This is a final result we can check later
}

foreach (genB() as $val) {  
    echo "\n $val"; // This will output values 1 to 5 in order
}

$genB()->getReturn(); // This should return 'success' when there are no errors.

期望

期望是在维护向后兼容性时对assert()的一个增强。他们允许在生产代码中零消耗的断言,并提供当断言失败时抛出自定义异常的能力,这一点在开发中很有帮助。

assert() 成为了PHP 7中的一个语言构造。 断言应该只在开发和测试环境中用于调试目的。为了配置它的行为,提供了两个新的标题给我们。

  • zend.assertions

    • 1:生成并执行代码(开发模式)(默认值)
    • 0:生成代码但在运行时围绕它跳转
    • -1:不生成代码,零消耗(生产模式)
  • assert.exception

    • 1:当断言失败时抛出,通过抛出作为异常而提供的对象或者如果异常未提供时抛出一个新的 AssertionError 对象
    • 0:如上面描述一样使用或者生成一个 Throwable ,但只生成一个基于那个对象的警告而不是抛出它(兼容PHP 5)

准备好把PHP 5移到PHP 7

这次主发布的介绍带来了改变/升级老功能的一个机遇,或者甚至如果被认为过于古老或者已废弃有一段时间的话就移除他们。这样的改变会在老的应用引入兼容性的中断。

另外从这次版本跳跃而引发的一个问题是你所依赖的重要类库和框架可能还没升级到支持最新的版本。PHP团队已尝试着把这些新的改变尽可能向后兼容并且 让迁移到新版本尽可能少点痛苦。更新和更持续更新的应用可以发现转移到新版本更容易,而老的应用则不得不决定是否收益大于成本,并且可能选择不作升级。

大部分中断都是轻微的并且可以轻松迁移,而另外一些可能要更大的努力的更多的时间。基本上来讲,如果在安装PHP 7前在你的应用中警告,很可能会有中断应用直到修复的错误。 你有被警告过了,对吗?

老的SAPI和扩展

最重要地,诸如mysql扩展(但你应该不会在第一个地方使用这个,是吗?)这样老的和弃用的SAPI已被移除。完整的扩展和特性移除可以在这里这里查看这个RFC。

此外,其他的SAPI正在移植到PHP 7。

从PHP 7起很多老的SAPI和扩展已被丢弃。我们在想他们应该不会有人想起。

统一变量语法

这个更新为变量-变量结构的一致性做了一些修改。这允许了更先进带变量的表达式但也在其他一些场景中引入了变化,如下所示。

                        // old meaning            // new meaning
$$foo['bar']['baz']     ${$foo['bar']['baz']}     ($$foo)['bar']['baz']
$foo->$bar['baz']       $foo->{$bar['baz']}       ($foo->$bar)['baz']
$foo->$bar['baz']()     $foo->{$bar['baz']}()     ($foo->$bar)['baz']()
Foo::$bar['baz']()      Foo::{$bar['baz']}()      (Foo::$bar)['baz']()  

这会中断应用访问像这些变量的行为。另一方面,你可以做一些像这样奇妙的东西:

// Nested ()
foo()(); // Calls the return of foo()  
$foo->bar()();

// IIFE syntax like JavaScript
(function() {
    // Function body
})();

// Nested ::
$foo::$bar::$baz

老风格标签的移除

<% ... %><%= ... %><script language="php"> ... </script>这类开/关标签已被移除并不再有效。用有效的替换这些很容易,但是你用这些来干嘛,怪咖?

类、接口和特征的无效名称

由于诸如参数和返回类型这样的附加项,类,接口和特征不再允许有以下名称:
+ bool + int + float + string + null + true + false

对于用到他们的已有应用和类库会引起中断,但修复很容易。同样,尽管不会引起任何错误并且是有效的,以下这些也不应该使用因为他们为将来使用而保留:
+ resource + object + mixed + numeric

克制使用他们能让你免于在未来再作调整。

对于会中断兼容的改变的完全列表,请查看这份文档

你也可以使用php7cc,它会检查你的代码并可以发现如果你转称到PHP 7可能会出来的任何潜在的问题。 但当然,没有比安装PHP 7并且自己亲自看一下更好的方式了。

潜在的PHP 7兼容性问题

PHP 7基础结构兼容性

大量托管的服务已开始着手添加对PHP 7的支持。这对于共享托管供应商是一个好消息,因为所得的性能允许他们在现有硬件提高网站客户数量, 降低运营费用,提高利润率。而对于客户端本身,他们期望在这样的条件下不应该有太大的提高,但出于公平而,无论如何共享托管不是一个面向性能的选择。

另一方面,提供虚拟私有服务商或专用服务商的服务会从这次性能跳跃中收益颇丰。一些支持PHP 7的PaaS服务如Heroku早期已上线,但其他的服务,像AWS Beanstalk 和Oracle’s OpenShift,正在滞后。检查你的PaaS供应商站点看下是否PHP 7已经被支持,或者是否即将支持。

当然,IaaS允许你掌控硬件和安装PHP 7(或者你喜欢也可以进行编译)。对于主流IaaS环境的PHP 7的包已经可用。

PHP 7软件兼容性

除了基础结构兼容性,你还需要留意潜在的软件兼容性问题。流行的内容管理系统(CMS)如WordPress,Joomla和Drupal已经在他们最新的版本吕添加了对PHP 7的支持。 主流的框架如Symfony和Laravel也已完善支持。

然而,是时候提到谨慎这个词了。此支持没有以附加组件,插件,包,或者不管你的CMS和框架如何调用他们的形式扩展到第三方代码。他们也许会在兼容性问题中 饱受折磨并且确保所有东西都已为PHP 7准备好是你的责任。

PHP的未来

PHP 7的发布移除了旧的、过时的代码并且为未来新特性和性能升级铺好了道路。所以,PHP有望很快得到更多的性能优化。尽管在过去发布中有一些兼容性中断,
大部分的问题都是很容易解决的。

类库和框架现在正迁移到他们的代码到PHP 7所以请获取最新的版本。我鼓励你去试一下并亲自看下结果。也许你的应该已经兼容并且等待使用,以及从PHP 7中受益。

参考:Before Debugging PHP That’s Not Working, Consult This List of the 10 Most Common Mistakes That PHP Developers Make

dogstar

一位喜欢翻译的开发人员,艾翻译创始人之一。

广州