PHP 8:函数和方法

2023-01-30 0 984

译者 | Deepak Vohra

翻译者 | 刘雅梦

策画 | 丁晓昀

责任编辑归属于专题讲座该文《精辟 PHP 8》。 依照w3tech的统计数据,PHP 依然是 Web 上采用最广为的JavaScript众所周知,77.3%的网站采用 PHP 展开服务端程式设计。PHP 8 增添了很多新优点和其它改良,他们将在本系列产品该文中展开深入探讨。PHP 8.0 加进了对数个表达式和方式有关优点的全力支持,当中许多是对原有优点的改良,而另一半则是全新的优点。PHP 8.1 中进一步增强的可初始化句法可用作透过可初始化第一类建立非官方表达式。重新命名表达式模块能与边线模块一同采用,除此之外除了两个益处,即重新命名模块没次序,能透过它的中文名称来传递涵义。纤程(Fiber)是可受阻的表达式,减少了对虚拟化的全力支持。

再次表述了专有方式上的承继

第一类承继是绝大多数面向第一类词汇(主要包括 PHP)所采用的程式设计本体论。它能从任何人扩充类中改写公用和受保护的方式,和在类中表述的类特性和自变量。在 PHP 中,公用方式无法透过更严苛的出访来再次同时实现,比如将public 方式设为 private 。为的是模拟这一点儿,考量两个扩充了类 A 的类 B,它再次同时实现了类 A 中两个公用方式。

<?php class A{ public function sortArray():string{ return “Class A method”; } } class B extends A{ private function sortArray():string{ return “Class B method”; } } $b=new B(); echo$b->sortArray();

运行时,脚本会生成如下的一条错误信息:

致命错误:B::sortArray()的出访级别必须是公用的(与类A一样) 公用方式无法再次同时实现。

相反,在类中表述的专有方式不是承继的,能在扩充它的类中再次同时实现。比如,类 B 在下面的脚本中扩充了类 A,并再次同时实现了类 A 中两个专有方式。

<?php class A{ private function sortArray():string{return “Class A method”; } } class B extends A{ private function sortArray(int $a):string{ return “Class B method”; } }

在 PHP 8.0 之前,对扩充类中专有方式的再次声明应用了两个限制:不允许更改 finalstatic 修饰符。如果 private 方式被声明为 final ,则不允许扩充类再次声明该方式。如果专有方式被声明为静态的,那么它将在扩充类中保持静态。而且,如果专有方式没static 修饰符,则不允许扩充类加进static修饰符。在 PHP 8 中,这两个限制都被取消了。以下脚本在 PHP 8 中能正常运行。

<?php class A { private final static function sortArray():string{ return “Class A method”; } } class B extends A { private function sortArray(int $a):string{ return “Class B method”; } }

PHP 8 中唯一的专有方式限制是强制采用 private final 构造表达式,当采用静态工厂方式作为替代时,有时会采用private final来禁用构造表达式。

<?php class A { private final function __construct(){ } } class B extends A { private final function __construct(){ } }

该脚本生成如下的错误信息:

致命错误:无法改写最终方式A::__construct()

可变模块能替换任意数量的表达式模块

在 PHP 8 中,单个可变模块能替换任意数量的表达式模块。考量下面的脚本,当中类 B 扩充了类 A,并用两个可变模块替换表达式 sortArray 的三个模块。

<?php class A { public function sortArray(array $arrayToSort, string $sortType, int $arraySize) { if ($sortType == “asc”) { sort($arrayToSort); foreach ($arrayToSort as $key => $val) { echo “$key = $val “; } }elseif ($sortType == “desc”) { rsort($arrayToSort); foreach ($arrayToSort as$key => $val) {echo “$key = $val “; } } } } class B extends A { public function sortArray(…$multiple) { $arrayToSort= $multiple[0]; $sortType=$multiple[1]; if ($sortType == “asc”) { sort($arrayToSort); foreach ($arrayToSort as $key => $val) { echo “$key = $val “; } }elseif ($sortType == “desc”) { rsort($arrayToSort); foreach ($arrayToSort as$key => $val) {echo “$key = $val “; } } } }

