【AICon】AI 基础设施、LLM运维、大模型训练与推理,一场会议,全方位涵盖! >>> 了解详情
写点什么

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:0012107

评论

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

阿里巴巴《Java权威面试指南(全彩版)》来袭,冲击“金九银十”有望了

Java 程序员 架构 面试 后端

架构实战营模块9作业

喻高咏        

架构实战营

阿里内部“SpringCloudAlibaba学习笔记”全彩第三版开源

Java spring 架构 面试 微服务

《Go 开发指南》-快速安装 Go 环境

看山

Effective-go 10月月更

6. python 查漏补缺,namedtuple 命名元组,双向队列 deque,Counter 计数器,可排序字典

梦想橡皮擦

10月月更

【LeetCode】 第三大的数Java题解

Albert

算法 LeetCode 10月月更

Netflix实用API设计(上)

俞凡

架构 netflix API 大厂实践 10月月更

起飞!这份技术点拉满的ELk+Lucene笔记,可能价值百万

Java 架构 面试 程序人生 编程语言

白月光与朱砂痣-Flannel略糙,Cilium太美

Lance

【Spring源码分析】带你正视一下Spring祖容器之BeanFactory的原理与功能分析(1)

洛神灬殇

spring 容器 spring源码 BeanFactory 10月月更

【Flutter 专题】37 图解 Flutter 基本动画 (二)

阿策小和尚

Flutter 小菜 0 基础学习 Flutter Android 小菜鸟 10月月更

“区块链+政务”纵深发展 链上共识提升服务效能

CECBC

代码要写注释了吗?

HelloWorld杰少

领航计划

点击量破百万!阿里内产微服务进阶讲义,简直是Java开发者的福音

Java 编程 架构 面试 程序人生

【Vuex 源码学习】第七篇 - Vuex 的模块安装

Brave

源码 vuex 10月月更

0 基础架构入门 - 5(微博评论的高性能高可用计算架构)

felix

架构实战营 0 基础架构入门

美团大佬的Java性能调优实战手册,上线当天 下载量破百万!

Java 编程 架构 程序人生

架构实战营模块 8 作业指导

华仔

架构实战营

Jupyter Notebook从入门到精通,TensorFlow一个计算机视觉示例 易筋 ARTS 打卡 Week 68

John(易筋)

ARTS 打卡计划

架构实战营 - 模块九作业

Julian Chu

架构实战营

新技术|基于信号特征的语音编码器Lyra Android实践

轻口味

android 音视频 引航计划 10月月更

这篇阿里扫地僧所写关于SpringCloudAlibaba的笔记真香!

Java 架构 面试 程序人生 编程语言

Web安全应急响应小记

网络安全学海

网络安全 信息安全 渗透测试 WEB安全 应急响应

在线base64加密解密工具

入门小站

工具

008云原生之Serverless架构

穿过生命散发芬芳

云原生 10月月更

Netflix实用API设计(下)

俞凡

架构 netflix API 大厂实践 10月月更

Python代码阅读(第32篇):随机返回列表中的一个元素

Felix

Python 编程 Code Programing 阅读代码

不愧是阿里巴巴内部Spring Boot实战文档,这细节讲解,神了

Java spring 架构 面试 微服务

项目管理中常见的十个问题

石云升

项目管理 管理 引航计划 内容合集 10月月更

私有云部署系列之动态IP获取(前期准备)

稻草鸟人

Python 私有云

架构实战营 模块九(毕业设计) 作业

一雄

作业 架构实战营 毕业设计 模块九

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