阿里、蚂蚁、晟腾、中科加禾精彩分享 AI 基础设施洞见,现购票可享受 9 折优惠 |AICon 了解详情
写点什么

PHP 8:函数和方法

  • 2023-01-21
    北京
  • 本文字数:12605 字

    阅读完需:约 41 分钟

PHP 8:函数和方法

本文属于专题文章《深入浅出 PHP 8》


根据w3tech的数据,PHP 仍然是 Web 上使用最广泛的脚本语言之一,77.3%的网站使用 PHP 进行服务器端编程。PHP 8 带来了许多新特性和其他改进,我们将在本系列文章中进行探讨。PHP 8.0 添加了对多个函数和方法相关特性的支持,其中一些是对现有特性的改进,而另一些则是全新的特性。PHP 8.1 中增强的可调用语法可用于通过可调用对象创建匿名函数。命名函数参数可以与位置参数一起使用,另外还有一个好处,即命名参数没有顺序,可以通过它们的名称来传达含义。纤程(Fiber)是可中断的函数,增加了对多任务的支持。

重新定义了私有方法上的继承

对象继承是大多数面向对象语言(包括 PHP)所使用的编程范式。它可以从任何扩展类中重写公共和受保护的方法,以及在类中定义的类属性和常量。在 PHP 中,公共方法不能通过更严格的访问来重新实现,例如将 public 方法设为 private 。为了演示这一点,考虑一个扩展了类 A 的类 B,它重新实现了类 A 中一个公共方法。


<?phpclass 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 中一个私有方法。


<?phpclass 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 中能正常运行。


<?phpclass 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来禁用构造函数。


<?phpclass A {  private final function __construct(){           }} class B extends A {  private final function __construct(){      }}
复制代码


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


致命错误:不能重写最终方法A::__construct()
复制代码

可变参数可以替换任意数量的函数参数

在 PHP 8 中,单个可变参数可以替换任意数量的函数参数。考虑下面的脚本,其中类 B 扩展了类 A,并用一个可变参数替换函数 sortArray 的三个参数。


 <?phpclass 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=new B();$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'](...);
复制代码


这有两个问题:


  1. 语法涉及字符串和数组

  2. 在创建可调用时,作用域不会被维护。为了演示这一点,请考虑如下用于对数组进行排序的脚本,其中 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 = new Sort($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() {              return Sort::aStaticFunction(...);             }        private  static  function aStaticFunction() {    }}$sortType="Asc";$arrayToSort=array("B", "A", "f", "C");$sort = new Sort($arrayToSort,$sortType);$cStatic=$sort->getStaticMethod();$cStatic();
复制代码


输出结果与之前的相同:


0 = A 1 = B 2 = C 3 = f
复制代码


以下是调用方法的等效方法:


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 = new Sort($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 = new Sort($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(new A,1);  fn2(null,1);?> 
复制代码

命名函数形参和实参

PHP 8.0 除了已经支持的位置形参和实参之外,还增加了对命名函数形参和实参的支持。命名参数在函数调用中的传递语法如下所示:


参数名称:参数值
复制代码


命名参数的一些好处如下所示:


  • 可以为函数参数指定一个有意义的名称,使它们能够自我记录

  • 按名称传递时,参数与顺序无关

  • 可以任意跳过默认值。在下面的脚本中, array_hashtable 函数声明了命名参数。 该函数传递的实参值可以带参数名,也可以不带参数名。当传递位置实参时,使用函数形参声明顺序。但传递命名实参时,可以使用任意顺序。


<?phpfunction 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 2510 0 20 50 25
复制代码


命名实参和位置实参可以在同一函数调用中使用。对相同的示例函数 array_hashtable 一起使用混合参数调用。


<?phpfunction 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
复制代码


请注意,命名参数只能用于位置参数之后。下面的脚本颠倒了顺序,在命名参数之后使用位置参数:


<?phpfunction 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); ?>
复制代码


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


致命错误:不能在命名参数后使用位置参数
复制代码


即使使用命名参数,也不推荐在必选参数之前声明可选参数,脚本如下所示:


<?phpfunction 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被隐式视为必选参数
复制代码


当在必选命名形参之后使用可选命名形参时,命名实参可用于跳过函数调用中的一个或多个可选形参,脚本如下所示:


<?phpfunction 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
复制代码


你可以只使用可选参数的子集来调用函数,而不用考虑它们的顺序。


<?phpfunction 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
复制代码


即使使用可选参数的子集调用函数,也不能在命名参数之后使用位置参数,脚本如下所示:


<?phpfunction 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 改进了命名实参特性,在解包实参后支持命名实参,脚本如下所示:


<?phpfunction array_hashtable($key1,$key2,$key3=30,$key4=40,$key5=50){   echo $key1.' '.$key2.' '.$key3.' '.$key4.' '.$key5;  echo "<br>";}echo array_hashtable(...[10, 20], key5: 40);  echo array_hashtable(...['key2' => 2, 'key1' => 2], key4: 50);   ?>
复制代码


输出如下所示:


10 20 30 40 402 2 30 50 50
复制代码


但是,命名的参数不能覆盖前面的参数,脚本如下所示:


<?phpfunction 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()


<?phpclass 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获取纤程回调的混合返回值。 如果纤程没有返回语句,则返回NULL。 如果纤程未终止或纤程抛出异常,则抛出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 = f0 = f 1 = C 2 = B 3 = A0 = 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() instanceof Stringable);
复制代码