能采用数个模块初始化类 B 中的 sortArray 表达式。

$sortType=“asc”; $arrayToSort=array(“B”, “A”, “f”, “C”); $arraySize=4; $b=newB(); $b->sortArray($arrayToSort,$sortType,$arraySize)

输出结果如下所示:

0 = A 1 = B 2 = C 3 = f

简化了可初始化句法

可初始化(callable)是能被初始化的 PHP 表达式,比如实例方式、静态方式或可初始化第一类。比如,可初始化可用作为方式初始化建立简短的表达式。在 PHP 8.1 中,能用新的可初始化句法:

AVariableCallableExpression(…)

AVariableCallableExpression 表示两个变量可初始化表达式。省略号…包含在句法中。

为什么要采用新的可初始化句法呢?让他们透过许多示例来回顾一下传统的可初始化句法是什么样子的: $f1 = strlen(…); $f2 = [$someobj, somemethod](); $f3 = [SomeClass::class, somestaticmethod]();

这有两个问题:

句法涉及字符串和数组在建立可初始化时,作用域不会被维护。为的是模拟这一点儿,请考量如下用作对数组展开排序的脚本,当中 getSortArrayMethod() 方式返回 sortArray()方式的可初始化项,[$this,sortArray]<?php class Sort { private array $arrayToSort; private string $sortType; public function __construct($arrayToSort,$sortType) { $this->arrayToSort = $arrayToSort; $this->sortType = $sortType; }public function getSortArrayMethod() { return [$this, sortArray]; } private function sortArray() { if ($this->sortType == “Asc”) { sort($this->arrayToSort); foreach ($this->arrayToSort as$key => $val) {echo “$key = $val “; } } elseif ($this->sortType == “Desc”) { rsort($this->arrayToSort); foreach ($this->arrayToSort as $key => $val) { echo “$key = $val “; } }else { shuffle($this->arrayToSort); foreach ($this->arrayToSort as $key => $val) { echo “$key = $val “; } } } } $sortType=“Asc”; $arrayToSort=array(“B”, “A”, “f”, “C”); $sort = newSort($arrayToSort,$sortType); $c = $sort->getSortArrayMethod(); $c();

该脚本会生成如下的错误信息:

致命错误:未捕获错误:初始化专有方式Sort::sortArray()来自全局作用域

采用 Closure::fromCallable([$this, sortArray]) 而不是 [$this, sortArray] 能解决作用域问题,但采用 Closure::fromCallable方式会使初始化变得冗长。新的可初始化句法解决了作用域和句法冗长的问题。采用新的可初始化句法,表达式变为:

public function getSortArrayMethod() { return $this->sortArray(…); }

数组依照输出展开排序:

0 = A 1 = B 2 = C 3 = f

新句法能与涉及字符串和数组的传统句法结合采用,以解决作用域问题。建立可初始化的作用域将保持不变。

public function getSortArrayMethod() { return [$this, sortArray](…); }

新的可初始化句法也能与静态方式一同采用,如下面的脚本所示,该脚本包含两个静态表达式。

<?php class Sort { private array $arrayToSort; private string $sortType; public function __construct($arrayToSort,$sortType) { $this->arrayToSort = $arrayToSort;$this->sortType = $sortType; } public function getStaticMethod() { returnSort::aStaticFunction(…); }private static function aStaticFunction() { } } $sortType=“Asc”; $arrayToSort=array(“B”, “A”, “f”, “C”); $sort = newSort($arrayToSort,$sortType); $cStatic=$sort->getStaticMethod(); $cStatic();

输出结果与之前的相同:

0 = A 1 = B 2 = C 3 =

以下是初始化方式的等效方式:

return $this->sortArray(…); return Closure::fromCallable([$this, sortArray]); return [$this,sortArray](…);

以下是初始化静态方式的等效方式:

return Sort::aStaticFunction(…); return [Sort::class, aStaticFunction](…);return Closure::fromCallable([Sort::class, aStaticFunction]);

