PHP 进化史 — 从 v5.6 到 v8.0

PHP 7.3 版本发布后,为了更好地理解这门广泛流行的编程语言的新特性和优化之处,我决定详细地研究下 PHP 开发:正在开发什么以及其开发方向。

在查看了 PHP 在 PHP 7.x 版本开发过程中实现的一系列特性的简要列表之后,我决定自己整合这个列表作为一个很好的补充,我相信也会有人觉得有用的。

我们将从 PHP 5.6 作为基准开始,研究添加或者更改了哪些内容。同时,我也在每一个被提及到的特性处添加了直达相关官方文档的链接,所以如果你有兴趣深入阅读,请随意。

我的官方群点击此处

PHP 7.0

匿名类的支持

在下面两种情况下,匿名类可能会被使用在命名类中:

当该类没有必要被记录下来的时候

当该类在程序执行过程中只使用一次的时候

new class($i) {
    public function __construct($i) {
        $this->i = $i;
    }
}

整除函数 — 安全的除法 (即使是被 0 整除)

该函数会返回第一个参数被第二个参数整除后结果的整数部分。当除数(也就是第二个参数)值为 0 时,该函数会抛出一个 E_WARNING 的错误并且返回 FALSE。

intdiv(int $numerator, int $divisor)

增加了新的空合并操作赋— 也就是 “??”

$x = NULL;
$y = NULL;
$z = 3;
var_dump($x ?? $y ?? $z); // int(3)

$x = ["c" => "meaningful_value"];
var_dump($x["a"] ?? $x["b"] ?? $x["c"]); // string(16) "meaningful_value"

添加新的操作符 — 飞船符(<=>)

飞船符用于优化和简化比较操作。

// 使用 <=> (飞船符)前
function order_func($a, $b) {
    return ($a < $b) ? -1 : (($a > $b) ? 1 : 0);
}
// 使用 <=> (飞船符)之后
function order_func($a, $b) {
    return $a <=> $b;
}

标量类型声明

这只是在 PHP — v0.5 中实现更强类型编程语言特性的第一步。

function add(float $a, float $b): float {
    return $a + $b;
}

add(1, 2); // float(3)

返回类型声明

增加了返回包括继承在内的标量类之外的其他类型的特性。不知何故没有将其设置为可选特性 (将在 v7.1 中说明)

interface A {
    static function make(): A;
}
class B implements A {
    static function make(): A {
        return new B();
    }
}

组使用声明

// 显式使用语法:
use FooLibrary\Bar\Baz\ClassA;
use FooLibrary\Bar\Baz\ClassB;
use FooLibrary\Bar\Baz\ClassC;
use FooLibrary\Bar\Baz\ClassD as Fizbo;
// 分组使用语法:
use FooLibrary\Bar\Baz\{ ClassA, ClassB, ClassC, ClassD as Fizbo };

生成器委托

生成器函数体中允许使用如下新的语法:

yield from <expr>

性能提升

PHP 7 比 PHP 5.6 快上两倍。

[图片上传失败...(image-2bef9f-1607342330595)]

显著降低内存占用

[图片上传失败...(image-aeadd9-1607342330595)]

从图表中可以看出, PHP 7.0 在性能和(减少)内存占用上有巨大改进。 对于带有数据库查询的页面,7.0.0 版本在启用 opcache 的情况下比 5.6 版本 快 3 倍,在未启用 opcache 的情况下比其 快 2.7 倍。在内存占用方面,两者的差异也是非常明显的。

**Throwable 接口****

** 重构的异常类具有非直观的命名方案,并且可以减少混淆,特别是对于初学者。

ErrorsExceptions 现在 实现了 Throwable.

这是 Throwable 层次结构:

interface Throwable
|- Error implements Throwable
    |- ArithmeticError extends Error
        |- DivisionByZeroError extends ArithmeticError
    |- AssertionError extends Error
    |- ParseError extends Error
    |- TypeError extends Error
        |- ArgumentCountError extends TypeError
|- Exception implements Throwable
    |- ClosedGeneratorException extends Exception
    |- DOMException extends Exception
    |- ErrorException extends Exception
    |- IntlException extends Exception
    |- LogicException extends Exception
        |- BadFunctionCallException extends LogicException
            |- BadMethodCallException extends BadFunctionCallException
        |- DomainException extends LogicException
        |- InvalidArgumentException extends LogicException
        |- LengthException extends LogicException
        |- OutOfRangeException extends LogicException
    |- PharException extends Exception
    |- ReflectionException extends Exception
    |- RuntimeException extends Exception
        |- OutOfBoundsException extends RuntimeException
        |- OverflowException extends RuntimeException
        |- PDOException extends RuntimeException
        |- RangeException extends RuntimeException
        |- UnderflowException extends RuntimeException
        |- UnexpectedValueException extends RuntimeException

