PHP8 的新特性和重大变化
PHP8将于2020年11月26日发布,关于此版本中即将推出的功能的大量文章 现已发布!由于这是一个主要版本,将会有新功能以及重大更改,因此了解正在发生的变化非常重要。这将使您更容易思考PHP8将如何影响您的应用程序以及您需要采取哪些措施来确保您可以顺利升级。
我想我会经历一些主要的变化,看看下一个PHP版本会发生什么。
运行PHP8
一个好的第一步是查看如何安装PHP8,以便您可以自己检查。如果您使用的是Ubuntu,那么安装PHP8的最简单方法是使用现有的ondrej/phpPPA库。这可以使用以下命令安装。
sudo add-apt-repository ppa:ondrej/php sudo apt-get update
您可以通过搜索PHP8来确保安装了正确的库。
sudoapt-cachesearchphp8.0
假设您看到上一条命令的一些输出,您现在可以使用此命令安装PHP8。
sudoapt-getinstallphp8.0
运行后,您现在可以运行PHP8。
$ php --version PHP 8.0.0rc1 (cli) (built: Oct 18 2020 19:43:43) ( NTS ) Copyright (c) The PHP Group Zend Engine v4.0.0-dev, Copyright (c) Zend Technologies with Zend OPcache v8.0.0rc1, Copyright (c), by Zend Technologies
除了自己安装之外,您现在还可以在以下平台上运行PHP8。
场地
平台.sh
您还可以在ExtendsClass站点上运行PHP8代码片段。
如果您也使用这些平台进行托管,这会很方便,因为它可以帮助您在发布前的大量时间内测试您的应用程序。如果您知道任何其他运行支持PHP8的平台,请发表评论并告诉我。
新特性
PHP8中有很多新特性。我在互联网上阅读了大量关于这些新功能的文章,但我想我会详细介绍每一个。
即时编译器(RFC)
Just-In-Time(或JIT)编译器是PHP7发布之前速度改进的结果。它被引入到PHP8中,因为如果不使用JIT,可能无法进行更多的速度改进。这个想法是它将进一步提高PHP性能。
当PHP代码被执行时,它被翻译成字节码,这些字节码用于执行程序中的步骤。JIT意味着PHP将分析正在执行的代码,并能够在代码执行时实时做出性能改进决策。这个想法是它将用于CPU密集型应用程序,而不一定用于基于Web的场景。这意味着服务器端PHP应用程序在PHP中内置的JIT系统中可能更流行。
要使用JIT,您首先需要激活它。在我的测试系统(Ubuntu20.04)上,我已经安装了PHPopcache模块,该模块与主要的PHP8包一起安装。这是在位于/etc/php/8.0/cli/conf.d/10-opcache.ini的文件中配置的。
要激活JIT,您需要启用opcache并为opcache.jit_buffer_size设置分配一些内存。这就是文件在我的系统上的样子。
; configuration for php opcache module ; priority=10 zend_extension=opcache.so opcache.enable_cli=1 opcache.jit_buffer_size=256M
您可以确保它处于活动状态,您可以使用该opcache_get_status()功能。您可以查看此数组的“jit”部分以获取有关JIT当前状态的信息。
var_dump(opcache_get_status()['jit']);
如果JIT已正确激活,这应该打印出如下内容。
array(7) { ["enabled"]=> bool(true) ["on"]=> bool(true) ["kind"]=> int(5) ["opt_level"]=> int(4) ["opt_flags"]=> int(6) ["buffer_size"]=> int(268435440) ["buffer_free"]=> int(268432880) }
那么它更快吗?一句话,是的。我见过一些人使用mandlebrot集进行基准测试,因此我决定使用我不久前创建的库,该库在PHP中绘制了几种不同类型的分形。我所做的只是生成三个分形以及使用该microtime()函数花费的时间。这是PHP7.4.8的结果。
Burningship 84.20269203186 Mandlebrot 21.552599906921 Tricorn 32.685042858124
当我在PHP8跑完全相同的代码,这是显着更快。数字不言自明。
Burningship 15.272277116776 Mandlebrot 3.7528541088104 Tricorn 4.4957919120789
这种大规模的速度提升真的很有趣。我在这里使用的代码创建了大小合适的分形,但我记得在创建代码时,我大部分时间都花在等待生成分形上。这个添加对我很感兴趣,因为我已经将PHP推到了它的极限几次(除了生成分形)。我可以看到这对PHP的未来有真正的好处,并且允许在通常的网站语言之外的情况下选择该语言。
我没有看到像Drupal或WordPress这样的应用程序的速度提升,但从我读到的内容来看,这些类型的应用程序可能几乎没有区别。将来我将在这些平台上运行基准测试,看看JIT在那里产生了什么样的不同。
联合类型(RFC)
从PHP7开始,可以规定参数和返回值必须具有什么样的类型。如果您传递的参数类型与预期类型不同,这将允许PHP抛出错误。在像PHP这样的松散类型语言中,确保函数接收和生成正确类型的值很重要。
在PHP8中,现在可以为参数和返回值规定不同的类型,由管道字符分隔。这是一个可以接受整数或浮点值的函数示例。
function addNumbers(int|float $number1, int|float $number2) : int|float { return $number1 + $number2; }
以前,需要在没有任何类型提示的情况下创建这样的函数,因为如果传递的参数不正确,PHP可以静默地转换类型。这意味着如果我们将参数类型设置为整数,那么PHP会将任何浮点值转换为整数,如果您不进行单元测试,这可能会导致一些棘手的错误被捕获。
要使用上述函数,我们只需像其他函数一样调用它即可。
echo addNumbers(1, 1); // prints 2 echo addNumbers(1.1, 1.1); // prints 2.2
如果我们尝试像这样将字符串传递给函数。
echoaddNumbers('one','two');
我们将收到一个PHP致命错误,指出我们需要将int或float传递到函数中。
PHP Fatal error: Uncaught TypeError: addNumbers(): Argument #1 ($number1) must be of type int|float, string given, called in union_types.php on line 10 and defined in union_types.php:3
这是一个简单的示例,但我们可以对此进行扩展并创建一个接受多种不同类型变量的函数。下面是一个设计非常糟糕的函数的例子,它展示了如何扩展这个特性。
function printNumber(int|float|string|array|bool|null $number) : void { printf("%f\n", $number); }
这条规则的例外是void类型,可以在上面的函数中看到。void类型不能用作联合类型,因为它规定函数将不返回任何内容。换句话说,你不能说一个函数会返回一个整数或一个空值,否则你会收到一个PHP致命错误。
虽然一个简单的改变我可以看到这个特性被使用了很多,因为它以前只能在注释中规定不同类型的值,这导致文档块注释变得比代码更具描述性。
Nullsafe运算符(RFC)
除了空合并运算符之外,现在还可以直接从方法中检测空返回值。如果您不知道,空合并运算符允许我们获得一个值而无需测试该值是否存在,如果第一个值为空,则返回一个不同的值。这意味着我们可以这样做以从$_GET超级全局获取一个值,如果该值不存在,则为0。
$page = $_GET['page'] ?? 0; echo $page;
nullsafe运算符以类似的方式工作,但允许我们创建一个方便的快捷方式来在尝试使用该值之前测试方法的null返回。使用以下几个创建Horn和Car对象的类。
class Horn { public function beep() { print 'beep'; } } class Car { protected $horn; public function setHorn(Horn $horn) { $this->horn = $horn; } public function getHorn() { return $this->horn; } }
在这种情况下,可以创建一个没有Horn对象的Car对象来驱动喇叭。在这种情况下,我们需要确保该getHorn()方法在尝试使用之前不会返回null。通常这意味着if语句和is_null()检查以确保事物存在,但是使用nullsafe运算符我们可以内联执行此操作。下面的示例创建一个Car对象,并且beep()仅当该getHorn()方法不返回空值时才调用该方法。
$car = new Car(); $car->getHorn()?->beep();
我可以看到这在Drupal中非常有用,我倾向于编写大量检查代码以确保对象属性或方法的返回值在使用它们之前确实包含在其中。由于Drupal中对象包含的内容具有上下文性质,因此这是必要的,并且此更改肯定会简化某些检查。
命名参数(RFC)
命名参数允许您调用函数并规定不同的参数顺序。取以下具有两个参数的简单函数并将数组填充到指定长度。
function fillArray(array $arrayToFill, int $number) : array { for ($i = 0; $i < $number; ++$i) { $arrayToFill[$i] = 1; } return $arrayToFill; }
我们可以通过按照定义的顺序传递参数来以正常方式调用此方法。
$newArray=fillArray([],2);
从PHP8开始,我们现在可以在将参数传递给函数时命名参数,这也允许我们以我们想要的任何顺序发送参数。
$newArray=fillArray(number:2,arrayToFill:[]);
一个简单的例子,但它也可以使代码更具可读性。
这种技术适用于PHP中的任何函数,而不仅仅是用户定义的函数。PHP是一种语言,其中数组和字符串函数具有不同的参数顺序,这是一个非常受欢迎的补充。
属性V2(RFC1RFC2RFC3)
属性提供了一种向PHP类、方法、函数、类属性、函数参数和常量添加元数据的机制。它们不能通过代码直接访问,需要使用内置反射类的PHP拉出。ReflectionClass类从PHP5开始就存在于PHP中,但PHP8的新getAttribute()方法是该方法。此方法返回一个包含属性信息的ReflectionAttribute对象数组。
这个添加经历了一些变化(正如您从上面的多个RFC中看到的那样),但当前向您的代码添加属性的语法如下。
#[Automobile] #[MyCustomAttribute(1, 'string')] class Car { #[ThingSetup(123)] public function doThing() {} }
如果我们实例化类,我们就可以使用ReflectionClass打印出包含在类级别上的属性信息。
$car = new Car(); $reflectionClass = new ReflectionClass($car); foreach ($reflectionClass->getAttributes() as $attribute) { echo $attribute->getName() . ' ' . PHP_EOL; $arguments = $attribute->getArguments(); foreach ($arguments as $argument) { echo '-' . $argument . PHP_EOL; } }
这将打印出以下内容。
Automobile MyCustomAttribute -1 -string
我们可以使用getMethods()ReflectionClass的方法来查找和报告Car类中方法的属性。
foreach ($reflectionClass->getMethods() as $method) { foreach ($method->getAttributes() as $attribute) { echo $attribute->getName() . ' ' . PHP_EOL; $arguments = $attribute->getArguments(); foreach ($arguments as $argument) { echo '-' . $argument . PHP_EOL; } } }
此代码打印出以下内容。
ThingSetup -123
PHP8中实际上有很多属性,因此我建议您通读RFC以熟悉它们是什么以及如何将它们集成到您的代码中。
匹配表达式(RFC)
新的匹配表达式就像一个简写的switch语句。它看起来有点像一个函数声明,它会根据传入的值返回一个值。match语句的语法如下所示。
$value = 2; $result = match($value) { 1 => 'One', 2 => 'Two', 3 => 'Three', }; echo $result; // Prints 'Two'.
需要注意的一件事是switch语句使用“==”比较,而match语句使用“===”比较。此外,“match”这个词现在是一个保留关键字,因此您不能再有任何称为match的类或函数。
重大变化
由于这是PHP的新版本,因此会有一些更改会破坏现有代码,并且PHP团队一直在努力编制即将发布的版本中的破坏性更改列表。重大更改主要是由于在PHP7中标记为已弃用的代码在向PHP8的过渡过程中被删除。我在这里挑选了一些您可能想要了解的更有趣的变化。
构造方法删除
当面向对象被添加到PHP时,可以创建与类同名的构造函数方法,这与其他语言(如Java)的工作方式类似。该__construct()方法是在PHP5中添加的,从那时起,建议将构造函数移至新格式。在PHP8中,不能再使用类名作为构造函数,因此必须使用__construct()方法格式。
删除each()方法
该each()方法从数组中检索下一项和键,并将指针向前移动一个。这是在从数组中获取值的同时将指针移动到数组中的下一项的便捷方法。这现在已被删除,应使用foreach或ArrayIterator代替。
移除了偏移访问的花括号(RFC)
不再可能使用花括号访问数组中的元素或字符串中的字符。我不得不承认我最近没有看到有人使用它,但是您可能会发现一些包含这种代码的旧代码。这是取自PHP文档的此功能的示例。
$array = [1, 2]; echo $array[1]; // prints 2 echo $array{1}; // also prints 2 $string = "foo"; echo $string[0]; // prints "f" echo $string{0}; // also prints "f"
更严格的算术运算符类型检查(RFC)
在以前的PHP版本中,对数组或对象运行算术或按位运算会返回一个无意义的值。
var_dump([123]%[321]);//Prints"int(0)".
在PHP8中运行相同的代码会产生一个TypeError。
PHPFatalerror: UncaughtTypeError:Unsupportedoperandtypes:array%arrayin/vagrant/union_types.php:3
@操作符不再隐藏致命错误
由于可以在PHP7中捕获致命错误,因此也可以使用@运算符来抑制它们。在PHP8中,情况不再如此,并且可能会发现隐藏在PHP7中的致命错误。确保在您的生产服务器上始终将display_errors设置为off,以便在发生这种情况时不会将任何错误泄露给您的用户。
更清晰的字符串数字解析(RFC)
PHP在将字符串解析为数值时做了很多聪明的事情。在PHP8中,这略有变化,因此值得检查RFC以确保您了解更改的来源。基本上,不要总是依赖于正确解释为整数的“42”字符串值。
更清晰的数字字符串比较(RFC)
对PHP比较字符串和数字的方式进行了一些小的更改。检查RFC以获取详细信息,但例如,以下将在PHP7中返回true,在PHP8中返回false。
var_dump(42=="42abc");
结论
PHP8有一些变化,但看起来大部分被删除的弃用代码都是针对我有一段时间没有使用过的旧功能。我真的很想知道JIT引擎在我使用的代码库上有什么,甚至在更广泛的PHP社区中。也就是说,我强烈建议扫描您的代码库是否存在PHP8不兼容性并运行单元测试,以确保您的PHP应用程序在升级之前能够正常运行。
目前,我会通读重大更改文档并密切关注PHP8发布日历以获取任何公告。