即使表达式声明了形参,也能采用新的可初始化句法。

<?php class Sort { private array $arrayToSort; private string $sortType; public function __construct($arrayToSort,$sortType) { $this->arrayToSort = $arrayToSort; $this->sortType = $sortType; } public function getSortArrayMethod() { return $this->sortArray(…); } private function sortArray(int $a,string $b) { if ($this->sortType ==“Asc”) { sort($this->arrayToSort); foreach ($this->arrayToSort as $key => $val) { echo “$key = $val “; } } elseif ($this->sortType == “Desc”) { rsort($this->arrayToSort);foreach ($this->arrayToSort as $key => $val) { echo “$key = $val “; } } else{ shuffle($this->arrayToSort); foreach ($this->arrayToSort as $key => $val) { echo “$key = $val “; } } } }

如果两个方式声明了任意模块,则必须采用它的模块来初始化可初始化第一类。

$sortType=“Asc”; $arrayToSort=array(“B”, “A”, “f”, “C”); $sort = newSort($arrayToSort,$sortType); $c = $sort->getSortArrayMethod(); $c(1,“A”);

简化句法可用作任意的 PHP Callable 表达式

简化的可初始化句法能用作任意的 PHP 可初始化表达式。用作第一类建立的 new 运算符不全力支持可初始化句法,因为可初始化句法 AVariableCallableExpression(…) 没指定构造表达式模块的规定,这可能是必需的。以下是不全力支持的示例:

$sort = new Sort(…);

生成的错误信息为:

致命错误:无法为new表达式建立闭包

以下的脚本模拟了受全力支持的所有可初始化表达式。

<?php class Sort { private array $arrayToSort; private string $sortType; public function __construct($arrayToSort,$sortType) { $this->arrayToSort = $arrayToSort; $this->sortType = $sortType; }public function getSortArrayMethod() { return $this->sortArray(…); } public function getStaticMethod() { return Sort::aStaticFunction(…); } public static function aStaticFunction() { } public function sortArray(int $a,string $b) { if ($this->sortType == “Asc”) { sort($this->arrayToSort); foreach ($this->arrayToSort as $key => $val) { echo “$key = $val “; } }elseif ($this->sortType == “Desc”) { rsort($this->arrayToSort); foreach ($this->arrayToSort as$key => $val) {echo “$key = $val “; } } else { shuffle($this->arrayToSort); foreach ($this->arrayToSort as $key => $val) { echo “$key = $val “; } } } public function __invoke() {} } $sortType=“Asc”; $arrayToSort=array(“B”, “A”, “f”, “C”); $classStr =Sort; $staticmethodStr = aStaticFunction; $c1 = $classStr::$staticmethodStr(…); $methodStr =sortArray; $sort = newSort($arrayToSort,$sortType); $c2 = strlen(…); $c3 = $sort(…);// 可初始化第一类$c4 = $sort->sortArray(…); $c5 = $sort->$methodStr(…); $c6 = Sort::aStaticFunction(…); $c7 = $classStr::$staticmethodStr(…);// 传统的可初始化采用字符串,数组 $c8 = strlen(…); $c9 = [$sort, sortArray](…); $c10 = [Sort::class,aStaticFunction](…); $c11 = $sort->getSortArrayMethod(); $c11(1,“A”); $cStatic=$sort->getStaticMethod(); $cStatic();

尾逗号和可选/必选的模块次序

PHP 8.0 的另两个新优点是全力支持在表达式的模块列表末尾添加两个尾逗号,以提高可读性。任何人尾逗号都将被忽略。尾逗号可能并不总是有用的,但如果模块列表很长,或者模块中文名称很长,则可能会有用,因此能垂直列出它。闭包采用列表也全力支持尾逗号。

PHP 8.0 不全力支持在必选模块之前声明可选模块。在必选模块之前声明的可选模块都是隐式的必选模块。

下面的脚本模拟了必选模块的隐式次序,和尾逗号的采用。