*⚠ 警告!你只能

通过继承ErrorException来实现Throwable

也就是说一个继承自Throwable的接口只能被ExceptionError的子类来实现。

Unicode Codepoint 转义语法**** — “\u{xxxxx}”

echo "\u{202E}Reversed text"; //输出反转文本
echo "mañana"; // "ma\u{00F1}ana"
echo "mañana"; // "man\u{0303}ana" "n" 结合 ~ 字符 (U+0303)

上下文敏感的语法分析器

****全局保留的单词都成为了 半保留:

callable  class  trait  extends  implements  static  abstract  final  public  protected  private  const
enddeclare  endfor  endforeach  endif  endwhile  and  global  goto  instanceof  insteadof  interface
namespace  new  or  xor  try  use  var  exit  list  clone  include  include_once  throw  array
print  echo  require  require_once  return  else  elseif  default  break  continue  switch  yield
function  if  endswitch  finally  for  foreach  declare  case  do  while  as  catch  die  self parent

除了仍然禁止定义一个名为 class类常量 之外,因为类名解析 ::class

生成器返回表达式

统一变量语法

dirname () 函数的级别支持

PHP 7.1

可空类型

function answer(): ?int  {
    return null; //成功
}

function answer(): ?int  {
    return 42; // 成功
}

function answer(): ?int {
    return new stdclass(); // error
}
function say(?string $msg) {
    if ($msg) {
        echo $msg;
    }
}

say('hello'); // 成功 -- 打印 hello
say(null); // 成功 -- 不打印
say(); // 错误 -- 参数丢失
say(new stdclass); //错误 --错误类型

Void 返回

function should_return_nothing(): void {
    return 1; // 致命错误: void 函数不能有返回值
}

与调用函数时强制执行的其他返回类型不同, 这个类型会在编译时检查,这意味着错误会在函数没有调用的时候就会产生。

具有void返回类型或者void function的函数可以隐式返回,也可以使用不带值的返回语句:

function lacks_return(): void {
    // valid
}

iterable也可以用作返回类型,表示函数将返回一个可迭代的值。 如果返回的值不是Traversable的数组或实例,则抛出TypeError

function bar(): iterable {
    return [1, 2, 3];
}

声明为iterable的参数可以使用null或数组作为默认值。

function foo(iterable $iterable = []) {
    // ...
}

可调用的闭包

class Closure {
    ...
    public static function fromCallable(callable $callable) : Closure {...}
    ...
}

数组结构赋值的方括号语法

$array = [1, 2, 3];
//为 $a,$b 和 $c 按键值从 0 开始的方式分配 $array 数组元素的值
[$a, $b, $c] = $array;

// 使用 “a”,“b” 和 “c” 键分别为 $a,$b 和 $c 分配 $array 中数组元素的值
["a" => $a, "b" => $b, "c" => $c] = $array;

list() 的方括号语法

$powersOfTwo = [1 => 2, 2 => 4, 3 => 8];
list(1 => $oneBit, 2 => $twoBit, 3 => $threeBit) = $powersOfTwo;

类常量的可见性

class Token {
    // 常量默认为 public
    const PUBLIC_CONST = 0;

        // 常量也可以定义可见性
        private const PRIVATE_CONST = 0;
        protected const PROTECTED_CONST = 0;
        public const PUBLIC_CONST_TWO = 0;

        //常量只能有一个可见性声明列表
        private const FOO = 1, BAR = 2;
}

捕获多个异常类型

try {
// 部分代码...
} catch (ExceptionType1 | ExceptionType2 $e) {
// 处理异常的代码
} catch (\Exception $e) {
// ...
}

PHP 7.2

参数类型扩大

<?php

class ArrayClass {
public function foo(array $foo) { /* ... */ }
}

// 这个 RFC 提议允许类型被扩大为无类型,也就是任何类型。
// 类型可以作为参数传递。
// 任何类型的限制都可以通过用户写在方法体中的代码来实现。
class EverythingClass extends ArrayClass {
public function foo($foo) { /* ... */ }
}

不可数对象的计数

当一个标量或者没有实现Countable 接口的对象调用count()方法时会返回1(不合逻辑)。

PHP 7.2版本中,对以标量、null、或者一个没有实现Countable 接口接口的对象作为参数调用count()方法的情况,新增了一个WARNING警告。

在命名空间的列表用法中使用尾随逗号

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

Argon2 密码散列算法