该脚本从 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:


<?phpif (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 of datathird 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']);
echo array_is_list([1 => 'a', 'b']); // falseecho array_is_list([1 => 'a', 0 => 'b']); // falseecho array_is_list([0 => 'a', 'b' => 'b']); // falseecho array_is_list([0 => 'a', 2 => 'b']); // false
复制代码


输出为:


111
复制代码

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

魔术方法是 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() { }}
复制代码


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


已弃用(不推荐):A::read()的返回类型应与Directory::read(): string|false兼容,或者应使用#[\ReturnTypeWillChange]属性来临时抑制通知
复制代码


但是,以下脚本是可以的:


<?php
class A extends Directory { public function read():string { return ""; }}
复制代码


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


<?phpclass 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 = new ArrayIterator($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:类和枚举

2023-01-21 08:0012077

评论

发布
暂无评论
发现更多内容

2023 云原生编程挑战赛火热报名中!导师解析 Serverless 冷启动赛题

阿里巴巴云原生

阿里云 Serverless 云原生

Android 架构模式如何选择

vivo互联网技术

mvc Compose MVVM 解耦 MVI

探索Linux命名空间和控制组:实现资源隔离与管理的双重利器

柠檬汁Code(binbin0325)

Linux 容器 namespace 底层原理 Cgroups

如何为Spring和Mybatis增加可逆计算支持

canonical

Spring Boot mybatis 低代码 可逆计算 Nop平台

RLHF 技术:如何能更有效?又有何局限性?

Baihai IDP

人工智能 强化学习 白海科技 RLHF 大语言模型

Spring AOP 中的代理对象是怎么创建出来的?

江南一点雨

Java spring

特性快闪:使用 Databend 玩转 Iceberg

Databend

PoseiSwap:通过 RWA 的全新叙事,反哺 Nautilus Chain 生态

大瞿科技

细数不懂Spring底层原理带来的伤与痛

java易二三

spring 程序员 Spring Boot 计算机 底层原理

火山引擎AB测试:广告实验深度打通巨量引擎,高效测试广告素材

字节跳动数据平台

大数据 A/B测试 对比试验 企业号 7 月 PK 榜 数字化增长

解析游戏陪练app源码的开发与意义

山东布谷网络科技

游戏 开源代码 APP软件开发

活动回顾|火山引擎DataLeap分享:DataOps、数据治理、指标体系最佳实践(文中领取PPT)

字节跳动数据平台

数据中台 数据治理 抖音 DataOps 企业号 7 月 PK 榜

Linux系统安装gcc详细教程。

百度搜索:蓝易云

云计算 Linux 运维 服务器 GCC

工赋开发者社区 | 面向CPS的制造执行系统(MES)实验平台验证

工赋开发者社区

Nodejs快速搭建简单的HTTP服务器详细教程。

百度搜索:蓝易云

node.js 云计算 Linux 运维 HTTP

面试官:说出 Java 中的 7 种重试机制

java易二三

编程 程序员 面试 计算机

这次是运行在 Intel AIxBoard™ 开发板上的 TDengine 预测“未来”

爱倒腾的程序员

工赋开发者社区 | 复杂电子装备制造数字化工厂实现逻辑与实施步骤

工赋开发者社区

Linux系统安装MySQL详细教程

百度搜索:蓝易云

MySQL 云计算 Linux 运维 服务器

在 Go 语言单元测试中如何解决 MySQL 存储依赖问题

江湖十年

golang Web 后端 单元测试 测试 单元测试

企业号 8 月 PK 榜,火热开启!

InfoQ写作社区官方

热门活动 企业号 8 月 PK 榜

直播平台源码开发,信息收发功能搭建

山东布谷科技

软件开发 直播 源码搭建 消息发送 直播平台源码

【华秋干货铺】一文轻松搞定PCB叠层和阻抗设计

华秋电子

TypeScript 玩转类型操作之字符串处理能力

小乌龟快跑

typescript 面试 前端

7月征文活动结果出炉,快来看看有没有你

InfoQ写作社区官方

热门活动 年中技术盘点

Centos7系统中找不到yum及安装方法。

百度搜索:蓝易云

云计算 Linux centos 运维 yum

合并k个已排序的链表

二哈侠

分享一些常用的开源博客社区网站

兮动人

博客 开源社区

飞桨AI Studio可以玩多模态了?MiniGPT4实战演练!

飞桨PaddlePaddle

人工智能 百度 paddle 飞桨 百度飞桨

未来前端框架将持续推进组件化开发

没有用户名丶

零信任体系化能力建设(1):身份可信与访问管理

权说安全

PHP 8:函数和方法_编程语言_Deepak Vohra_InfoQ精选文章