<?php function trailing_comma_example( $the_very_first_arg_of_this_function, $the_second_arg_of_this_function, $the_third_arg_of_this_function =1, $the_fourth_arg_of_this_function, $the_last_arg_of_this_function, ){ echo$the_third_arg_of_this_function; } trailing_comma_example(1,2,null,3,4) ?>

输出如下所示:

已弃用(不推荐):在必选模块$the_last_arg_of_this_function之前声明的可选模块 $the_third_arg_of_tis_function将被隐式地视为必选模块

可空模块不会被视为可选模块,能采用 $param=null形式或显式可空类型在必选模块之前声明,脚本如下所示:

<?php class A {} function fn1(A $a = null, $b) {} function fn2(?A $a, $b) {} fn1(newA,1); fn2(null,1); ?>

重新命名表达式形参和实参

PHP 8.0 除了已经全力支持的边线形参和实参之外,还减少了对重新命名表达式形参和实参的全力支持。重新命名模块在表达式初始化中的传递句法如下所示:

模块中文名称:模块值

重新命名模块的许多益处如下所示:

能为表达式模块指定两个有意义的中文名称,使它能够自我记录按中文名称传递时,模块与次序无关能任意跳过默认值。在下面的脚本中, array_hashtable表达式声明了命名模块。 该表达式传递的实参值能带模块名,也能不带模块名。当传递边线实参时,采用表达式形参声明次序。但传递重新命名实参时,能采用任意次序。<?php function array_hashtable($key1,$key2,$key3,$key4,$key5){ echo $key1..$key2..$key3..$key4..$key5; echo “<br>”; } // 采用边线实参:array_hashtable(0, 10, 50, 20, 25); // 采用重新命名实参: array_hashtable(key2: 0, key5: 25, key1: 10, key4: 50, key3: 20); ?>

输出结果为:

0 10 50 20 25

重新命名实参和边线实参能在同一表达式初始化中采用。对相同的示例表达式 array_hashtable 一同采用混合模块初始化。

<?php function array_hashtable($key1,$key2,$key3,$key4,$key5){ echo $key1..$key2..$key3..$key4..$key5; echo “<br>”; } // 采用混合模块: array_hashtable(0, 10, 50, key5: 25, key4: 20); ?>

输出结果为:

0 10 50 20 25

请注意,重新命名模块只能用作边线模块之后。下面的脚本颠倒了次序,在重新命名模块之后采用位置模块:

<?php function array_hashtable($key1,$key2,$key3,$key4,$key5){ echo$key1..$key2..$key3..$key4..$key5;echo “<br>”; } // Using mixed arguments: array_hashtable(0, 10, key3: 25, 50, key5: 20); ?>

该脚本生成的错误信息为:

致命错误:无法在重新命名模块后采用边线模块

即使采用重新命名模块,也不推荐在必选模块之前声明可选模块,脚本如下所示:

<?php function array_hashtable($key1=0,$key2=10,$key3=20,$key4,$key5){ echo $key1..$key2..$key3..$key4..$key5; echo “<br>”; } // 采用混合模块:array_hashtable(1,2,key3: 25, key4: 1,key5: 20); ?>

输出将主要包括已弃用(不推荐)信息:

已弃用(不推荐):在必选模块$key5之前声明的可选模块$key1被隐式视为必选模块 已弃用(不推荐):在必选模块$key5之前声明的可选模块$key2被隐式视为必选模块 已弃用(不推荐):在必选模块$key5之前声明的可选模块$key3被隐式视为必选模块

当在必选重新命名形参之后采用可选重新命名形参时,重新命名实参可用作跳过表达式初始化中的两个或数个可选形参,脚本如下所示:

<?php function array_hashtable($key1,$key2,$key3=20,$key4=50,$key5=10){ echo $key1..$key2..$key3..$key4..$key5; echo “<br>”; } // 采用混合模块:array_hashtable(key1:1, key2:2,key4: 25); ?>

输出结果为:

1 2 20 25 10

你能只采用可选模块的子集来初始化表达式,而不用考量它的次序。