现有的 password 函数为散列密码提供了一个向前兼容的简单接口。这个 RFC 提议 password 函数实现 Argon2i (v1.3),用来取代 Bcrypt 密码散列算法。

调试 PDO 预处理语句模拟

$db = new PDO(...);

// 生成没有绑定值的语句
$stmt = $db->query('SELECT 1');
var_dump($stmt->activeQueryString()); // => string(8) "SELECT 1"

$stmt = $db->prepare('SELECT :string');
$stmt->bindValue(':string', 'foo');

// 返回执行前,未解析的查询
var_dump($stmt->activeQueryString()); // => string(14) "SELECT :string"

// 返回执行后,已解析的查询
$stmt->execute();
var_dump($stmt->activeQueryString()); // => string(11) "SELECT 'foo'"

PHP 7.3

JSON_THROW_ON_ERROR

很长一段时间内在使用 JSON 时没有足够的方式去处理错误,全世界的开发人员都认为这是该语言的巨大缺点。

在 PHP v7.2 版本前,我们需要使用一种方法来从 JSON 中获取错误,虽然它既不可靠,也不精通;

例子如下:

json_decode("{");
json_last_error() === JSON_ERROR_NONE //结果是错误的
json_last_error_msg() // 结果是"语法错误"

那么让我们看看如何使用这种新语法糖:

use JsonException;

try {
    $json = json_encode("{", JSON_THROW_ON_ERROR);
    return base64_encode($json);
} catch (JsonException $e) {
    throw new EncryptException('Could not encrypt the data.', 0, $e);
}

从上面的代码可以看到json_encode函数现在有了一个可选参数JSON_THROW_ON_ERROR — 这将捕获错误并且用下列 异常方法 显示出来:

$e->getMessage(); // 相当于 json_last_error_msg()
$e->getCode(); // 相当于 json_last_error()

href="https://wiki.php.net/rfc/is-countable">添加is_countable函数

// 之前:
if (is_array($foo) || $foo instanceof Countable) {
    // $foo is countable
}
// 之后
if (is_countable($foo)) {
    // $foo is countable
}

添加数组函数array_key_first(), array_key_last()

$firstKey = array_key_first($array);
$lastKey = array_key_last($array);

原生支持同站点 Cookie 判断

有两种方式使用同站点 Cookie 判断:LaxStrict。它们的区别在于跨域 HTTP GET 请求中 Cookie 的可访问性。 使用 Lax 的 Cookie 允许跨域 GET 访问,而使用 Strict 的 Cookie 不允许跨域 GET 访问。 而 POST 方法则没有区别:因为浏览器不允许在跨域的 POST 请求中访问 Cookie。

Set-Cookie: key=value; path=/; domain=example.org; HttpOnly; SameSite=Lax|Strict

从 PCRE 迁移至 PCRE2

Argon2 哈希密码功能增强
现有的 password_* 函数为散列密码提供了前向兼容的简化接口。此 RFC 建议在 password _* 函数中实现Argon2id,以用作最初提出的 Argon2i 的安全替代方案。

在函数调用中允许尾随逗号

$newArray = array_merge(
    $arrayOne,
    $arrayTwo,
    ['foo', 'bar'], // 函数调用中允许使用逗号结尾
);

list () 使用参考

$array = [1, 2];
list($a, &$b) = $array;

相当于:

$array = [1, 2];
$a = $array[0];
$b = &$array[1];

PHP 7.4

参数类型(Typed properties)

class User {
    public int $id;
    public string $name;

    public function __construct(int $id, string $name) {
        $this->id = $id;
        $this->name = $name;
    }
}

外部函数接口(Foreign Function Interface)

外部函数接口(下简称FFI)是PythonLuaJIT在快速原型中非常实用的功能之一。FFI使得纯脚本语言能直接调用 C 语言函数和数据类型,从而更高效地开发「系统代码」。而 PHP 在FFI中开辟了一种使用 PHP 语言编写 PHP 扩展并绑定到 C 语言库的方法。

非空赋值运算符(Null Coalescing Assignment Operator)

// 下面几行代码完成相同功能
$this->request->data['comments']['user_id'] = $this->request->data['comments']['user_id'] ?? 'value';
// 使用非空赋值运算符,替代上面的方法
$this->request->data['comments']['user_id'] ??= 'value';

预加载(Preloading)

PHP 已经使用操作码缓存(opcode caches)很久了(APC、Turck MMCache、Zend OpCache)。它们通过几乎完全消除 PHP 代码重新编译的开销,实现了显著的性能提升。新的预加载功能将只需一个新的 php.ini 配置实现 ——opcache.preload。通过该配置指定一个 PHP 文件,该文件将执行预加载任务,然后通过包含其他文件或使用opcache_compile_file() 函数预加载其他文件。