<?php function array_hashtable($key1=0,$key2=10,$key3=20,$key4=50,$key5=10){ echo $key1..$key2..$key3..$key4..$key5; echo “<br>”; }// 采用混合模块: array_hashtable(1,2,key4: 25); ?>

输出结果如下所示:

1 2 20 25 10

即使采用可选模块的子集初始化表达式,也无法在重新命名模块之后采用边线模块,脚本如下所示:

<?php function array_hashtable($key1=0,$key2=10,$key3=20,$key4=50,$key5=10){ echo$key1..$key2..$key3..$key4..$key5;echo “<br>”; } // Using mixed arguments: array_hashtable(1,2,key4: 25,5); ?>

生成的错误信息以下所示:

致命错误:无法在重新命名模块后采用边线模块

PHP 8.1 改良了重新命名实参优点,在解包实参后全力支持重新命名实参,脚本如下所示:

<?php function array_hashtable($key1,$key2,$key3=30,$key4=40,$key5=50){ echo $key1..$key2..$key3..$key4..$key5; echo “<br>”; } echoarray_hashtable(…[10, 20], key5: 40); echo array_hashtable(…[key2 => 2, key1 => 2], key4: 50); ?>

输出如下所示:

10 20 30 40 40 2 2 30 50 50

但是,重新命名的模块无法盖前面的模块,脚本如下所示:

<?php function array_hashtable($key1,$key2,$key3=30,$key4=40,$key5=50){ echo $key1..$key2..$key3..$key4..$key5; echo “<br>”; } echo array_hashtable(…[10, 20], key2: 40);?>

输出如下所示:

致命错误:未捕获错误:重新命名模块$key2覆盖上两个模块。

非静态方式无法被静态初始化

在 PHP 8.0 之前,如果在静态上下文中初始化非静态方式,或者静态初始化,则只会收到一条已弃用(不推荐)的信息。采用 8.0,你现在会收到一条错误信息。此外,$this 在静态上下文中是未表述的。为的是模拟这一点儿,请考量如下的脚本,当中采用静态句法 A::aNonStaticMethod()初始化了非静态方法aNonStaticMethod()

<?php class A { function aNonStaticMethod() { } } A::aNonStaticMethod();

如果你运行这个脚本,将会得到如下的错误信息:

未捕获错误:非静态方式A::aNonStaticMethod()无法被静态初始化

纤程

PHP 8.1 加进了对纤程(Fiber)虚拟化的支持。纤程是两个可受阻的表达式,它具有自己的堆栈。纤程能从初始化堆栈中的任何人边线挂起,然后再恢复。新的 Fiber 类是两个 final 类,它全力支持以下的公用方式:

方式

描述

__construct(callable $callback) <br>

建立新Fiber实例的构造表达式。 该模块是启动纤程时要初始化的可初始化第一类。 提供给Fiber::start()的模块将作为给定可初始化第一类的模块提供。 可初始化第一类根本不需要初始化Fiber::suspend(),或者即使初始化也不需要直接初始化。对Fiber::suspend()的初始化可能在初始化堆栈中嵌套很深。

start(mixed …$args): mixed

启动纤程。该方式在纤程挂起或终止时返回。在构造纤程时,为所采用的可初始化表达式提供了可变的模块列表。如果纤程返回,则从第两个挂起点返回混合值或返回NULL。如果初始化该方式时纤程已经启动,则会抛出FiberError错误。

resume(mixed $value = null): mixed

恢复纤程,从Fiber::suspend()返回给定的混合值。 当纤程挂起或终止时返回。 返回的混合值实际上是从下两个挂起点返回的,如果纤程返回,则返回NULL。 如果纤程尚未启动、正在运行或已终止,则抛出FiberError错误。

throw(Throwable $exception): mixed

将给定的异常从Fiber::suspend()抛出到纤程中。 当纤程挂起或终止时返回。 模块是Throwable $exception。 返回的混合值实际上是从下两个挂起点返回的,如果纤程返回,则返回NULL。 如果纤程尚未启动、正在运行或已终止,则抛出FiberError错误。