始终可用的哈希扩展(Always available hash extension)

这将使 hash 扩展(ext/hash)始终可用,类似于 date。hash 扩展提供了非常丰富实用功能与哈希算法,这是不仅有利于 PHP 开发者,也有利于 PHP 本身的开发。

PHP 8.0

JIT.

简而言之。当你启动 PHP 程序时, Zend Engine 会将代码解析为抽象语法树(AST)并将其转换为操作码。操作码是 ** Zend 虚拟机的执行单元 ** (Zend VM)。 操作码相当底层(low-leve),转换为机器代码比原始 PHP 代码要快得多。 PHP 在核心中有一个名为 OPcache 的扩展,用于缓存这些操作码。

“JIT” 是一种在运行时编译部分代码的技术,因此可以使用编译版本。

这是仍在讨论的最新和最大的 PHP 优化策略之一。 PHP 工程师正期待这个新的功能可以在他们的应用中挤压出来多少性能。我自己是真的热衷于亲眼看到这一点。

内部函数的一致类型错误

如果参数解析失败,则使得内部参数解析 API 始终生成TypeError错误。应该要注意的是, 这些错误也包括用来表示传递太少 / 很多参数的情况的ArgumentCountError(TypeError的子类) 。

性能比较

我编写了一个简单的测试来帮助轻松比较不同 PHP 版本的性能(使用 Docker )。 这甚至可以通过添加新容器名称轻松检查新 PHP 版本的性能。

在 Macbook pro,2.5 GHz Intel Core i7 上运行。

PHP 版本 : 5.6.40
--------------------------------------
test_math                 : 1.101 sec.
test_stringmanipulation   : 1.144 sec.
test_loops                : 1.736 sec.
test_ifelse               : 1.122 sec.
Mem: 429.4609375 kb Peak mem: 687.65625 kb
--------------------------------------
Total time:               : 5.103

PHP 版本 : 7.0.33
--------------------------------------
test_math                 : 0.344 sec.
test_stringmanipulation   : 0.516 sec.
test_loops                : 0.477 sec.
test_ifelse               : 0.373 sec.
Mem: 421.0859375 kb Peak mem: 422.2109375 kb
--------------------------------------
Total time:               : 1.71

PHP 版本 : 7.1.28
--------------------------------------
test_math                 : 0.389 sec.
test_stringmanipulation   : 0.514 sec.
test_loops                : 0.501 sec.
test_ifelse               : 0.464 sec.
Mem: 420.9375 kb Peak mem: 421.3828125 kb
--------------------------------------
Total time:               : 1.868

PHP 版本 : 7.2.17
--------------------------------------
test_math                 : 0.264 sec.
test_stringmanipulation   : 0.391 sec.
test_loops                : 0.182 sec.
test_ifelse               : 0.252 sec.
Mem: 456.578125 kb Peak mem: 457.0234375 kb
--------------------------------------
Total time:               : 1.089

PHP 版本 : 7.3.4
--------------------------------------
test_math                 : 0.233 sec.
test_stringmanipulation   : 0.317 sec.
test_loops                : 0.171 sec.
test_ifelse               : 0.263 sec.
Mem: 459.953125 kb Peak mem: 460.3984375 kb
--------------------------------------
Total time:               : 0.984

PHP 版本 : 7.4.0-dev
--------------------------------------
test_math                 : 0.212 sec.
test_stringmanipulation   : 0.358 sec.
test_loops                : 0.205 sec.
test_ifelse               : 0.228 sec.
Mem: 459.6640625 kb Peak mem: 460.109375 kb
--------------------------------------
Total time:               : 1.003

发展方向

在整个 PHP 7.x 版本中,有一条通往更多类型化(和更客观)和现代编程语言的可见路径。尽管如此,PHP 还是喜欢采用其他编程语言中简洁有用的特性。

很快我们就能看到一些更好的功能,例如:

参考

PHP: rfc

https://www.cloudways.com/blog/php-5-6-vs-...

WordPress 5.0 PHP 7.2 vs PHP 7.3 Performance and Speed Benchmark...

image

以上内容希望帮助到大家

很多PHPer在进阶的时候总会遇到一些问题和瓶颈,业务代码写多了迷茫没方向,不知道该从哪儿入手去提升自己。→→管理整理了一些资料,有 腾讯 等一线大厂进阶知识体系 可供参考(相关学习资料以及笔面试题)
覆盖各个技术栈:分布式架构、高可扩展、高性能、高并发、服务器性能调优、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql优化、shell脚本、Docker、微服务、Nginx等多个知识点高级进阶干货欢迎加入我的官方群点击此处

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容