getReturn(): mixed

出FiberError错误。

isStarted(): bool

如果纤程已经启动,则返回布尔值True。

isSuspended(): bool

如果纤程已挂起,则返回布尔值True。

isRunning(): bool

如果纤程正在运行,则返回布尔值True。

isTerminated(): bool

如果纤程已终止,则返回布尔值True。

static suspend(mixed $value = null): mixed

挂起纤程。 返回对Fiber->start()、Fiber->resume() 或 Fiber->throw() 的初始化执行。能采用Fiber::resume()或Fiber::throw()恢复纤程。无法从纤程外的主线程初始化。 模块是从Fiber::resume()或 Fiber::throw()返回的混合值$value。返回的混合值提供给Fiber::resume()。<br>如果不在纤程内(即,如果从主线程初始化),则抛出FiberError错误。

static getCurrent(): ?Fiber

返回当前正在执行的纤程实例,如果在主线程中则返回NULL。

纤程只能启动一次,但能挂起并恢复多次。下面的脚本透过采用纤程在数组上执行不同类型的排序来模拟虚拟化处理。纤程在每次排序后都会挂起,然后再恢复执行不同类型的排序。

<?php $fiber = new Fiber(function (array $arr): void { sort($arr); foreach ($arr as $key => $val) { echo “$key = $val “; } echo “<br/>”; Fiber::suspend(); rsort($arr); foreach ($arr as $key => $val) { echo “$key = $val “; } echo “<br/>”; Fiber::suspend(); shuffle($arr); foreach ($arr as$key => $val) {echo “$key = $val “; } }); $arrayToSort=array(“B”, “A”, “f”, “C”); $value = $fiber->start($arrayToSort); $fiber->resume(); $fiber->resume();?>

输出如下所示:

0 = A 1 = B 2 = C 3 = f 0 = f 1 = C 2 = B 3 = A 0 = C 1 = f 2 = A 3 = B

如果纤程在第一次挂起后没再恢复,则只展开一种类型的排序,这能透过注释掉对 resume() 的两次初始化来同时实现。

//$fiber->resume(); //$fiber->resume();

输出的是第一次排序的结果:

0 = A 1 = B 2 = C 3 = f

Stringable 接口和 __toString()

PHP 8.0 引入了一个名为Stringable 的新接口,它只提供两个方式 __toString()__toString() 方式如果在类中提供,将隐式同时实现 Stringable 接口。考量提供 __toString()方式的类 A。

<?php class A { public function __toString(): string { return ” “; } } echo (new A() instanceofStringable);

该脚本从 Stringable 的类型检查中返回 1。

然而,反之则不然。如果类同时实现了 Stringable 接口,则必须显式提供 __toString()方式,因为该方式不会自动加进,比如:

<?php class A implements Stringable { public function __toString(): string { } }

新的标准库表达式

PHP 8 引入了很多归属于其标准库的新表达式。

str_contains 表达式返回两个 bool 值,用作指示作为第两个模块的字符串是否包含作为第二个模块的字符串。以下脚本将返回 false

<?php if (str_contains(haystack, needle)) { echo true; } else { echo false; }

下面的脚本返回 1,或 true:

<?php if (str_contains(haystack, hay)) { echo true; }else { echo “false”; }

str_starts_with 表达式返回两个bool值 ,指示作为第两个模块的字符串是否以作为第二个模块的字符串开头。以下脚本将返回false

<?php if (str_contains(haystack, hay)) { echo true; }else { echo “false”; }

下面的脚本将返回 1,或 true。

<?php if (str_starts_with(haystack, needle)) { echo true; } else{echo false; }

str_ends_with 表达式返回两个bool 值 ,指示作为第两个模块的字符串是否以作为第二个模块的字符串结尾。以下脚本将返回 false

<?php if(str_starts_with(haystack, needle)) { echo true; } else { echo false; }

下面的脚本将返回 1,或 true。

<?php if (str_starts_with(haystack, hay)) { echo true; } else { echo false; }

fdiv 表达式将两个数字相除并返回两个 float 值,脚本如下所示:

<?php var_dump(fdiv(1.5, 1.3)); var_dump(fdiv(10, 2)); var_dump(fdiv(5.0, 0.0)); var_dump(fdiv(-2.0, 0.0)); var_dump(fdiv(0.0, 0.0)); var_dump(fdiv(5.0, 1.0)); var_dump(fdiv(10.0, 2));

输出为:

float(1.1538461538461537)float(5) float(INF) float(-INF) float(NAN) float(5) float(5)

fdatasync 表达式在 Windows 上的别名为 fsync ,用作将统计数据同步到文件上的流中。为的是模拟它的用法,在包含要运行的 PHP 脚本的脚本目录中建立两个空文件 test.txt 。运行脚本:

<?php $file = test.txt; $stream = fopen($file,w); fwrite($stream, first line of data); fwrite($stream, “\r\n”); fwrite($stream,second line of data); fwrite($stream, third line of data); fdatasync($stream); fclose($stream);

随后,打开 test.txt 文件会发现包含如下的文本:

first line of datasecond line ofdata third line of data

array_is_list 表达式返回布尔值,用作指示给定的数组是否为列表。数组必须从 0 开始,键必须是连续的整数,并且次序正确。下面的脚本模拟了 array_is_list 表达式:

<?php echo array_is_list([]); echo array_is_list([1, 2, 3]); echo array_is_list([0 => a, b]); echoarray_is_list([1 => a, b]); // false echo array_is_list([1 => a, 0 => b]); // false echo array_is_list([0 => a, b => b]); // false echo array_is_list([0 => a, 2 => b]); // false

输出为:

1 1 1

魔术方式必须要有正确的签名

魔术方式是 PHP 中用作覆盖默认操作的特殊方式。它主要包括如下的方式,其中构造表达式__construct() 可能是大家最熟悉的:

__construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __serialize(), __unserialize(), __toString(), __invoke(), __set_state(), __clone(), __debugInfo()

从 PHP 8.0 开始,魔术方式表述的签名必须要是正确的,这意味着如果在方式模块或返回类型中采用类型声明,则它必须与文档中的声明相同。新的 __toString() 方式的返回类型必须要声明为 string 。下面的模拟将返回类型声明为 int

<?php class A { public function __toString(): int { } }

将生成如下的错误信息:

致命错误:A::__toString():返回类型在声明时必须是字符串

但是,未透过表述声明返回类型的表达式(如构造表达式)无法声明返回类型,即使是 void返回类型也不行。示比如下脚本所示:

<?php class A { public function __construct():void { } }

该脚本将返回如下的错误信息:

致命错误:方式A::__construct()无法声明返回类型

所有魔术方式,除了少数例外(比如 __construct() )外,都必须声明为具有公用可见性。为的是模拟这一点儿,声明了两个带有 private 可见性的 __callStatic

<?php class A { private static function __callStatic(string $name, array $arguments) {} }

输出的警告信息为:

警告:魔术方式A::__callStatic()必须要具有公用可见性

尽管能省略混合返回类型,但方式签名也必须相同。比如,在下面的脚本中,类 A 声明了 __callStatic而没指定其返回类型,而类 B 将其第两个模块表述为int

<?php class A { public static function __callStatic(string $name, array $arguments) {} class B { public static function __callStatic(int $name, array $arguments) {} }

输出的错误信息如下所示:

致命错误:B::__callStatic():模块 #1 ($name) 在声明时必须为字符串类型

返回类型与内部类的兼容性

在 PHP 8.1 中,绝大多数内部方式,即内部类中的方式,都已经“试探性地”开始声明返回类型。试探性地暗示,虽然在 8.1 中只会引发不推荐(Deprecation)通知,但在 9.0 版中,则会输出错误条件信息。因此,任何人扩充类都必须声明与内部类相兼容的返回类型,否则将会引发已弃用(不推荐)通知。为的是模拟这一点儿,扩充内部类Directory 并再次声明没返回类型的表达式 read()

<?php class A extends Directory { public function read() { } }

该脚本将生成已弃用(不推荐)通知:

<?php class A extends Directory { public function read():string { return “”; } }

但是,以下脚本是能的:

<?php class A extends Directory { public function read():string { return “”; } }

加进 #[\ReturnTypeWillChange] 特性能抑制已弃用(不推荐)通知:

<?php class A extends Directory { #[\ReturnTypeWillChange] public function read() { } }

\SensitiveParameter 特性

虽然包含有关方式模块的详细信息的异常堆栈跟踪对调试非常有用,但你可能不希望输出某些敏感模块的模块值,比如与密码和凭据关联的模块值。PHP 8.2 引入了两个名为\SensitiveParameter 的新特性,这样,如果采用 \SensitivyParameter特性注解方式的模块,则该模块的值不会在异常堆栈跟踪中输出。

为的是模拟这一点儿,考量下面的脚本,当中表达式 f1 具有与 \SensitiveParameter 特性关联的模块 $password

<?php function f1( $param1 =1, #[\SensitiveParameter] $password = “s@5f_u7”, $param3 = null ) { throw new \Exception(Error); }

为的是模拟 \SensitiveParameter优点,该表达式只是抛出两个异常。初始化表达式:

f1(param3: a);

请注意,异常堆栈跟踪不包含 $password 模块的值,而是列出了 Object(SensitiveParameterValue)

Stack trace: #0 : f1(1, Object(SensitiveParameterValue),a) #1 {main}

内置表达式弃用/进一步增强

内置表达式 utf8_encode()utf8_decode()经常被误解,因为它的中文名称意味着对任何人字符串展开编码/解码。实际上,这些表达式仅用作编码/解码 ISO8859-1,即“Latin-1”字符串。此外,它生成的错误信息对于调试来说描述性不够。PHP 8.2 已经弃用了这些表达式。下面的脚本采用了它:

<?php $string_to_encode = “\x7A\x6A\xdB”; $utf8_string = utf8_encode($string_to_encode); echo bin2hex($utf8_string), “\n”; $utf8_string = “\x6A\x6B\xD3\xCB”; $decoded_string = utf8_decode($utf8_string);echo bin2hex($decoded_string), “\n”;

对于 PHP 8.2,会输出已弃用(不推荐)通知:

已弃用(不推荐):表达式utf8_encode()已弃用 已弃用(不推荐):表达式utf8_decode()已弃用

在 PHP 8.2 中,表达式 iterator_countiterator_to_array 接受所有可迭代的第一类。 iterator_to_array() 表达式将迭代器的元素复制到数组中。 iterator_count() 表达式对数组的元素展开计数。这些表达式接受两个 $iterator作为第两个模块。在 PHP 8.2 中,$iterator 模块的类型已从 Traversable 扩充为 Traversable|array ,以便接受任意的可迭代值。

下面的脚本模拟了它在 arraysTraversables 中的采用。

<?php $a=array(1=>one, two, three, four); $iterator = newArrayIterator($a); var_dump(iterator_to_array($iterator,true)); var_dump(iterator_to_array($a, true)); var_dump(iterator_count($iterator)); var_dump(iterator_count($a));

输出如下所示:

array(4) { [1]=> string(3) “one” [2]=> string(3) “two” [3]=> string(5) “three” [4]=> string(4) “four” } array(4) { [1]=> string(3) “one” [2]=> string(3) “two” [3]=> string(5) “three” [4]=> string(4) “four” } int(4) int(4)

总结

在这篇 PHP 8 系列产品该文中,他们讨论了与表达式和方式有关的新优点,当中最突出的是重新命名表达式的形参/实参、简化的可初始化句法和被称为纤程(Fiber)的可受阻表达式。

在本系列产品的下一篇该文中,他们将介绍 PHP 类型系统的新优点。

原文链接:

https://www.infoq.com/articles/php8-functions-methods/

有关阅读:

PHP 8:注解、match 表达式及其它改良

PHP 8:类和枚

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务